From 42efc79ef11cbd7275b45254327d34877b310f0b Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Wed, 1 Sep 2010 02:02:47 -0400 Subject: [PATCH 01/85] Scheduler: add scheduled task persistence. The list of tasks scheduled with the Scheduler plugin is now saved on exit, and restored upon restart. Previously all scheduled tasks would be forgotten upon bot restart, which was undesirable behavior. --- plugins/Scheduler/plugin.py | 67 ++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/plugins/Scheduler/plugin.py b/plugins/Scheduler/plugin.py index dc45f297e..ce4460880 100644 --- a/plugins/Scheduler/plugin.py +++ b/plugins/Scheduler/plugin.py @@ -28,6 +28,10 @@ ### import time +import os +import shutil +import tempfile +import cPickle as pickle import supybot.conf as conf import supybot.utils as utils @@ -36,6 +40,10 @@ import supybot.schedule as schedule import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Scheduler') +import supybot.world as world + +datadir = conf.supybot.directories.data() +filename = conf.supybot.directories.data.dirize('Scheduler.pickle') class Scheduler(callbacks.Plugin): def __init__(self, irc): @@ -58,28 +66,20 @@ class Scheduler(callbacks.Plugin): except IOError, e: self.log.debug('Unable to open pickle file: %s', e) return - for name, event in eventdict.iteritems(): - ircobj = callbacks.ReplyIrcProxy(irc, event['msg']) - try: + try: + for name, event in eventdict.iteritems(): + ircobj = callbacks.ReplyIrcProxy(irc, event['msg']) if event['type'] == 'single': # non-repeating event - n = None - if schedule.schedule.counter > int(name): - # counter not reset, we're probably reloading the plugin - # though we'll never know for sure, because other - # plugins can schedule stuff, too. - n = int(name) self._add(ircobj, event['msg'], - event['time'], event['command'], n) + event['time'], event['command']) elif event['type'] == 'repeat': # repeating event self._repeat(ircobj, event['msg'], name, - event['time'], event['command'], False) - except AssertionError, e: - if str(e) == 'An event with the same name has already been scheduled.': - # we must be reloading the plugin, event is still scheduled - self.log.info('Event %s already exists, adding to dict.' % (name,)) - self.events[name] = event - else: - raise + event['time'], event['command']) + except AssertionError, e: + if str(e) == 'An event with the same name has already been scheduled.': + pass # we must be reloading the plugin + else: + raise def _flush(self): try: @@ -95,7 +95,6 @@ class Scheduler(callbacks.Plugin): self.log.warning('File error: %s', e) def die(self): - self._flush() world.flushers.remove(self._flush) self.__parent.die() @@ -108,6 +107,16 @@ class Scheduler(callbacks.Plugin): self.Proxy(irc.irc, msg, tokens) return f + def _add(self, irc, msg, t, command): + f = self._makeCommandFunction(irc, msg, command) + id = schedule.addEvent(f, t) + f.eventId = id + self.events[str(id)] = {'command':command, + 'msg':msg, + 'time':t, + 'type':'single'} + return id + @internationalizeDocstring def add(self, irc, msg, args, seconds, command): """ @@ -118,10 +127,8 @@ class Scheduler(callbacks.Plugin): command was given in (with no prefixed nick, a consequence of using echo). Do pay attention to the quotes in that example. """ - f = self._makeCommandFunction(irc, msg, command) - id = schedule.addEvent(f, time.time() + seconds) - f.eventId = id - self.events[str(id)] = command + t = time.time() + seconds + id = self._add(irc, msg, t, command) irc.replySuccess(format(_('Event #%i added.'), id)) add = wrap(add, ['positiveInt', 'text']) @@ -146,9 +153,9 @@ class Scheduler(callbacks.Plugin): irc.error(_('Invalid event id.')) remove = wrap(remove, ['lowered']) - def _repeat(self, irc, msg, name, seconds, command, now=True): + def _repeat(self, irc, msg, name, seconds, command): f = self._makeCommandFunction(irc, msg, command, remove=False) - id = schedule.addPeriodicEvent(f, seconds, name, now) + id = schedule.addPeriodicEvent(f, seconds, name) assert id == name self.events[name] = {'command':command, 'msg':msg, @@ -168,10 +175,7 @@ class Scheduler(callbacks.Plugin): if name in self.events: irc.error(_('There is already an event with that name, please ' 'choose another name.'), Raise=True) - self.events[name] = command - f = self._makeCommandFunction(irc, msg, command, remove=False) - id = schedule.addPeriodicEvent(f, seconds, name) - assert id == name + self._repeat(irc, msg, name, seconds, command) # We don't reply because the command runs immediately. # But should we? What if the command doesn't have visible output? # irc.replySuccess() @@ -187,8 +191,11 @@ class Scheduler(callbacks.Plugin): if L: L.sort() for (i, (name, command)) in enumerate(L): - L[i] = format('%s: %q', name, command) + L[i] = format('%s: %q', name, command['command']) irc.reply(format('%L', L)) + irc.reply(schedule.schedule.schedule) + irc.reply(schedule.schedule.events) + irc.reply(schedule.schedule.counter) else: irc.reply(_('There are currently no scheduled commands.')) list = wrap(list) From f6e3698c24abf61bcda2665c1fc80c5de254919e Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Wed, 1 Sep 2010 16:37:55 -0400 Subject: [PATCH 02/85] Scheduler: handle event persistence on plugin reload. Write data to disk on unload; populate events dict with events which are still scheduled on reload. --- plugins/Scheduler/plugin.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/plugins/Scheduler/plugin.py b/plugins/Scheduler/plugin.py index ce4460880..cb4d1f657 100644 --- a/plugins/Scheduler/plugin.py +++ b/plugins/Scheduler/plugin.py @@ -66,20 +66,28 @@ class Scheduler(callbacks.Plugin): except IOError, e: self.log.debug('Unable to open pickle file: %s', e) return - try: - for name, event in eventdict.iteritems(): - ircobj = callbacks.ReplyIrcProxy(irc, event['msg']) + for name, event in eventdict.iteritems(): + ircobj = callbacks.ReplyIrcProxy(irc, event['msg']) + try: if event['type'] == 'single': # non-repeating event + n = None + if schedule.schedule.counter > int(name): + # counter not reset, we're probably reloading the plugin + # though we'll never know for sure, because other + # plugins can schedule stuff, too. + n = int(name) self._add(ircobj, event['msg'], - event['time'], event['command']) + event['time'], event['command'], n) elif event['type'] == 'repeat': # repeating event self._repeat(ircobj, event['msg'], name, event['time'], event['command']) - except AssertionError, e: - if str(e) == 'An event with the same name has already been scheduled.': - pass # we must be reloading the plugin - else: - raise + except AssertionError, e: + if str(e) == 'An event with the same name has already been scheduled.': + # we must be reloading the plugin, event is still scheduled + self.log.info('Event %s already exists, adding to dict.' % (name,)) + self.events[name] = event + else: + raise def _flush(self): try: @@ -95,6 +103,7 @@ class Scheduler(callbacks.Plugin): self.log.warning('File error: %s', e) def die(self): + self._flush() world.flushers.remove(self._flush) self.__parent.die() @@ -107,9 +116,9 @@ class Scheduler(callbacks.Plugin): self.Proxy(irc.irc, msg, tokens) return f - def _add(self, irc, msg, t, command): + def _add(self, irc, msg, t, command, name=None): f = self._makeCommandFunction(irc, msg, command) - id = schedule.addEvent(f, t) + id = schedule.addEvent(f, t, name) f.eventId = id self.events[str(id)] = {'command':command, 'msg':msg, From f3a20df8f104cb721906a62c7582df4f4085ce3c Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sun, 20 Feb 2011 11:13:57 +0100 Subject: [PATCH 03/85] Import Conditional from gribble repo --- plugins/Conditional/README.txt | 1 + plugins/Conditional/__init__.py | 66 ++++++ plugins/Conditional/config.py | 49 +++++ plugins/Conditional/local/__init__.py | 1 + plugins/Conditional/plugin.py | 276 ++++++++++++++++++++++++++ plugins/Conditional/test.py | 143 +++++++++++++ 6 files changed, 536 insertions(+) create mode 100644 plugins/Conditional/README.txt create mode 100644 plugins/Conditional/__init__.py create mode 100644 plugins/Conditional/config.py create mode 100644 plugins/Conditional/local/__init__.py create mode 100644 plugins/Conditional/plugin.py create mode 100644 plugins/Conditional/test.py diff --git a/plugins/Conditional/README.txt b/plugins/Conditional/README.txt new file mode 100644 index 000000000..d60b47a97 --- /dev/null +++ b/plugins/Conditional/README.txt @@ -0,0 +1 @@ +Insert a description of your plugin here, with any notes, etc. about using it. diff --git a/plugins/Conditional/__init__.py b/plugins/Conditional/__init__.py new file mode 100644 index 000000000..8a48b27be --- /dev/null +++ b/plugins/Conditional/__init__.py @@ -0,0 +1,66 @@ +### +# Copyright (c) 2010, Daniel Folkinshteyn +# 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. + +### + +""" +Add a description of the plugin (to be presented to the user inside the wizard) +here. This should describe *what* the plugin does. +""" + +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. +__contributors__ = {} + +# This is a url where the most recent plugin package can be downloaded. +__url__ = '' # 'http://supybot.com/Members/yourname/Conditional/download' + +import config +import plugin +reload(plugin) # In case we're being reloaded. +# 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! + +if world.testing: + import test + +Class = plugin.Class +configure = config.configure + + +# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/plugins/Conditional/config.py b/plugins/Conditional/config.py new file mode 100644 index 000000000..ab69968bd --- /dev/null +++ b/plugins/Conditional/config.py @@ -0,0 +1,49 @@ +### +# Copyright (c) 2010, Daniel Folkinshteyn +# 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. + +### + +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('Conditional', True) + + +Conditional = conf.registerPlugin('Conditional') +# This is where your configuration variables (if any) should go. For example: +# conf.registerGlobalValue(Conditional, 'someConfigVariableName', +# registry.Boolean(False, """Help for someConfigVariableName.""")) + + +# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/plugins/Conditional/local/__init__.py b/plugins/Conditional/local/__init__.py new file mode 100644 index 000000000..e86e97b86 --- /dev/null +++ b/plugins/Conditional/local/__init__.py @@ -0,0 +1 @@ +# Stub so local is a module, used for third-party modules diff --git a/plugins/Conditional/plugin.py b/plugins/Conditional/plugin.py new file mode 100644 index 000000000..6f9422152 --- /dev/null +++ b/plugins/Conditional/plugin.py @@ -0,0 +1,276 @@ +### +# Copyright (c) 2010, Daniel Folkinshteyn +# 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. + +### + +import supybot.utils as utils +from supybot.commands import * +import supybot.plugins as plugins +import supybot.ircutils as ircutils +import supybot.callbacks as callbacks + +import re + +# builtin any is overwritten by callbacks... and python2.4 doesn't have it +def _any(iterable): + for element in iterable: + if element: + return True + return False +# for consistency with above, and for python2.4 +def _all(iterable): + for element in iterable: + if not element: + return False + return True + + +class Conditional(callbacks.Plugin): + """Add the help for "@plugin help Conditional" here + This should describe *how* to use this plugin.""" + threaded = True + def __init__(self, irc): + callbacks.Plugin.__init__(self, irc) + + def _runCommandFunction(self, irc, msg, command): + """Run a command from message, as if command was sent over IRC.""" + tokens = callbacks.tokenize(command) + try: + self.Proxy(irc.irc, msg, tokens) + except Exception, e: + log.exception('Uncaught exception in requested function:') + + def cif(self, irc, msg, args, condition, ifcommand, elsecommand): + """ + + Runs if evaluates to true, runs + if it evaluates to false. + + Use other logical operators defined in this plugin and command nesting + to your advantage here. + """ + if condition: + self._runCommandFunction(irc, msg, ifcommand) + else: + self._runCommandFunction(irc, msg, elsecommand) + irc.noReply() + cif = wrap(cif, ['boolean', 'something', 'something']) + + def cand(self, irc, msg, args, conds): + """ [ ... ] + + Returns true if all conditions supplied evaluate to true. + """ + if _all(conds): + irc.reply("true") + else: + irc.reply("false") + cand = wrap(cand, [many('boolean'),]) + + def cor(self, irc, msg, args, conds): + """ [ ... ] + + Returns true if any one of conditions supplied evaluates to true. + """ + if _any(conds): + irc.reply("true") + else: + irc.reply("false") + cor = wrap(cor, [many('boolean'),]) + + def cxor(self, irc, msg, args, conds): + """ [ ... ] + + Returns true if only one of conditions supplied evaluates to true. + """ + if sum(conds) == 1: + irc.reply("true") + else: + irc.reply("false") + cxor = wrap(cxor, [many('boolean'),]) + + def ceq(self, irc, msg, args, item1, item2): + """ + + Does a string comparison on and . + Returns true if they are equal. + """ + if item1 == item2: + irc.reply('true') + else: + irc.reply('false') + ceq = wrap(ceq, ['anything', 'anything']) + + def ne(self, irc, msg, args, item1, item2): + """ + + Does a string comparison on and . + Returns true if they are not equal. + """ + if item1 != item2: + irc.reply('true') + else: + irc.reply('false') + ne = wrap(ne, ['anything', 'anything']) + + def gt(self, irc, msg, args, item1, item2): + """ + + Does a string comparison on and . + Returns true if is greater than . + """ + if item1 > item2: + irc.reply('true') + else: + irc.reply('false') + gt = wrap(gt, ['anything', 'anything']) + + def ge(self, irc, msg, args, item1, item2): + """ + + Does a string comparison on and . + Returns true if is greater than or equal to . + """ + if item1 >= item2: + irc.reply('true') + else: + irc.reply('false') + ge = wrap(ge, ['anything', 'anything']) + + def lt(self, irc, msg, args, item1, item2): + """ + + Does a string comparison on and . + Returns true if is less than . + """ + if item1 < item2: + irc.reply('true') + else: + irc.reply('false') + lt = wrap(lt, ['anything', 'anything']) + + def le(self, irc, msg, args, item1, item2): + """ + + Does a string comparison on and . + Returns true if is less than or equal to . + """ + if item1 <= item2: + irc.reply('true') + else: + irc.reply('false') + le = wrap(le, ['anything', 'anything']) + + def match(self, irc, msg, args, item1, item2): + """ + + Determines if is a substring of . + Returns true if is contained in . + """ + if item2.find(item1) != -1: + irc.reply('true') + else: + irc.reply('false') + match = wrap(match, ['something', 'something']) + + def nceq(self, irc, msg, args, item1, item2): + """ + + Does a numeric comparison on and . + Returns true if they are equal. + """ + if item1 == item2: + irc.reply('true') + else: + irc.reply('false') + nceq = wrap(nceq, ['float', 'float']) + + def nne(self, irc, msg, args, item1, item2): + """ + + Does a numeric comparison on and . + Returns true if they are not equal. + """ + if item1 != item2: + irc.reply('true') + else: + irc.reply('false') + nne = wrap(nne, ['float', 'float']) + + def ngt(self, irc, msg, args, item1, item2): + """ + + Does a numeric comparison on and . + Returns true if they is greater than . + """ + if item1 > item2: + irc.reply('true') + else: + irc.reply('false') + ngt = wrap(ngt, ['float', 'float']) + + def nge(self, irc, msg, args, item1, item2): + """ + + Does a numeric comparison on and . + Returns true if is greater than or equal to . + """ + if item1 >= item2: + irc.reply('true') + else: + irc.reply('false') + nge = wrap(nge, ['float', 'float']) + + def nlt(self, irc, msg, args, item1, item2): + """ + + Does a numeric comparison on and . + Returns true if is less than . + """ + if item1 < item2: + irc.reply('true') + else: + irc.reply('false') + nlt = wrap(nlt, ['float', 'float']) + + def nle(self, irc, msg, args, item1, item2): + """ + + Does a numeric comparison on and . + Returns true if is less than or equal to . + """ + if item1 <= item2: + irc.reply('true') + else: + irc.reply('false') + nle = wrap(nle, ['float', 'float']) + +Class = Conditional + + +# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/plugins/Conditional/test.py b/plugins/Conditional/test.py new file mode 100644 index 000000000..9dc856b94 --- /dev/null +++ b/plugins/Conditional/test.py @@ -0,0 +1,143 @@ +### +# Copyright (c) 2010, Daniel Folkinshteyn +# 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 ConditionalTestCase(PluginTestCase): + plugins = ('Conditional','Utilities',) + + def testCif(self): + self.assertError('cif stuff') + self.assertRegexp('cif [ceq bla bla] "echo moo" "echo foo"', 'moo') + self.assertRegexp('cif [ceq bla bar] "echo moo" "echo foo"', 'foo') + self.assertRegexp('cif [cand [ceq bla bla] [ne soo boo]] "echo moo" "echo foo"', 'moo') + self.assertRegexp('cif [ceq [echo $nick] "test"] "echo yay" "echo nay"', 'yay') + + def testCand(self): + self.assertRegexp('cand true true', 'true') + self.assertRegexp('cand false true', 'false') + self.assertRegexp('cand true false', 'false') + self.assertRegexp('cand false false', 'false') + self.assertRegexp('cand true true true', 'true') + + def testCor(self): + self.assertRegexp('cor true true', 'true') + self.assertRegexp('cor false true', 'true') + self.assertRegexp('cor true false', 'true') + self.assertRegexp('cor false false', 'false') + self.assertRegexp('cor true true true', 'true') + + def testCxor(self): + self.assertRegexp('cxor true true', 'false') + self.assertRegexp('cxor false true', 'true') + self.assertRegexp('cxor true false', 'true') + self.assertRegexp('cxor false false', 'false') + self.assertRegexp('cxor true true true', 'false') + + def testCeq(self): + self.assertRegexp('ceq bla bla', 'true') + self.assertRegexp('ceq bla moo', 'false') + self.assertError('ceq bla bla bla') + + def testNe(self): + self.assertRegexp('ne bla bla', 'false') + self.assertRegexp('ne bla moo', 'true') + self.assertError('ne bla bla bla') + + def testGt(self): + self.assertRegexp('gt bla bla', 'false') + self.assertRegexp('gt bla moo', 'false') + self.assertRegexp('gt moo bla', 'true') + self.assertError('gt bla bla bla') + + def testGe(self): + self.assertRegexp('ge bla bla', 'true') + self.assertRegexp('ge bla moo', 'false') + self.assertRegexp('ge moo bla', 'true') + self.assertError('ge bla bla bla') + + def testLt(self): + self.assertRegexp('lt bla bla', 'false') + self.assertRegexp('lt bla moo', 'true') + self.assertRegexp('lt moo bla', 'false') + self.assertError('lt bla bla bla') + + def testLe(self): + self.assertRegexp('le bla bla', 'true') + self.assertRegexp('le bla moo', 'true') + self.assertRegexp('le moo bla', 'false') + self.assertError('le bla bla bla') + + def testMatch(self): + self.assertRegexp('match bla mooblafoo', 'true') + self.assertRegexp('match bla mooblfoo', 'false') + self.assertError('match bla bla stuff') + + def testNceq(self): + self.assertRegexp('nceq 10.0 10', 'true') + self.assertRegexp('nceq 4 5', 'false') + self.assertError('nceq 1 2 3') + self.assertError('nceq bla 1') + + def testNne(self): + self.assertRegexp('nne 1 1', 'false') + self.assertRegexp('nne 2.2 3', 'true') + self.assertError('nne 1 2 3') + self.assertError('nne bla 3') + + def testNgt(self): + self.assertRegexp('ngt 3 3', 'false') + self.assertRegexp('ngt 2 3', 'false') + self.assertRegexp('ngt 4 3', 'true') + self.assertError('ngt 1 2 3') + self.assertError('ngt 3 bla') + + def testNge(self): + self.assertRegexp('nge 3 3', 'true') + self.assertRegexp('nge 3 4', 'false') + self.assertRegexp('nge 5 4.3', 'true') + self.assertError('nge 3 4.5 4') + self.assertError('nge 45 bla') + + def testNlt(self): + self.assertRegexp('nlt 3 3', 'false') + self.assertRegexp('nlt 3 4.5', 'true') + self.assertRegexp('nlt 5 3', 'false') + self.assertError('nlt 2 3 4') + self.assertError('nlt bla bla') + + def testNle(self): + self.assertRegexp('nle 2 2', 'true') + self.assertRegexp('nle 2 3.5', 'true') + self.assertRegexp('nle 4 3', 'false') + self.assertError('nle 3 4 5') + self.assertError('nle 1 bla') + +# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: From 9d7287a436d14313b3d637f020eb5304be1ca02f Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sun, 20 Feb 2011 11:14:29 +0100 Subject: [PATCH 04/85] Import MessageParser from gribble repo --- plugins/MessageParser/README.txt | 3 + plugins/MessageParser/__init__.py | 66 ++++ plugins/MessageParser/config.py | 71 ++++ plugins/MessageParser/local/__init__.py | 1 + plugins/MessageParser/plugin.py | 422 ++++++++++++++++++++++++ plugins/MessageParser/test.py | 174 ++++++++++ 6 files changed, 737 insertions(+) create mode 100644 plugins/MessageParser/README.txt create mode 100644 plugins/MessageParser/__init__.py create mode 100644 plugins/MessageParser/config.py create mode 100644 plugins/MessageParser/local/__init__.py create mode 100644 plugins/MessageParser/plugin.py create mode 100644 plugins/MessageParser/test.py diff --git a/plugins/MessageParser/README.txt b/plugins/MessageParser/README.txt new file mode 100644 index 000000000..25159d3cf --- /dev/null +++ b/plugins/MessageParser/README.txt @@ -0,0 +1,3 @@ +The MessageParser plugin allows you to set custom regexp triggers, which will trigger the bot to respond if they match anywhere in the message. This is useful for those cases when you want a bot response even when the bot was not explicitly addressed by name or prefix character. + +An updated page of this plugin's documentation is located here: http://sourceforge.net/apps/mediawiki/gribble/index.php?title=MessageParser_Plugin diff --git a/plugins/MessageParser/__init__.py b/plugins/MessageParser/__init__.py new file mode 100644 index 000000000..c2d0efb5a --- /dev/null +++ b/plugins/MessageParser/__init__.py @@ -0,0 +1,66 @@ +### +# Copyright (c) 2010, Daniel Folkinshteyn +# 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. + +### + +""" +Add a description of the plugin (to be presented to the user inside the wizard) +here. This should describe *what* the plugin does. +""" + +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. +__contributors__ = {} + +# This is a url where the most recent plugin package can be downloaded. +__url__ = '' # 'http://supybot.com/Members/yourname/MessageParser/download' + +import config +import plugin +reload(plugin) # In case we're being reloaded. +# 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! + +if world.testing: + import test + +Class = plugin.Class +configure = config.configure + + +# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/plugins/MessageParser/config.py b/plugins/MessageParser/config.py new file mode 100644 index 000000000..f70dbdb24 --- /dev/null +++ b/plugins/MessageParser/config.py @@ -0,0 +1,71 @@ +### +# Copyright (c) 2010, Daniel Folkinshteyn +# 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. + +### + +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('MessageParser', True) + +MessageParser = conf.registerPlugin('MessageParser') +# This is where your configuration variables (if any) should go. For example: +# conf.registerGlobalValue(MessageParser, 'someConfigVariableName', +# registry.Boolean(False, """Help for someConfigVariableName.""")) +conf.registerChannelValue(MessageParser, 'enable', + registry.Boolean(True, """Determines whether the + message parser is enabled. If enabled, will trigger on regexps + added to the regexp db.""")) +conf.registerChannelValue(MessageParser, 'keepRankInfo', + registry.Boolean(True, """Determines whether we keep updating the usage + count for each regexp, for popularity ranking.""")) +conf.registerChannelValue(MessageParser, 'rankListLength', + registry.Integer(20, """Determines the number of regexps returned + by the triggerrank command.""")) +conf.registerChannelValue(MessageParser, 'requireVacuumCapability', + registry.String('admin', """Determines the capability required (if any) to + vacuum the database.""")) +conf.registerChannelValue(MessageParser, 'requireManageCapability', + registry.String('admin; channel,op', + """Determines the + capabilities required (if any) to manage the regexp database, + including add, remove, lock, unlock. Use 'channel,capab' for + channel-level capabilities. + Note that absence of an explicit anticapability means user has + capability.""")) +conf.registerChannelValue(MessageParser, 'listSeparator', + registry.String(', ', """Determines the separator used between rexeps when + shown by the list command.""")) + +# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/plugins/MessageParser/local/__init__.py b/plugins/MessageParser/local/__init__.py new file mode 100644 index 000000000..e86e97b86 --- /dev/null +++ b/plugins/MessageParser/local/__init__.py @@ -0,0 +1 @@ +# Stub so local is a module, used for third-party modules diff --git a/plugins/MessageParser/plugin.py b/plugins/MessageParser/plugin.py new file mode 100644 index 000000000..62d8e9dad --- /dev/null +++ b/plugins/MessageParser/plugin.py @@ -0,0 +1,422 @@ +### +# Copyright (c) 2010, Daniel Folkinshteyn +# 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. + +### + +import supybot.utils as utils +from supybot.commands import * +import supybot.plugins as plugins +import supybot.ircutils as ircutils +import supybot.callbacks as callbacks + +import supybot.conf as conf +import supybot.ircdb as ircdb + +import re +import os +import time + +#try: + #import sqlite +#except ImportError: + #raise callbacks.Error, 'You need to have PySQLite installed to use this ' \ + #'plugin. Download it at ' \ + #'' + +try: + import sqlite3 +except ImportError: + from pysqlite2 import dbapi2 as sqlite3 # for python2.4 + +# these are needed cuz we are overriding getdb +import threading +import supybot.world as world + + +import supybot.log as log + + +class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler): + """This plugin can set regexp triggers to activate the bot. + Use 'add' command to add regexp trigger, 'remove' to remove.""" + threaded = True + def __init__(self, irc): + callbacks.Plugin.__init__(self, irc) + plugins.ChannelDBHandler.__init__(self) + + def makeDb(self, filename): + """Create the database and connect to it.""" + if os.path.exists(filename): + db = sqlite3.connect(filename) + db.text_factory = str + return db + db = sqlite3.connect(filename) + db.text_factory = str + cursor = db.cursor() + cursor.execute("""CREATE TABLE triggers ( + id INTEGER PRIMARY KEY, + regexp TEXT UNIQUE ON CONFLICT REPLACE, + added_by TEXT, + added_at TIMESTAMP, + usage_count INTEGER, + action TEXT, + locked BOOLEAN + )""") + db.commit() + return db + + # override this because sqlite3 doesn't have autocommit + # use isolation_level instead. + def getDb(self, channel): + """Use this to get a database for a specific channel.""" + currentThread = threading.currentThread() + if channel not in self.dbCache and currentThread == world.mainThread: + self.dbCache[channel] = self.makeDb(self.makeFilename(channel)) + if currentThread != world.mainThread: + db = self.makeDb(self.makeFilename(channel)) + else: + db = self.dbCache[channel] + db.isolation_level = None + return db + + def _updateRank(self, channel, regexp): + if self.registryValue('keepRankInfo', channel): + db = self.getDb(channel) + cursor = db.cursor() + cursor.execute("""SELECT usage_count + FROM triggers + WHERE regexp=?""", (regexp,)) + old_count = cursor.fetchall()[0][0] + cursor.execute("UPDATE triggers SET usage_count=? WHERE regexp=?", (old_count + 1, regexp,)) + db.commit() + + def _runCommandFunction(self, irc, msg, command): + """Run a command from message, as if command was sent over IRC.""" + tokens = callbacks.tokenize(command) + try: + self.Proxy(irc.irc, msg, tokens) + except Exception, e: + log.exception('Uncaught exception in function called by MessageParser:') + + def _checkManageCapabilities(self, irc, msg, channel): + """Check if the user has any of the required capabilities to manage + the regexp database.""" + capabilities = self.registryValue('requireManageCapability') + if capabilities: + for capability in re.split(r'\s*;\s*', capabilities): + if capability.startswith('channel,'): + capability = ircdb.makeChannelCapability(channel, capability[8:]) + if capability and ircdb.checkCapability(msg.prefix, capability): + #print "has capability:", capability + return True + return False + else: + return True + + def doPrivmsg(self, irc, msg): + channel = msg.args[0] + if not irc.isChannel(channel): + return + if self.registryValue('enable', channel): + if callbacks.addressed(irc.nick, msg): #message is direct command + return + actions = [] + db = self.getDb(channel) + cursor = db.cursor() + cursor.execute("SELECT regexp, action FROM triggers") + results = cursor.fetchall() + if len(results) == 0: + return + for (regexp, action) in results: + for match in re.finditer(regexp, msg.args[1]): + if match is not None: + thisaction = action + self._updateRank(channel, regexp) + for (i, j) in enumerate(match.groups()): + thisaction = re.sub(r'\$' + str(i+1), match.group(i+1), thisaction) + actions.append(thisaction) + + for action in actions: + self._runCommandFunction(irc, msg, action) + + def add(self, irc, msg, args, channel, regexp, action): + """[] + + Associates with . is only + necessary if the message isn't sent on the channel + itself. Action is echoed upon regexp match, with variables $1, $2, + etc. being interpolated from the regexp match groups.""" + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) + db = self.getDb(channel) + cursor = db.cursor() + cursor.execute("SELECT id, usage_count, locked FROM triggers WHERE regexp=?", (regexp,)) + results = cursor.fetchall() + if len(results) != 0: + (id, usage_count, locked) = map(int, results[0]) + else: + locked = 0 + usage_count = 0 + if not locked: + try: + re.compile(regexp) + except Exception, e: + irc.error('Invalid python regexp: %s' % (e,)) + return + if ircdb.users.hasUser(msg.prefix): + name = ircdb.users.getUser(msg.prefix).name + else: + name = msg.nick + cursor.execute("""INSERT INTO triggers VALUES + (NULL, ?, ?, ?, ?, ?, ?)""", + (regexp, name, int(time.time()), usage_count, action, locked,)) + db.commit() + irc.replySuccess() + else: + irc.error('That trigger is locked.') + return + add = wrap(add, ['channel', 'something', 'something']) + + def remove(self, irc, msg, args, channel, optlist, regexp): + """[] [--id] ] + + Removes the trigger for from the triggers database. + is only necessary if + the message isn't sent in the channel itself. + If option --id specified, will retrieve by regexp id, not content. + """ + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) + db = self.getDb(channel) + cursor = db.cursor() + target = 'regexp' + for (option, arg) in optlist: + if option == 'id': + target = 'id' + sql = "SELECT id, locked FROM triggers WHERE %s=?" % (target,) + cursor.execute(sql, (regexp,)) + results = cursor.fetchall() + if len(results) != 0: + (id, locked) = map(int, results[0]) + else: + irc.error('There is no such regexp trigger.') + return + + if locked: + irc.error('This regexp trigger is locked.') + return + + cursor.execute("""DELETE FROM triggers WHERE id=?""", (id,)) + db.commit() + irc.replySuccess() + remove = wrap(remove, ['channel', + getopts({'id': '',}), + 'something']) + + def lock(self, irc, msg, args, channel, regexp): + """[] + + Locks the so that it cannot be + removed or overwritten to. is only necessary if the message isn't + sent in the channel itself. + """ + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) + db = self.getDb(channel) + cursor = db.cursor() + cursor.execute("SELECT id FROM triggers WHERE regexp=?", (regexp,)) + results = cursor.fetchall() + if len(results) == 0: + irc.error('There is no such regexp trigger.') + return + cursor.execute("UPDATE triggers SET locked=1 WHERE regexp=?", (regexp,)) + db.commit() + irc.replySuccess() + lock = wrap(lock, ['channel', 'text']) + + def unlock(self, irc, msg, args, channel, regexp): + """[] + + Unlocks the entry associated with so that it can be + removed or overwritten. is only necessary if the message isn't + sent in the channel itself. + """ + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) + db = self.getDb(channel) + cursor = db.cursor() + cursor.execute("SELECT id FROM triggers WHERE regexp=?", (regexp,)) + results = cursor.fetchall() + if len(results) == 0: + irc.error('There is no such regexp trigger.') + return + cursor.execute("UPDATE triggers SET locked=0 WHERE regexp=?", (regexp,)) + db.commit() + irc.replySuccess() + unlock = wrap(unlock, ['channel', 'text']) + + def show(self, irc, msg, args, channel, optlist, regexp): + """[] [--id] + + Looks up the value of in the triggers database. + is only necessary if the message isn't sent in the channel + itself. + If option --id specified, will retrieve by regexp id, not content. + """ + db = self.getDb(channel) + cursor = db.cursor() + target = 'regexp' + for (option, arg) in optlist: + if option == 'id': + target = 'id' + sql = "SELECT regexp, action FROM triggers WHERE %s=?" % (target,) + cursor.execute(sql, (regexp,)) + results = cursor.fetchall() + if len(results) != 0: + (regexp, action) = results[0] + else: + irc.error('There is no such regexp trigger.') + return + + irc.reply("The action for regexp trigger \"%s\" is \"%s\"" % (regexp, action)) + show = wrap(show, ['channel', + getopts({'id': '',}), + 'something']) + + def info(self, irc, msg, args, channel, optlist, regexp): + """[] [--id] + + Display information about in the triggers database. + is only necessary if the message isn't sent in the channel + itself. + If option --id specified, will retrieve by regexp id, not content. + """ + db = self.getDb(channel) + cursor = db.cursor() + target = 'regexp' + for (option, arg) in optlist: + if option == 'id': + target = 'id' + sql = "SELECT * FROM triggers WHERE %s=?" % (target,) + cursor.execute(sql, (regexp,)) + results = cursor.fetchall() + if len(results) != 0: + (id, regexp, added_by, added_at, usage_count, + action, locked) = results[0] + else: + irc.error('There is no such regexp trigger.') + return + + irc.reply("The regexp id is %d, regexp is \"%s\", and action is" + " \"%s\". It was added by user %s on %s, has been " + "triggered %d times, and is %s." % (id, + regexp, + action, + added_by, + time.strftime(conf.supybot.reply.format.time(), + time.localtime(int(added_at))), + usage_count, + locked and "locked" or "not locked",)) + info = wrap(info, ['channel', + getopts({'id': '',}), + 'something']) + + def list(self, irc, msg, args, channel): + """[] + + Lists regexps present in the triggers database. + is only necessary if the message isn't sent in the channel + itself. Regexp ID listed in paretheses. + """ + db = self.getDb(channel) + cursor = db.cursor() + cursor.execute("SELECT regexp, id FROM triggers") + results = cursor.fetchall() + if len(results) != 0: + regexps = results + else: + irc.reply('There are no regexp triggers in the database.') + return + + s = [ "\"%s\" (%d)" % (regexp[0], regexp[1]) for regexp in regexps ] + separator = self.registryValue('listSeparator', channel) + irc.reply(separator.join(s)) + list = wrap(list, ['channel']) + + def rank(self, irc, msg, args, channel): + """[] + + Returns a list of top-ranked regexps, sorted by usage count + (rank). The number of regexps returned is set by the + rankListLength registry value. is only necessary if the + message isn't sent in the channel itself. + """ + numregexps = self.registryValue('rankListLength', channel) + db = self.getDb(channel) + cursor = db.cursor() + cursor.execute("""SELECT regexp, usage_count + FROM triggers + ORDER BY usage_count DESC + LIMIT ?""", (numregexps,)) + regexps = cursor.fetchall() + if len(regexps) == 0: + irc.reply('There are no regexp triggers in the database.') + return + s = [ "#%d \"%s\" (%d)" % (i+1, regexp[0], regexp[1]) for i, regexp in enumerate(regexps) ] + irc.reply(", ".join(s)) + rank = wrap(rank, ['channel']) + + def vacuum(self, irc, msg, args, channel): + """[] + + Vacuums the database for . + See SQLite vacuum doc here: http://www.sqlite.org/lang_vacuum.html + is only necessary if the message isn't sent in + the channel itself. + First check if user has the required capability specified in plugin + config requireVacuumCapability. + """ + capability = self.registryValue('requireVacuumCapability') + if capability: + if not ircdb.checkCapability(msg.prefix, capability): + irc.errorNoCapability(capability, Raise=True) + db = self.getDb(channel) + cursor = db.cursor() + cursor.execute("""VACUUM""") + db.commit() + irc.replySuccess() + vacuum = wrap(vacuum, ['channel']) + +Class = MessageParser + + +# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/plugins/MessageParser/test.py b/plugins/MessageParser/test.py new file mode 100644 index 000000000..870f83ebd --- /dev/null +++ b/plugins/MessageParser/test.py @@ -0,0 +1,174 @@ +### +# Copyright (c) 2010, Daniel Folkinshteyn +# 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 * + +try: + import sqlite3 +except ImportError: + from pysqlite2 import dbapi2 as sqlite3 # for python2.4 + + +class MessageParserTestCase(ChannelPluginTestCase): + plugins = ('MessageParser','Utilities','User') + #utilities for the 'echo' + #user for register for testVacuum + + def testAdd(self): + self.assertError('messageparser add') #no args + self.assertError('messageparser add "stuff"') #no action arg + self.assertNotError('messageparser add "stuff" "echo i saw some stuff"') + self.assertRegexp('messageparser show "stuff"', '.*i saw some stuff.*') + + self.assertError('messageparser add "[a" "echo stuff"') #invalid regexp + self.assertError('messageparser add "(a" "echo stuff"') #invalid regexp + self.assertNotError('messageparser add "stuff" "echo i saw no stuff"') #overwrite existing regexp + self.assertRegexp('messageparser show "stuff"', '.*i saw no stuff.*') + + + try: + world.testing = False + origuser = self.prefix + self.prefix = 'stuff!stuff@stuff' + self.assertNotError('register nottester stuff', private=True) + + self.assertError('messageparser add "aoeu" "echo vowels are nice"') + origconf = conf.supybot.plugins.MessageParser.requireManageCapability() + conf.supybot.plugins.MessageParser.requireManageCapability.setValue('') + self.assertNotError('messageparser add "aoeu" "echo vowels are nice"') + finally: + world.testing = True + self.prefix = origuser + conf.supybot.plugins.MessageParser.requireManageCapability.setValue(origconf) + + def testShow(self): + self.assertNotError('messageparser add "stuff" "echo i saw some stuff"') + self.assertRegexp('messageparser show "nostuff"', 'there is no such regexp trigger') + self.assertRegexp('messageparser show "stuff"', '.*i saw some stuff.*') + self.assertRegexp('messageparser show --id 1', '.*i saw some stuff.*') + + def testInfo(self): + self.assertNotError('messageparser add "stuff" "echo i saw some stuff"') + self.assertRegexp('messageparser info "nostuff"', 'there is no such regexp trigger') + self.assertRegexp('messageparser info "stuff"', '.*i saw some stuff.*') + self.assertRegexp('messageparser info --id 1', '.*i saw some stuff.*') + self.assertRegexp('messageparser info "stuff"', 'has been triggered 0 times') + self.feedMsg('this message has some stuff in it') + self.getMsg(' ') + self.assertRegexp('messageparser info "stuff"', 'has been triggered 1 times') + + def testTrigger(self): + self.assertNotError('messageparser add "stuff" "echo i saw some stuff"') + self.feedMsg('this message has some stuff in it') + m = self.getMsg(' ') + self.failUnless(str(m).startswith('PRIVMSG #test :i saw some stuff')) + + def testLock(self): + self.assertNotError('messageparser add "stuff" "echo i saw some stuff"') + self.assertNotError('messageparser lock "stuff"') + self.assertError('messageparser add "stuff" "echo some other stuff"') + self.assertError('messageparser remove "stuff"') + self.assertRegexp('messageparser info "stuff"', 'is locked') + + def testUnlock(self): + self.assertNotError('messageparser add "stuff" "echo i saw some stuff"') + self.assertNotError('messageparser lock "stuff"') + self.assertError('messageparser remove "stuff"') + self.assertNotError('messageparser unlock "stuff"') + self.assertRegexp('messageparser info "stuff"', 'is not locked') + self.assertNotError('messageparser remove "stuff"') + + def testRank(self): + self.assertRegexp('messageparser rank', + 'There are no regexp triggers in the database\.') + self.assertNotError('messageparser add "stuff" "echo i saw some stuff"') + self.assertRegexp('messageparser rank', '#1 "stuff" \(0\)') + self.assertNotError('messageparser add "aoeu" "echo vowels are nice!"') + self.assertRegexp('messageparser rank', '#1 "stuff" \(0\), #2 "aoeu" \(0\)') + self.feedMsg('instead of asdf, dvorak has aoeu') + self.getMsg(' ') + self.assertRegexp('messageparser rank', '#1 "aoeu" \(1\), #2 "stuff" \(0\)') + + def testList(self): + self.assertRegexp('messageparser list', + 'There are no regexp triggers in the database\.') + self.assertNotError('messageparser add "stuff" "echo i saw some stuff"') + self.assertRegexp('messageparser list', '"stuff" \(1\)') + self.assertNotError('messageparser add "aoeu" "echo vowels are nice!"') + self.assertRegexp('messageparser list', '"stuff" \(1\), "aoeu" \(2\)') + + def testRemove(self): + self.assertError('messageparser remove "stuff"') + self.assertNotError('messageparser add "stuff" "echo i saw some stuff"') + self.assertNotError('messageparser lock "stuff"') + self.assertError('messageparser remove "stuff"') + self.assertNotError('messageparser unlock "stuff"') + self.assertNotError('messageparser remove "stuff"') + self.assertNotError('messageparser add "stuff" "echo i saw some stuff"') + self.assertNotError('messageparser remove --id 1') + + def testVacuum(self): + self.assertNotError('messageparser add "stuff" "echo i saw some stuff"') + self.assertNotError('messageparser remove "stuff"') + self.assertNotError('messageparser vacuum') + # disable world.testing since we want new users to not + # magically be endowed with the admin capability + try: + world.testing = False + original = self.prefix + self.prefix = 'stuff!stuff@stuff' + self.assertNotError('register nottester stuff', private=True) + self.assertError('messageparser vacuum') + + orig = conf.supybot.plugins.MessageParser.requireVacuumCapability() + conf.supybot.plugins.MessageParser.requireVacuumCapability.setValue('') + self.assertNotError('messageparser vacuum') + finally: + world.testing = True + self.prefix = original + conf.supybot.plugins.MessageParser.requireVacuumCapability.setValue(orig) + + def testKeepRankInfo(self): + orig = conf.supybot.plugins.MessageParser.keepRankInfo() + + try: + conf.supybot.plugins.MessageParser.keepRankInfo.setValue(False) + self.assertNotError('messageparser add "stuff" "echo i saw some stuff"') + self.feedMsg('instead of asdf, dvorak has aoeu') + self.getMsg(' ') + self.assertRegexp('messageparser info "stuff"', 'has been triggered 0 times') + finally: + conf.supybot.plugins.MessageParser.keepRankInfo.setValue(orig) + + self.feedMsg('this message has some stuff in it') + self.getMsg(' ') + self.assertRegexp('messageparser info "stuff"', 'has been triggered 1 times') + +# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: From 6381266cfd03005a8eb2371b76d5cce6d7cfd8a4 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Thu, 3 Jun 2010 12:52:48 -0400 Subject: [PATCH 05/85] Utilities: add 'sample' command, a basic interface to random.sample() Also add tests for it. --- plugins/Utilities/locale/fr.po | 15 +++++++++++++-- plugins/Utilities/messages.pot | 13 +++++++++++-- plugins/Utilities/plugin.py | 15 ++++++++++++++- plugins/Utilities/test.py | 6 ++++++ 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/plugins/Utilities/locale/fr.po b/plugins/Utilities/locale/fr.po index 932921d94..e488e35ca 100644 --- a/plugins/Utilities/locale/fr.po +++ b/plugins/Utilities/locale/fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Supybot-fr\n" -"POT-Creation-Date: 2010-10-20 09:39+CEST\n" +"POT-Creation-Date: 2011-02-20 11:27+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Supybot-fr \n" @@ -71,7 +71,7 @@ msgstr "" msgid "" " [ ...]\n" "\n" -" Shuffles the arguments given it.\n" +" Shuffles the arguments given.\n" " " msgstr "" " [ ...]\n" @@ -80,6 +80,17 @@ msgstr "" #: plugin.py:109 msgid "" +" [ ...]\n" +"\n" +" Randomly chooses items out of the arguments given.\n" +" " +msgstr "" +" [ ...]\n" +"\n" +"Choisi de façon aléatoire un certain d's." + +#: plugin.py:122 +msgid "" " \n" "\n" " Tokenizes and calls with the resulting arguments.\n" diff --git a/plugins/Utilities/messages.pot b/plugins/Utilities/messages.pot index c7187afce..58272ab07 100644 --- a/plugins/Utilities/messages.pot +++ b/plugins/Utilities/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-20 09:39+CEST\n" +"POT-Creation-Date: 2011-02-20 11:27+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -66,13 +66,22 @@ msgstr "" msgid "" " [ ...]\n" "\n" -" Shuffles the arguments given it.\n" +" Shuffles the arguments given.\n" " " msgstr "" #: plugin.py:109 #, docstring msgid "" +" [ ...]\n" +"\n" +" Randomly chooses items out of the arguments given.\n" +" " +msgstr "" + +#: plugin.py:122 +#, docstring +msgid "" " \n" "\n" " Tokenizes and calls with the resulting arguments.\n" diff --git a/plugins/Utilities/plugin.py b/plugins/Utilities/plugin.py index 5f7fa72eb..767b51a88 100644 --- a/plugins/Utilities/plugin.py +++ b/plugins/Utilities/plugin.py @@ -98,12 +98,25 @@ class Utilities(callbacks.Plugin): def shuffle(self, irc, msg, args, things): """ [ ...] - Shuffles the arguments given it. + Shuffles the arguments given. """ random.shuffle(things) irc.reply(' '.join(things)) shuffle = wrap(shuffle, [many('anything')]) + @internationalizeDocstring + def sample(self, irc, msg, args, num, things): + """ [ ...] + + Randomly chooses items out of the arguments given. + """ + try: + samp = random.sample(things, num) + irc.reply(' '.join(samp)) + except ValueError, e: + irc.error('%s' % (e,)) + sample = wrap(sample, ['positiveInt', many('anything')]) + @internationalizeDocstring def apply(self, irc, msg, args, command, rest): """ diff --git a/plugins/Utilities/test.py b/plugins/Utilities/test.py index 7241b2d61..851a6cae0 100644 --- a/plugins/Utilities/test.py +++ b/plugins/Utilities/test.py @@ -59,4 +59,10 @@ class UtilitiesTestCase(PluginTestCase): def testShuffle(self): self.assertResponse('shuffle a', 'a') + def testSample(self): + self.assertResponse('sample 1 a', 'a') + self.assertError('sample moo') + self.assertError('sample 5 moo') + self.assertRegexp('sample 2 a b c', '^[a-c] [a-c]$') + # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: From 957998f4835abf7d138c33f4c3f049e0afcadca2 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Thu, 3 Jun 2010 16:08:25 -0400 Subject: [PATCH 06/85] Utilities: add countargs function, returns number of arguments supplied. also add tests for it. --- plugins/Utilities/locale/fr.po | 13 ++++++++++++- plugins/Utilities/messages.pot | 11 ++++++++++- plugins/Utilities/plugin.py | 9 +++++++++ plugins/Utilities/test.py | 5 +++++ 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/plugins/Utilities/locale/fr.po b/plugins/Utilities/locale/fr.po index e488e35ca..49417bb57 100644 --- a/plugins/Utilities/locale/fr.po +++ b/plugins/Utilities/locale/fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Supybot-fr\n" -"POT-Creation-Date: 2011-02-20 11:27+CET\n" +"POT-Creation-Date: 2011-02-20 11:29+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Supybot-fr \n" @@ -91,6 +91,17 @@ msgstr "" #: plugin.py:122 msgid "" +" [ ...]\n" +"\n" +" Counts the arguments given.\n" +" " +msgstr "" +" [ ...]\n" +"\n" +"Retorune le nombre d's donnés." + +#: plugin.py:131 +msgid "" " \n" "\n" " Tokenizes and calls with the resulting arguments.\n" diff --git a/plugins/Utilities/messages.pot b/plugins/Utilities/messages.pot index 58272ab07..f31c687e6 100644 --- a/plugins/Utilities/messages.pot +++ b/plugins/Utilities/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2011-02-20 11:27+CET\n" +"POT-Creation-Date: 2011-02-20 11:29+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -82,6 +82,15 @@ msgstr "" #: plugin.py:122 #, docstring msgid "" +" [ ...]\n" +"\n" +" Counts the arguments given.\n" +" " +msgstr "" + +#: plugin.py:131 +#, docstring +msgid "" " \n" "\n" " Tokenizes and calls with the resulting arguments.\n" diff --git a/plugins/Utilities/plugin.py b/plugins/Utilities/plugin.py index 767b51a88..33b8882ee 100644 --- a/plugins/Utilities/plugin.py +++ b/plugins/Utilities/plugin.py @@ -117,6 +117,15 @@ class Utilities(callbacks.Plugin): irc.error('%s' % (e,)) sample = wrap(sample, ['positiveInt', many('anything')]) + @internationalizeDocstring + def countargs(self, irc, msg, args, things): + """ [ ...] + + Counts the arguments given. + """ + irc.reply(len(things)) + countargs = wrap(countargs, [any('anything')]) + @internationalizeDocstring def apply(self, irc, msg, args, command, rest): """ diff --git a/plugins/Utilities/test.py b/plugins/Utilities/test.py index 851a6cae0..753b010a4 100644 --- a/plugins/Utilities/test.py +++ b/plugins/Utilities/test.py @@ -65,4 +65,9 @@ class UtilitiesTestCase(PluginTestCase): self.assertError('sample 5 moo') self.assertRegexp('sample 2 a b c', '^[a-c] [a-c]$') + def testCountargs(self): + self.assertResponse('countargs a b c', '3') + self.assertResponse('countargs a "b c"', '2') + self.assertResponse('countargs', '0') + # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: From d2ac0e742a3861cfbf29038d9d5e18c170f751e7 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Fri, 5 Mar 2010 13:03:08 -0500 Subject: [PATCH 07/85] implement factoid usage counter --- plugins/Factoids/config.py | 7 ++++- plugins/Factoids/plugin.py | 61 +++++++++++++++++++++++++++++++------- 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/plugins/Factoids/config.py b/plugins/Factoids/config.py index 4f167865f..bb16421de 100644 --- a/plugins/Factoids/config.py +++ b/plugins/Factoids/config.py @@ -65,5 +65,10 @@ conf.registerChannelValue(Factoids, 'format', the response given when a factoid's value is requested. All the standard substitutes apply, in addition to "$key" for the factoid's key and "$value" for the factoid's value."""))) - +conf.registerChannelValue(Factoids, 'keepRankInfo', + registry.Boolean(True, """Determines whether we keep updating the usage + count for each factoid, for popularity ranking.""")) +conf.registerChannelValue(Factoids, 'rankListLength', + registry.Integer(20, """Determines the number of factoid keys returned + by the factrank command.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index 7a2d8c21f..9dd345787 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -98,6 +98,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): key_id INTEGER, added_by TEXT, added_at TIMESTAMP, + usage_count INTEGER, fact TEXT )""") cursor.execute("""CREATE TRIGGER remove_factoids @@ -143,8 +144,8 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): else: name = msg.nick cursor.execute("""INSERT INTO factoids VALUES - (NULL, %s, %s, %s, %s)""", - id, name, int(time.time()), factoid) + (NULL, %s, %s, %s, %s, %s)""", + id, name, int(time.time()), 0, factoid) db.commit() irc.replySuccess() else: @@ -163,18 +164,33 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): def _lookupFactoid(self, channel, key): db = self.getDb(channel) cursor = db.cursor() - cursor.execute("""SELECT factoids.fact FROM factoids, keys + cursor.execute("""SELECT factoids.fact, factoids.id FROM factoids, keys WHERE keys.key LIKE %s AND factoids.key_id=keys.id ORDER BY factoids.id LIMIT 20""", key) - return [t[0] for t in cursor.fetchall()] - - def _replyFactoids(self, irc, msg, key, factoids, + return cursor.fetchall() + #return [t[0] for t in cursor.fetchall()] + + def _updateRank(self, channel, factoids): + if self.registryValue('keepRankInfo', channel): + db = self.getDb(channel) + cursor = db.cursor() + for (fact,id) in factoids: + cursor.execute("""SELECT factoids.usage_count + FROM factoids + WHERE factoids.id=%s""", id) + old_count = cursor.fetchall()[0][0] + cursor.execute("UPDATE factoids SET usage_count=%s WHERE id=%s", old_count + 1, id) + db.commit() + + def _replyFactoids(self, irc, msg, key, channel, factoids, number=0, error=True): if factoids: + print factoids if number: try: - irc.reply(factoids[number-1]) + irc.reply(factoids[number-1][0]) + self._updateRank(channel, [factoids[number-1]]) except IndexError: irc.error(_('That\'s not a valid number for that key.')) return @@ -186,15 +202,16 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): return ircutils.standardSubstitute(irc, msg, formatter, env) if len(factoids) == 1: - irc.reply(prefixer(factoids[0])) + irc.reply(prefixer(factoids[0][0])) else: factoidsS = [] counter = 1 for factoid in factoids: - factoidsS.append(format('(#%i) %s', counter, factoid)) + factoidsS.append(format('(#%i) %s', counter, factoid[0])) counter += 1 irc.replies(factoidsS, prefixer=prefixer, joiner=', or ', onlyPrefixFirst=True) + self._updateRank(channel, factoids) elif error: irc.error(_('No factoid matches that key.')) @@ -204,7 +221,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): if self.registryValue('replyWhenInvalidCommand', channel): key = ' '.join(tokens) factoids = self._lookupFactoid(channel, key) - self._replyFactoids(irc, msg, key, factoids, error=False) + self._replyFactoids(irc, msg, key, channel, factoids, error=False) @internationalizeDocstring def whatis(self, irc, msg, args, channel, words): @@ -222,9 +239,31 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): irc.errorInvalid(_('key id')) key = ' '.join(words) factoids = self._lookupFactoid(channel, key) - self._replyFactoids(irc, msg, key, factoids, number) + self._replyFactoids(irc, msg, key, channel, factoids, number) whatis = wrap(whatis, ['channel', many('something')]) + @internationalizeDocstring + def factrank(self, irc, msg, args, channel): + """[] + + Returns a list of top-ranked factoid keys, sorted by usage count + (rank). The number of factoid keys returned is set by the + rankListLength registry value. is only necessary if the + message isn't sent in the channel itself. + """ + numfacts = self.registryValue('rankListLength', channel) + db = self.getDb(channel) + cursor = db.cursor() + cursor.execute("""SELECT keys.key, factoids.usage_count + FROM keys, factoids + WHERE factoids.key_id=keys.id + ORDER BY factoids.usage_count DESC + LIMIT %s""", numfacts) + factkeys = cursor.fetchall() + s = [ "#%d %s (%d)" % (i, key[0], key[1]) for i, key in enumerate(factkeys) ] + irc.reply(", ".join(s)) + factrank = wrap(factrank, ['channel']) + @internationalizeDocstring def lock(self, irc, msg, args, channel, key): """[] From 7351ec1702d50c5d44e9bc93ad28f956715dbb8e Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Fri, 5 Mar 2010 14:51:25 -0500 Subject: [PATCH 08/85] sort keys in factoid search output by alphabetically by key name. --- plugins/Factoids/plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index 9dd345787..e74564743 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -464,6 +464,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): cursor = db.cursor() sql = """SELECT keys.key FROM %s WHERE %s""" % \ (', '.join(tables), ' AND '.join(criteria)) + sql = sql + " ORDER BY keys.key" sql = sql.replace('TARGET', target) cursor.execute(sql, formats) if cursor.rowcount == 0: From 34f8557cfd77cb62bd8e79de4d3267c8640c0dd6 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Mon, 8 Mar 2010 14:47:27 -0500 Subject: [PATCH 09/85] remove rogue test-print --- plugins/Factoids/plugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index e74564743..9e0bb623c 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -186,7 +186,6 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): def _replyFactoids(self, irc, msg, key, channel, factoids, number=0, error=True): if factoids: - print factoids if number: try: irc.reply(factoids[number-1][0]) From c0ebdddb472147eaae7bdf54f7877381015d1df8 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Mon, 15 Mar 2010 23:12:35 -0400 Subject: [PATCH 10/85] start factoid rankings from 1 not from 0 --- plugins/Factoids/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index 9e0bb623c..fd56884e0 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -259,7 +259,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): ORDER BY factoids.usage_count DESC LIMIT %s""", numfacts) factkeys = cursor.fetchall() - s = [ "#%d %s (%d)" % (i, key[0], key[1]) for i, key in enumerate(factkeys) ] + s = [ "#%d %s (%d)" % (i+1, key[0], key[1]) for i, key in enumerate(factkeys) ] irc.reply(", ".join(s)) factrank = wrap(factrank, ['channel']) From f71464adb3906ce833437098c53ad2f2f74ba2ff Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Sun, 21 Mar 2010 02:25:11 -0400 Subject: [PATCH 11/85] This merges an old branch i had for sqlite3 factoids+moobotfactoids. fix up moobotfactoids+factoids to use the sqlite text_factory=str, also fixed up a test for factoid search, since it now sorts keys alphabetically. --- plugins/Factoids/plugin.py | 135 ++++++++++++++++++++----------- plugins/Factoids/test.py | 4 +- plugins/MoobotFactoids/plugin.py | 115 +++++++++++++------------- plugins/__init__.py | 83 ++++++++++--------- 4 files changed, 190 insertions(+), 147 deletions(-) diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index fd56884e0..2e4c7cd50 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -42,12 +42,21 @@ import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Factoids') +#try: + #import sqlite3 as sqlite +#except ImportError: + #raise callbacks.Error, 'You need to have PySQLite installed to use this ' \ + #'plugin. Download it at ' \ + #'' + try: - import sqlite + import sqlite3 except ImportError: - raise callbacks.Error, 'You need to have PySQLite installed to use this ' \ - 'plugin. Download it at ' \ - '' + from pysqlite2 import dbapi2 as sqlite3 # for python2.4 + +# these are needed cuz we are overriding getdb +import threading +import supybot.world as world def getFactoid(irc, msg, args, state): assert not state.channel @@ -85,8 +94,11 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): def makeDb(self, filename): if os.path.exists(filename): - return sqlite.connect(filename) - db = sqlite.connect(filename) + db = sqlite3.connect(filename) + db.text_factory = str + return db + db = sqlite3.connect(filename) + db.text_factory = str cursor = db.cursor() cursor.execute("""CREATE TABLE keys ( id INTEGER PRIMARY KEY, @@ -110,6 +122,20 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): db.commit() return db + # override this because sqlite3 doesn't have autocommit + # use isolation_level instead. + def getDb(self, channel): + """Use this to get a database for a specific channel.""" + currentThread = threading.currentThread() + if channel not in self.dbCache and currentThread == world.mainThread: + self.dbCache[channel] = self.makeDb(self.makeFilename(channel)) + if currentThread != world.mainThread: + db = self.makeDb(self.makeFilename(channel)) + else: + db = self.dbCache[channel] + db.isolation_level = None + return db + def getCommandHelp(self, command, simpleSyntax=None): method = self.getCommandMethod(command) if method.im_func.func_name == 'learn': @@ -131,12 +157,14 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): def learn(self, irc, msg, args, channel, key, factoid): db = self.getDb(channel) cursor = db.cursor() - cursor.execute("SELECT id, locked FROM keys WHERE key LIKE %s", key) - if cursor.rowcount == 0: - cursor.execute("""INSERT INTO keys VALUES (NULL, %s, 0)""", key) + cursor.execute("SELECT id, locked FROM keys WHERE key LIKE ?", (key,)) + results = cursor.fetchall() + if len(results) == 0: + cursor.execute("""INSERT INTO keys VALUES (NULL, ?, 0)""", (key,)) db.commit() - cursor.execute("SELECT id, locked FROM keys WHERE key LIKE %s",key) - (id, locked) = map(int, cursor.fetchone()) + cursor.execute("SELECT id, locked FROM keys WHERE key LIKE ?", (key,)) + results = cursor.fetchall() + (id, locked) = map(int, results[0]) capability = ircdb.makeChannelCapability(channel, 'factoids') if not locked: if ircdb.users.hasUser(msg.prefix): @@ -144,8 +172,8 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): else: name = msg.nick cursor.execute("""INSERT INTO factoids VALUES - (NULL, %s, %s, %s, %s, %s)""", - id, name, int(time.time()), 0, factoid) + (NULL, ?, ?, ?, ?, ?)""", + (id, name, int(time.time()), 0, factoid)) db.commit() irc.replySuccess() else: @@ -165,9 +193,9 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): db = self.getDb(channel) cursor = db.cursor() cursor.execute("""SELECT factoids.fact, factoids.id FROM factoids, keys - WHERE keys.key LIKE %s AND factoids.key_id=keys.id + WHERE keys.key LIKE ? AND factoids.key_id=keys.id ORDER BY factoids.id - LIMIT 20""", key) + LIMIT 20""", (key,)) return cursor.fetchall() #return [t[0] for t in cursor.fetchall()] @@ -178,9 +206,9 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): for (fact,id) in factoids: cursor.execute("""SELECT factoids.usage_count FROM factoids - WHERE factoids.id=%s""", id) + WHERE factoids.id=?""", (id,)) old_count = cursor.fetchall()[0][0] - cursor.execute("UPDATE factoids SET usage_count=%s WHERE id=%s", old_count + 1, id) + cursor.execute("UPDATE factoids SET usage_count=? WHERE id=?", (old_count + 1, id,)) db.commit() def _replyFactoids(self, irc, msg, key, channel, factoids, @@ -257,7 +285,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): FROM keys, factoids WHERE factoids.key_id=keys.id ORDER BY factoids.usage_count DESC - LIMIT %s""", numfacts) + LIMIT ?""", (numfacts,)) factkeys = cursor.fetchall() s = [ "#%d %s (%d)" % (i+1, key[0], key[1]) for i, key in enumerate(factkeys) ] irc.reply(", ".join(s)) @@ -273,7 +301,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): """ db = self.getDb(channel) cursor = db.cursor() - cursor.execute("UPDATE keys SET locked=1 WHERE key LIKE %s", key) + cursor.execute("UPDATE keys SET locked=1 WHERE key LIKE ?", (key,)) db.commit() irc.replySuccess() lock = wrap(lock, ['channel', 'text']) @@ -288,7 +316,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): """ db = self.getDb(channel) cursor = db.cursor() - cursor.execute("UPDATE keys SET locked=0 WHERE key LIKE %s", key) + cursor.execute("UPDATE keys SET locked=0 WHERE key LIKE ?", (key,)) db.commit() irc.replySuccess() unlock = wrap(unlock, ['channel', 'text']) @@ -317,32 +345,33 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): cursor = db.cursor() cursor.execute("""SELECT keys.id, factoids.id FROM keys, factoids - WHERE key LIKE %s AND - factoids.key_id=keys.id""", key) - if cursor.rowcount == 0: + WHERE key LIKE ? AND + factoids.key_id=keys.id""", (key,)) + results = cursor.fetchall() + if len(results) == 0: irc.error(_('There is no such factoid.')) - elif cursor.rowcount == 1 or number is True: - (id, foo) = cursor.fetchone() - cursor.execute("""DELETE FROM factoids WHERE key_id=%s""", id) - cursor.execute("""DELETE FROM keys WHERE key LIKE %s""", key) + elif len(results) == 1 or number is True: + (id, _) = results[0] + cursor.execute("""DELETE FROM factoids WHERE key_id=?""", (id,)) + cursor.execute("""DELETE FROM keys WHERE key LIKE ?""", (key,)) db.commit() irc.replySuccess() else: if number is not None: - results = cursor.fetchall() + #results = cursor.fetchall() try: (foo, id) = results[number-1] except IndexError: irc.error(_('Invalid factoid number.')) return - cursor.execute("DELETE FROM factoids WHERE id=%s", id) + cursor.execute("DELETE FROM factoids WHERE id=?", (id,)) db.commit() irc.replySuccess() else: irc.error(_('%s factoids have that key. ' 'Please specify which one to remove, ' 'or use * to designate all of them.') % - cursor.rowcount) + len(results)) forget = wrap(forget, ['channel', many('something')]) @internationalizeDocstring @@ -357,10 +386,11 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): cursor.execute("""SELECT fact, key_id FROM factoids ORDER BY random() LIMIT 3""") - if cursor.rowcount != 0: + results = cursor.fetchall() + if len(results) != 0: L = [] - for (factoid, id) in cursor.fetchall(): - cursor.execute("""SELECT key FROM keys WHERE id=%s""", id) + for (factoid, id) in results: + cursor.execute("""SELECT key FROM keys WHERE id=?""", (id,)) (key,) = cursor.fetchone() L.append('"%s": %s' % (ircutils.bold(key), factoid)) irc.reply('; '.join(L)) @@ -378,14 +408,15 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): """ db = self.getDb(channel) cursor = db.cursor() - cursor.execute("SELECT id, locked FROM keys WHERE key LIKE %s", key) - if cursor.rowcount == 0: + cursor.execute("SELECT id, locked FROM keys WHERE key LIKE ?", (key,)) + results = cursor.fetchall() + if len(results) == 0: irc.error(_('No factoid matches that key.')) return - (id, locked) = map(int, cursor.fetchone()) + (id, locked) = map(int, results[0]) cursor.execute("""SELECT added_by, added_at FROM factoids - WHERE key_id=%s - ORDER BY id""", id) + WHERE key_id=? + ORDER BY id""", (id,)) factoids = cursor.fetchall() L = [] counter = 0 @@ -413,16 +444,17 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): cursor = db.cursor() cursor.execute("""SELECT factoids.id, factoids.fact FROM keys, factoids - WHERE keys.key LIKE %s AND - keys.id=factoids.key_id""", key) - if cursor.rowcount == 0: + WHERE keys.key LIKE ? AND + keys.id=factoids.key_id""", (key,)) + results = cursor.fetchall() + if len(results) == 0: irc.error(format(_('I couldn\'t find any key %q'), key)) return - elif cursor.rowcount < number: - irc.errorInvalid(_('key id')) - (id, fact) = cursor.fetchall()[number-1] + elif len(results) < number: + irc.errorInvalid('key id') + (id, fact) = results[number-1] newfact = replacer(fact) - cursor.execute("UPDATE factoids SET fact=%s WHERE id=%s", newfact, id) + cursor.execute("UPDATE factoids SET fact=? WHERE id=?", (newfact, id)) db.commit() irc.replySuccess() change = wrap(change, ['channel', 'something', @@ -458,7 +490,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): db.create_function(predicateName, 1, p) predicateName += 'p' for glob in globs: - criteria.append('TARGET LIKE %s') + criteria.append('TARGET LIKE ?') formats.append(glob.translate(self._sqlTrans)) cursor = db.cursor() sql = """SELECT keys.key FROM %s WHERE %s""" % \ @@ -474,8 +506,17 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): elif cursor.rowcount > 100: irc.reply(_('More than 100 keys matched that query; ' 'please narrow your query.')) + results = cursor.fetchall() + if len(results) == 0: + irc.reply(_('No keys matched that query.')) + elif len(results) == 1 and \ + self.registryValue('showFactoidIfOnlyOneMatch', channel): + self.whatis(irc, msg, [results[0][0]]) + elif len(results) > 100: + irc.reply(_('More than 100 keys matched that query; ' + 'please narrow your query.')) else: - keys = [repr(t[0]) for t in cursor.fetchall()] + keys = [repr(t[0]) for t in results] s = format('%L', keys) irc.reply(s) search = wrap(search, ['channel', diff --git a/plugins/Factoids/test.py b/plugins/Factoids/test.py index fb8fa7e35..30a8a9f06 100644 --- a/plugins/Factoids/test.py +++ b/plugins/Factoids/test.py @@ -99,8 +99,8 @@ if sqlite: self.assertRegexp('factoids search --regexp m/^j/ *ss*', 'jamessan') self.assertRegexp('factoids search --regexp /^j/', - 'jemfinch.*jamessan') - self.assertRegexp('factoids search j*', 'jemfinch.*jamessan') + 'jamessan.*jemfinch') + self.assertRegexp('factoids search j*', 'jamessan.*jemfinch') self.assertRegexp('factoids search *ke*', 'inkedmn.*strike|strike.*inkedmn') self.assertRegexp('factoids search ke', diff --git a/plugins/MoobotFactoids/plugin.py b/plugins/MoobotFactoids/plugin.py index 6576dadbd..1063c63dd 100644 --- a/plugins/MoobotFactoids/plugin.py +++ b/plugins/MoobotFactoids/plugin.py @@ -100,19 +100,21 @@ class SqliteMoobotDB(object): def _getDb(self, channel): try: - import sqlite + import sqlite3 except ImportError: - raise callbacks.Error, \ - 'You need to have PySQLite installed to use this ' \ - 'plugin. Download it at ' \ - '' + from pysqlite2 import dbapi2 as sqlite3 # for python2.4 + if channel in self.dbs: return self.dbs[channel] filename = plugins.makeChannelFilename(self.filename, channel) + if os.path.exists(filename): - self.dbs[channel] = sqlite.connect(filename) - return self.dbs[channel] - db = sqlite.connect(filename) + db = sqlite3.connect(filename) + db.text_factory = str + self.dbs[channel] = db + return db + db = sqlite3.connect(filename) + db.text_factory = str self.dbs[channel] = db cursor = db.cursor() cursor.execute("""CREATE TABLE factoids ( @@ -135,11 +137,12 @@ class SqliteMoobotDB(object): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT fact FROM factoids - WHERE key LIKE %s""", key) - if cursor.rowcount == 0: + WHERE key LIKE ?""", (key,)) + results = cursor.fetchall() + if len(results) == 0: return None else: - return cursor.fetchall()[0] + return results[0] def getFactinfo(self, channel, key): db = self._getDb(channel) @@ -149,63 +152,65 @@ class SqliteMoobotDB(object): last_requested_by, last_requested_at, requested_count, locked_by, locked_at FROM factoids - WHERE key LIKE %s""", key) - if cursor.rowcount == 0: + WHERE key LIKE ?""", (key,)) + results = cursor.fetchall() + if len(results) == 0: return None else: - return cursor.fetchone() + return results[0] def randomFactoid(self, channel): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT fact, key FROM factoids ORDER BY random() LIMIT 1""") - if cursor.rowcount == 0: + results = cursor.fetchall() + if len(results) == 0: return None else: - return cursor.fetchone() + return results[0] def addFactoid(self, channel, key, value, creator_id): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""INSERT INTO factoids VALUES - (%s, %s, %s, NULL, NULL, NULL, NULL, - NULL, NULL, %s, 0)""", - key, creator_id, int(time.time()), value) + (?, ?, ?, NULL, NULL, NULL, NULL, + NULL, NULL, ?, 0)""", + (key, creator_id, int(time.time()), value)) db.commit() def updateFactoid(self, channel, key, newvalue, modifier_id): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""UPDATE factoids - SET fact=%s, modified_by=%s, - modified_at=%s WHERE key LIKE %s""", - newvalue, modifier_id, int(time.time()), key) + SET fact=?, modified_by=?, + modified_at=? WHERE key LIKE ?""", + (newvalue, modifier_id, int(time.time()), key)) db.commit() def updateRequest(self, channel, key, hostmask): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""UPDATE factoids SET - last_requested_by = %s, - last_requested_at = %s, + last_requested_by = ?, + last_requested_at = ?, requested_count = requested_count + 1 - WHERE key = %s""", - hostmask, int(time.time()), key) + WHERE key = ?""", + (hostmask, int(time.time()), key)) db.commit() def removeFactoid(self, channel, key): db = self._getDb(channel) cursor = db.cursor() - cursor.execute("""DELETE FROM factoids WHERE key LIKE %s""", - key) + cursor.execute("""DELETE FROM factoids WHERE key LIKE ?""", + (key,)) db.commit() def locked(self, channel, key): db = self._getDb(channel) cursor = db.cursor() cursor.execute ("""SELECT locked_by FROM factoids - WHERE key LIKE %s""", key) + WHERE key LIKE ?""", (key,)) if cursor.fetchone()[0] is None: return False else: @@ -215,17 +220,17 @@ class SqliteMoobotDB(object): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""UPDATE factoids - SET locked_by=%s, locked_at=%s - WHERE key LIKE %s""", - locker_id, int(time.time()), key) + SET locked_by=?, locked_at=? + WHERE key LIKE ?""", + (locker_id, int(time.time()), key)) db.commit() def unlock(self, channel, key): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""UPDATE factoids - SET locked_by=%s, locked_at=%s - WHERE key LIKE %s""", None, None, key) + SET locked_by=?, locked_at=? + WHERE key LIKE ?""", (None, None, key)) db.commit() def mostAuthored(self, channel, limit): @@ -233,14 +238,14 @@ class SqliteMoobotDB(object): cursor = db.cursor() cursor.execute("""SELECT created_by, count(key) FROM factoids GROUP BY created_by - ORDER BY count(key) DESC LIMIT %s""", limit) + ORDER BY count(key) DESC LIMIT ?""", (limit,)) return cursor.fetchall() def mostRecent(self, channel, limit): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT key FROM factoids - ORDER BY created_at DESC LIMIT %s""", limit) + ORDER BY created_at DESC LIMIT ?""", (limit,)) return cursor.fetchall() def mostPopular(self, channel, limit): @@ -248,43 +253,35 @@ class SqliteMoobotDB(object): cursor = db.cursor() cursor.execute("""SELECT key, requested_count FROM factoids WHERE requested_count > 0 - ORDER BY requested_count DESC LIMIT %s""", limit) - if cursor.rowcount == 0: - return [] - else: - return cursor.fetchall() + ORDER BY requested_count DESC LIMIT ?""", (limit,)) + results = cursor.fetchall() + return results def getKeysByAuthor(self, channel, authorId): db = self._getDb(channel) cursor = db.cursor() - cursor.execute("""SELECT key FROM factoids WHERE created_by=%s - ORDER BY key""", authorId) - if cursor.rowcount == 0: - return [] - else: - return cursor.fetchall() + cursor.execute("""SELECT key FROM factoids WHERE created_by=? + ORDER BY key""", (authorId,)) + results = cursor.fetchall() + return results def getKeysByGlob(self, channel, glob): db = self._getDb(channel) cursor = db.cursor() glob = '%%%s%%' % glob - cursor.execute("""SELECT key FROM factoids WHERE key LIKE %s - ORDER BY key""", glob) - if cursor.rowcount == 0: - return [] - else: - return cursor.fetchall() + cursor.execute("""SELECT key FROM factoids WHERE key LIKE ? + ORDER BY key""", (glob,)) + results = cursor.fetchall() + return results def getKeysByValueGlob(self, channel, glob): db = self._getDb(channel) cursor = db.cursor() glob = '%%%s%%' % glob - cursor.execute("""SELECT key FROM factoids WHERE fact LIKE %s - ORDER BY key""", glob) - if cursor.rowcount == 0: - return [] - else: - return cursor.fetchall() + cursor.execute("""SELECT key FROM factoids WHERE fact LIKE ? + ORDER BY key""", (glob,)) + results = cursor.fetchall() + return results MoobotDB = plugins.DB('MoobotFactoids', {'sqlite': SqliteMoobotDB}) diff --git a/plugins/__init__.py b/plugins/__init__.py index c177eb840..2e9b2c14f 100644 --- a/plugins/__init__.py +++ b/plugins/__init__.py @@ -51,46 +51,51 @@ from supybot.commands import * import supybot.ircutils as ircutils import supybot.callbacks as callbacks +## i think we don't need any of this with sqlite3 +#try: + ## We need to sweep away all that mx.* crap because our code doesn't account + ## for PySQLite's arbitrary use of it. Whoever decided to change sqlite's + ## behavior based on whether or not that module is installed was a *CRACK* + ## **FIEND**, plain and simple. + #mxCrap = {} + #for (name, module) in sys.modules.items(): + #if name.startswith('mx'): + #mxCrap[name] = module + #sys.modules.pop(name) + ## Now that the mx crap is gone, we can import sqlite. + #import sqlite3 as sqlite + ## And now we'll put it back, even though it sucks. + #sys.modules.update(mxCrap) + ## Just in case, we'll do this as well. It doesn't seem to work fine by + ## itself, though, or else we'd just do this in the first place. + #sqlite.have_datetime = False + #Connection = sqlite.Connection + #class MyConnection(sqlite.Connection): + #def commit(self, *args, **kwargs): + #if self.autocommit: + #return + #else: + #Connection.commit(self, *args, **kwargs) + + #def __del__(self): + #try: + #Connection.__del__(self) + #except AttributeError: + #pass + #except Exception, e: + #try: + #log.exception('Uncaught exception in __del__:') + #except: + #pass + #sqlite.Connection = MyConnection + ##del Connection.__del__ +#except ImportError: + #pass + try: - # We need to sweep away all that mx.* crap because our code doesn't account - # for PySQLite's arbitrary use of it. Whoever decided to change sqlite's - # behavior based on whether or not that module is installed was a *CRACK* - # **FIEND**, plain and simple. - mxCrap = {} - for (name, module) in sys.modules.items(): - if name.startswith('mx'): - mxCrap[name] = module - sys.modules.pop(name) - # Now that the mx crap is gone, we can import sqlite. - import sqlite - # And now we'll put it back, even though it sucks. - sys.modules.update(mxCrap) - # Just in case, we'll do this as well. It doesn't seem to work fine by - # itself, though, or else we'd just do this in the first place. - sqlite.have_datetime = False - Connection = sqlite.Connection - class MyConnection(sqlite.Connection): - def commit(self, *args, **kwargs): - if self.autocommit: - return - else: - Connection.commit(self, *args, **kwargs) - - def __del__(self): - try: - Connection.__del__(self) - except AttributeError: - pass - except Exception, e: - try: - log.exception('Uncaught exception in __del__:') - except: - pass - sqlite.Connection = MyConnection - #del Connection.__del__ + import sqlite3 except ImportError: - pass - + from pysqlite2 import dbapi2 as sqlite3 # for python2.4 class NoSuitableDatabase(Exception): def __init__(self, suitable): @@ -176,7 +181,7 @@ class ChannelDBHandler(object): db = self.makeDb(self.makeFilename(channel)) else: db = self.dbCache[channel] - db.autocommit = 1 + db.isolation_level = None return db def die(self): From 2b5ffaa9405e9b947629dda1b231cebd82fd4117 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Mon, 22 Mar 2010 01:06:02 -0400 Subject: [PATCH 12/85] add replies function to reply plugin, which makes multiple replies, if supybot.reply.oneToOne is false. --- plugins/Reply/plugin.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/Reply/plugin.py b/plugins/Reply/plugin.py index d12b08cd7..fb4618359 100644 --- a/plugins/Reply/plugin.py +++ b/plugins/Reply/plugin.py @@ -79,6 +79,16 @@ class Reply(callbacks.Plugin): """ irc.reply(text, prefixNick=True) reply = wrap(reply, ['text']) + + @internationalizeDocstring + def replies(self, irc, msg, args, strings): + """ [ ...] + + Replies with each of its arguments in separate replies, depending + the configuration of supybot.reply.oneToOne. + """ + irc.replies(strings) + replies = wrap(replies, [many('something')]) Reply = internationalizeDocstring(Reply) Class = Reply From 5c0596f48462764ea1a68efb6899a2ceb226c2c9 Mon Sep 17 00:00:00 2001 From: nanotube Date: Tue, 23 Mar 2010 13:34:50 -0400 Subject: [PATCH 13/85] rename factrank to just rank for consistency --- plugins/Factoids/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index 2e4c7cd50..63a532429 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -270,7 +270,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): whatis = wrap(whatis, ['channel', many('something')]) @internationalizeDocstring - def factrank(self, irc, msg, args, channel): + def rank(self, irc, msg, args, channel): """[] Returns a list of top-ranked factoid keys, sorted by usage count @@ -289,7 +289,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): factkeys = cursor.fetchall() s = [ "#%d %s (%d)" % (i+1, key[0], key[1]) for i, key in enumerate(factkeys) ] irc.reply(", ".join(s)) - factrank = wrap(factrank, ['channel']) + rank = wrap(rank, ['channel']) @internationalizeDocstring def lock(self, irc, msg, args, channel, key): From b922890b6f20005d409f371c34a6572aaa2a0c01 Mon Sep 17 00:00:00 2001 From: nanotube Date: Tue, 23 Mar 2010 13:54:31 -0400 Subject: [PATCH 14/85] add rank test to factoids, update factoids test code to sqlite3 --- plugins/Factoids/test.py | 254 ++++++++++++++++++++------------------- 1 file changed, 130 insertions(+), 124 deletions(-) diff --git a/plugins/Factoids/test.py b/plugins/Factoids/test.py index 30a8a9f06..5cafe3ce9 100644 --- a/plugins/Factoids/test.py +++ b/plugins/Factoids/test.py @@ -31,137 +31,143 @@ from supybot.test import * try: - import sqlite + import sqlite3 except ImportError: - sqlite = None + from pysqlite2 import dbapi2 as sqlite3 # for python2.4 -if sqlite: - class FactoidsTestCase(ChannelPluginTestCase): - plugins = ('Factoids',) - def testRandomfactoid(self): - self.assertError('random') - self.assertNotError('learn jemfinch as my primary author') - self.assertRegexp('random', 'primary author') +class FactoidsTestCase(ChannelPluginTestCase): + plugins = ('Factoids',) + def testRandomfactoid(self): + self.assertError('random') + self.assertNotError('learn jemfinch as my primary author') + self.assertRegexp('random', 'primary author') - def testLearn(self): - self.assertError('learn as my primary author') - self.assertError('learn jemfinch as') - self.assertNotError('learn jemfinch as my primary author') - self.assertNotError('info jemfinch') - self.assertRegexp('whatis jemfinch', 'my primary author') - self.assertRegexp('whatis JEMFINCH', 'my primary author') - self.assertRegexp('whatis JEMFINCH 1', 'my primary author') - self.assertNotError('learn jemfinch as a bad assembly programmer') - self.assertRegexp('whatis jemfinch 2', 'bad assembly') - self.assertNotRegexp('whatis jemfinch 2', 'primary author') - self.assertRegexp('whatis jemfinch', r'.*primary author.*assembly') - self.assertError('forget jemfinch') - self.assertError('forget jemfinch 3') - self.assertError('forget jemfinch 0') - self.assertNotError('forget jemfinch 2') - self.assertNotError('forget jemfinch 1') - self.assertError('whatis jemfinch') - self.assertError('info jemfinch') + def testLearn(self): + self.assertError('learn as my primary author') + self.assertError('learn jemfinch as') + self.assertNotError('learn jemfinch as my primary author') + self.assertNotError('info jemfinch') + self.assertRegexp('whatis jemfinch', 'my primary author') + self.assertRegexp('whatis JEMFINCH', 'my primary author') + self.assertRegexp('whatis JEMFINCH 1', 'my primary author') + self.assertNotError('learn jemfinch as a bad assembly programmer') + self.assertRegexp('whatis jemfinch 2', 'bad assembly') + self.assertNotRegexp('whatis jemfinch 2', 'primary author') + self.assertRegexp('whatis jemfinch', r'.*primary author.*assembly') + self.assertError('forget jemfinch') + self.assertError('forget jemfinch 3') + self.assertError('forget jemfinch 0') + self.assertNotError('forget jemfinch 2') + self.assertNotError('forget jemfinch 1') + self.assertError('whatis jemfinch') + self.assertError('info jemfinch') - self.assertNotError('learn foo bar as baz') - self.assertNotError('info foo bar') - self.assertRegexp('whatis foo bar', 'baz') - self.assertNotError('learn foo bar as quux') - self.assertRegexp('whatis foo bar', '.*baz.*quux') - self.assertError('forget foo bar') - self.assertNotError('forget foo bar 2') - self.assertNotError('forget foo bar 1') - self.assertError('whatis foo bar') - self.assertError('info foo bar') + self.assertNotError('learn foo bar as baz') + self.assertNotError('info foo bar') + self.assertRegexp('whatis foo bar', 'baz') + self.assertNotError('learn foo bar as quux') + self.assertRegexp('whatis foo bar', '.*baz.*quux') + self.assertError('forget foo bar') + self.assertNotError('forget foo bar 2') + self.assertNotError('forget foo bar 1') + self.assertError('whatis foo bar') + self.assertError('info foo bar') - self.assertError('learn foo bar baz') # No 'as' - self.assertError('learn foo bar') # No 'as' + self.assertError('learn foo bar baz') # No 'as' + self.assertError('learn foo bar') # No 'as' - def testChangeFactoid(self): + def testChangeFactoid(self): + self.assertNotError('learn foo as bar') + self.assertNotError('change foo 1 s/bar/baz/') + self.assertRegexp('whatis foo', 'baz') + self.assertError('change foo 2 s/bar/baz/') + self.assertError('change foo 0 s/bar/baz/') + + def testSearchFactoids(self): + self.assertNotError('learn jemfinch as my primary author') + self.assertNotError('learn strike as a cool guy working on me') + self.assertNotError('learn inkedmn as another of my developers') + self.assertNotError('learn jamessan as a developer of much python') + self.assertNotError('learn bwp as author of my weather command') + self.assertRegexp('factoids search --regexp /.w./', 'bwp') + self.assertRegexp('factoids search --regexp /^.+i/', + 'jemfinch.*strike') + self.assertNotRegexp('factoids search --regexp /^.+i/', 'inkedmn') + self.assertRegexp('factoids search --regexp m/j/ --regexp m/ss/', + 'jamessan') + self.assertRegexp('factoids search --regexp m/^j/ *ss*', + 'jamessan') + self.assertRegexp('factoids search --regexp /^j/', + 'jamessan.*jemfinch') + self.assertRegexp('factoids search j*', 'jamessan.*jemfinch') + self.assertRegexp('factoids search *ke*', + 'inkedmn.*strike|strike.*inkedmn') + self.assertRegexp('factoids search ke', + 'inkedmn.*strike|strike.*inkedmn') + self.assertRegexp('factoids search jemfinch', + 'my primary author') + self.assertRegexp('factoids search --values primary author', + 'my primary author') + + def testWhatisOnNumbers(self): + self.assertNotError('learn 911 as emergency number') + self.assertRegexp('whatis 911', 'emergency number') + + def testNotZeroIndexed(self): + self.assertNotError('learn foo as bar') + self.assertNotRegexp('info foo', '#0') + self.assertNotRegexp('whatis foo', '#0') + self.assertNotError('learn foo as baz') + self.assertNotRegexp('info foo', '#0') + self.assertNotRegexp('whatis foo', '#0') + + def testInfoReturnsRightNumber(self): + self.assertNotError('learn foo as bar') + self.assertNotRegexp('info foo', '2 factoids') + + def testLearnSeparator(self): + self.assertError('learn foo is bar') + self.assertNotError('learn foo as bar') + self.assertRegexp('whatis foo', 'bar') + orig = conf.supybot.plugins.Factoids.learnSeparator() + try: + conf.supybot.plugins.Factoids.learnSeparator.setValue('is') + self.assertError('learn bar as baz') + self.assertNotError('learn bar is baz') + self.assertRegexp('whatis bar', 'baz') + finally: + conf.supybot.plugins.Factoids.learnSeparator.setValue(orig) + + def testShowFactoidIfOnlyOneMatch(self): + m1 = self.assertNotError('factoids search m/foo|bar/') + orig = conf.supybot.plugins.Factoids.showFactoidIfOnlyOneMatch() + try: + conf.supybot.plugins.Factoids. \ + showFactoidIfOnlyOneMatch.setValue(False) + m2 = self.assertNotError('factoids search m/foo/') + self.failUnless(m1.args[1].startswith(m2.args[1])) + finally: + conf.supybot.plugins.Factoids. \ + showFactoidIfOnlyOneMatch.setValue(orig) + + def testInvalidCommand(self): + orig = conf.supybot.plugins.Factoids.replyWhenInvalidCommand() + try: + conf.supybot.plugins.Factoids.\ + replyWhenInvalidCommand.setValue(True) self.assertNotError('learn foo as bar') - self.assertNotError('change foo 1 s/bar/baz/') - self.assertRegexp('whatis foo', 'baz') - self.assertError('change foo 2 s/bar/baz/') - self.assertError('change foo 0 s/bar/baz/') - - def testSearchFactoids(self): - self.assertNotError('learn jemfinch as my primary author') - self.assertNotError('learn strike as a cool guy working on me') - self.assertNotError('learn inkedmn as another of my developers') - self.assertNotError('learn jamessan as a developer of much python') - self.assertNotError('learn bwp as author of my weather command') - self.assertRegexp('factoids search --regexp /.w./', 'bwp') - self.assertRegexp('factoids search --regexp /^.+i/', - 'jemfinch.*strike') - self.assertNotRegexp('factoids search --regexp /^.+i/', 'inkedmn') - self.assertRegexp('factoids search --regexp m/j/ --regexp m/ss/', - 'jamessan') - self.assertRegexp('factoids search --regexp m/^j/ *ss*', - 'jamessan') - self.assertRegexp('factoids search --regexp /^j/', - 'jamessan.*jemfinch') - self.assertRegexp('factoids search j*', 'jamessan.*jemfinch') - self.assertRegexp('factoids search *ke*', - 'inkedmn.*strike|strike.*inkedmn') - self.assertRegexp('factoids search ke', - 'inkedmn.*strike|strike.*inkedmn') - self.assertRegexp('factoids search jemfinch', - 'my primary author') - self.assertRegexp('factoids search --values primary author', - 'my primary author') - - def testWhatisOnNumbers(self): - self.assertNotError('learn 911 as emergency number') - self.assertRegexp('whatis 911', 'emergency number') - - def testNotZeroIndexed(self): - self.assertNotError('learn foo as bar') - self.assertNotRegexp('info foo', '#0') - self.assertNotRegexp('whatis foo', '#0') - self.assertNotError('learn foo as baz') - self.assertNotRegexp('info foo', '#0') - self.assertNotRegexp('whatis foo', '#0') - - def testInfoReturnsRightNumber(self): - self.assertNotError('learn foo as bar') - self.assertNotRegexp('info foo', '2 factoids') - - def testLearnSeparator(self): - self.assertError('learn foo is bar') - self.assertNotError('learn foo as bar') - self.assertRegexp('whatis foo', 'bar') - orig = conf.supybot.plugins.Factoids.learnSeparator() - try: - conf.supybot.plugins.Factoids.learnSeparator.setValue('is') - self.assertError('learn bar as baz') - self.assertNotError('learn bar is baz') - self.assertRegexp('whatis bar', 'baz') - finally: - conf.supybot.plugins.Factoids.learnSeparator.setValue(orig) - - def testShowFactoidIfOnlyOneMatch(self): - m1 = self.assertNotError('factoids search m/foo|bar/') - orig = conf.supybot.plugins.Factoids.showFactoidIfOnlyOneMatch() - try: - conf.supybot.plugins.Factoids. \ - showFactoidIfOnlyOneMatch.setValue(False) - m2 = self.assertNotError('factoids search m/foo/') - self.failUnless(m1.args[1].startswith(m2.args[1])) - finally: - conf.supybot.plugins.Factoids. \ - showFactoidIfOnlyOneMatch.setValue(orig) - - def testInvalidCommand(self): - orig = conf.supybot.plugins.Factoids.replyWhenInvalidCommand() - try: - conf.supybot.plugins.Factoids.\ - replyWhenInvalidCommand.setValue(True) - self.assertNotError('learn foo as bar') - self.assertRegexp('foo', 'bar') - finally: - conf.supybot.plugins.Factoids.\ - replyWhenInvalidCommand.setValue(orig) - + self.assertRegexp('foo', 'bar') + finally: + conf.supybot.plugins.Factoids.\ + replyWhenInvalidCommand.setValue(orig) + + def testRank(self): + self.assertNotError('learn foo as bar') + self.assertNotError('learn moo as cow') + self.assertRegexp('factoids rank', '#1 foo \(0\), #2 moo \(0\)') + self.assertRegexp('whatis moo', '.*cow.*') + self.assertRegexp('factoids rank', '#1 moo \(1\), #2 foo \(0\)') + def testQuoteHandling(self): self.assertNotError('learn foo as "\\"bar\\""') self.assertRegexp('whatis foo', r'"bar"') From e71ee8fbb166b7ed81f960063d33f3a74c8a6aed Mon Sep 17 00:00:00 2001 From: nanotube Date: Tue, 23 Mar 2010 15:46:22 -0400 Subject: [PATCH 15/85] don't give up too easily with invalid command, instead search factoid keys with wildcard first. --- plugins/Factoids/config.py | 5 +++++ plugins/Factoids/plugin.py | 21 ++++++++++++++++++++- plugins/Factoids/test.py | 3 +++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/plugins/Factoids/config.py b/plugins/Factoids/config.py index bb16421de..d6be96f79 100644 --- a/plugins/Factoids/config.py +++ b/plugins/Factoids/config.py @@ -60,6 +60,11 @@ conf.registerChannelValue(Factoids, 'replyWhenInvalidCommand', registry.Boolean(True, _("""Determines whether the bot will reply to invalid commands by searching for a factoid; basically making the whatis unnecessary when you want all factoids for a given key."""))) +conf.registerChannelValue(Factoids, 'replyWhenInvalidCommandSearchKeys', + registry.Boolean(True, _("""If replyWhenInvalidCommand is True, and you + supply a nonexistent factoid as a command, this setting make the bot try a + wildcard search for factoid keys, returning a list of matching keys, + before giving up with an invalid command error."""))) conf.registerChannelValue(Factoids, 'format', FactoidFormat(_('$key could be $value.'), _("""Determines the format of the response given when a factoid's value is requested. All the standard diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index 63a532429..7d87db439 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -199,6 +199,16 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): return cursor.fetchall() #return [t[0] for t in cursor.fetchall()] + def _searchFactoid(self, channel, key): + db = self.getDb(channel) + cursor = db.cursor() + key = '%' + key + '%' + cursor.execute("""SELECT key FROM keys + WHERE key LIKE ? + LIMIT 20""", (key,)) + return cursor.fetchall() + + def _updateRank(self, channel, factoids): if self.registryValue('keepRankInfo', channel): db = self.getDb(channel) @@ -248,7 +258,16 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): if self.registryValue('replyWhenInvalidCommand', channel): key = ' '.join(tokens) factoids = self._lookupFactoid(channel, key) - self._replyFactoids(irc, msg, key, channel, factoids, error=False) + if factoids: + self._replyFactoids(irc, msg, key, channel, factoids, error=False) + else: + if self.registryValue('replyWhenInvalidCommandSearchKeys'): + factoids = self._searchFactoid(channel, key) + #print 'searchfactoids result:', factoids, '>' + if factoids: + keylist = ["'%s'" % (fact[0],) for fact in factoids] + keylist = ', '.join(keylist) + irc.reply("I do not know about '%s', but I do know about these similar topics: %s" % (key, keylist)) @internationalizeDocstring def whatis(self, irc, msg, args, channel, words): diff --git a/plugins/Factoids/test.py b/plugins/Factoids/test.py index 5cafe3ce9..ae35271a9 100644 --- a/plugins/Factoids/test.py +++ b/plugins/Factoids/test.py @@ -157,6 +157,9 @@ class FactoidsTestCase(ChannelPluginTestCase): replyWhenInvalidCommand.setValue(True) self.assertNotError('learn foo as bar') self.assertRegexp('foo', 'bar') + self.assertNotError('learn mooz as cowz') + self.assertRegexp('moo', 'mooz') + self.assertError('nosuchthing') finally: conf.supybot.plugins.Factoids.\ replyWhenInvalidCommand.setValue(orig) From 471921eab6b3ec0763fbf596908945b90d961580 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Thu, 1 Apr 2010 00:51:25 -0400 Subject: [PATCH 16/85] make 'factoids info' include usage count in output. add test for same. --- plugins/Factoids/plugin.py | 9 +++++---- plugins/Factoids/test.py | 6 ++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index 7d87db439..de93771cc 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -433,18 +433,19 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): irc.error(_('No factoid matches that key.')) return (id, locked) = map(int, results[0]) - cursor.execute("""SELECT added_by, added_at FROM factoids + cursor.execute("""SELECT added_by, added_at, usage_count FROM factoids WHERE key_id=? ORDER BY id""", (id,)) factoids = cursor.fetchall() L = [] counter = 0 - for (added_by, added_at) in factoids: + for (added_by, added_at, usage_count) in factoids: counter += 1 added_at = time.strftime(conf.supybot.reply.format.time(), time.localtime(int(added_at))) - L.append(format(_('#%i was added by %s at %s'), - counter, added_by, added_at)) + L.append(format(_('#%i was added by %s at %s, and has been ' + 'recalled %n'), + counter, added_by, added_at, (usage_count, 'time'))) factoids = '; '.join(L) s = format('Key %q is %s and has %n associated with it: %s', key, locked and 'locked' or 'not locked', diff --git a/plugins/Factoids/test.py b/plugins/Factoids/test.py index ae35271a9..8127c801d 100644 --- a/plugins/Factoids/test.py +++ b/plugins/Factoids/test.py @@ -125,6 +125,12 @@ class FactoidsTestCase(ChannelPluginTestCase): self.assertNotError('learn foo as bar') self.assertNotRegexp('info foo', '2 factoids') + def testInfoUsageCount(self): + self.assertNotError('learn moo as cow') + self.assertRegexp('info moo', 'recalled 0 times') + self.assertNotError('whatis moo') + self.assertRegexp('info moo', 'recalled 1 time') + def testLearnSeparator(self): self.assertError('learn foo is bar') self.assertNotError('learn foo as bar') From 436d2bade8557111d13a586ab845ed43ff5872c8 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Fri, 2 Apr 2010 00:03:01 -0400 Subject: [PATCH 17/85] mod factoids plugin to use a separate key-value relationship table this avoids duplication, and allows one to set a bunch of aliases for a factoid, without creating duplicates of the same fact content. --- plugins/Factoids/plugin.py | 193 ++++++++++++++++++++++++------------- 1 file changed, 126 insertions(+), 67 deletions(-) diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index de93771cc..bb61aede0 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -102,23 +102,21 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): cursor = db.cursor() cursor.execute("""CREATE TABLE keys ( id INTEGER PRIMARY KEY, - key TEXT UNIQUE ON CONFLICT IGNORE, - locked BOOLEAN + key TEXT UNIQUE ON CONFLICT REPLACE )""") cursor.execute("""CREATE TABLE factoids ( id INTEGER PRIMARY KEY, - key_id INTEGER, added_by TEXT, added_at TIMESTAMP, - usage_count INTEGER, - fact TEXT + fact TEXT UNIQUE ON CONFLICT REPLACE, + locked BOOLEAN + )""") + cursor.execute("""CREATE TABLE relations ( + id INTEGER PRIMARY KEY, + key_id INTEGER, + fact_id INTEGER, + usage_count INTEGER )""") - cursor.execute("""CREATE TRIGGER remove_factoids - BEFORE DELETE ON keys - BEGIN - DELETE FROM factoids WHERE key_id = old.id; - END - """) db.commit() return db @@ -153,31 +151,52 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): doc=method._fake__doc__ % (s, s), name=callbacks.formatCommand(command)) return super(Factoids, self).getCommandHelp(command, simpleSyntax) - - def learn(self, irc, msg, args, channel, key, factoid): + + def _getKeyAndFactId(self, channel, key, factoid): db = self.getDb(channel) cursor = db.cursor() - cursor.execute("SELECT id, locked FROM keys WHERE key LIKE ?", (key,)) - results = cursor.fetchall() - if len(results) == 0: - cursor.execute("""INSERT INTO keys VALUES (NULL, ?, 0)""", (key,)) + cursor.execute("SELECT id FROM keys WHERE key=?", (key,)) + keyresults = cursor.fetchall() + cursor.execute("SELECT id FROM factoids WHERE fact=?", (factoid,)) + factresults = cursor.fetchall() + return (keyresults, factresults,) + + def learn(self, irc, msg, args, channel, key, factoid): + + # if neither key nor factoid exist, add them. + # if key exists but factoid doesn't, add factoid, link it to existing key + # if factoid exists but key doesn't, add key, link it to existing factoid + # if both key and factoid already exist, and are linked, do nothing, print nice message + db = self.getDb(channel) + cursor = db.cursor() + (keyid, factid) = self._getKeyAndFactId(channel, key, factoid) + + if len(keyid) == 0: + cursor.execute("""INSERT INTO keys VALUES (NULL, ?)""", (key,)) db.commit() - cursor.execute("SELECT id, locked FROM keys WHERE key LIKE ?", (key,)) - results = cursor.fetchall() - (id, locked) = map(int, results[0]) - capability = ircdb.makeChannelCapability(channel, 'factoids') - if not locked: + if len(factid) == 0: if ircdb.users.hasUser(msg.prefix): name = ircdb.users.getUser(msg.prefix).name else: name = msg.nick cursor.execute("""INSERT INTO factoids VALUES - (NULL, ?, ?, ?, ?, ?)""", - (id, name, int(time.time()), 0, factoid)) + (NULL, ?, ?, ?, ?)""", + (name, int(time.time()), factoid, 0)) + db.commit() + (keyid, factid) = self._getKeyAndFactId(channel, key, factoid) + + cursor.execute("""SELECT id, key_id, fact_id from relations + WHERE key_id=? AND fact_id=?""", + (keyid[0][0], factid[0][0],)) + existingrelation = cursor.fetchall() + if len(existingrelation) == 0: + cursor.execute("""INSERT INTO relations VALUES (NULL, ?, ?, ?)""", + (keyid[0][0],factid[0][0],0,)) db.commit() irc.replySuccess() else: - irc.error('That factoid is locked.') + irc.error("This key-factoid relationship already exists.") + learn = wrap(learn, ['factoid']) learn._fake__doc__ = _("""[] %s @@ -192,8 +211,8 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): def _lookupFactoid(self, channel, key): db = self.getDb(channel) cursor = db.cursor() - cursor.execute("""SELECT factoids.fact, factoids.id FROM factoids, keys - WHERE keys.key LIKE ? AND factoids.key_id=keys.id + cursor.execute("""SELECT factoids.fact, factoids.id, relations.id FROM factoids, keys, relations + WHERE keys.key LIKE ? AND relations.key_id=keys.id AND relations.fact_id=factoids.id ORDER BY factoids.id LIMIT 20""", (key,)) return cursor.fetchall() @@ -213,12 +232,13 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): if self.registryValue('keepRankInfo', channel): db = self.getDb(channel) cursor = db.cursor() - for (fact,id) in factoids: - cursor.execute("""SELECT factoids.usage_count - FROM factoids - WHERE factoids.id=?""", (id,)) + for (fact,factid,relationid) in factoids: + cursor.execute("""SELECT relations.usage_count + FROM relations + WHERE relations.id=?""", (relationid,)) old_count = cursor.fetchall()[0][0] - cursor.execute("UPDATE factoids SET usage_count=? WHERE id=?", (old_count + 1, id,)) + cursor.execute("UPDATE relations SET usage_count=? WHERE id=?", + (old_count + 1, relationid,)) db.commit() def _replyFactoids(self, irc, msg, key, channel, factoids, @@ -300,10 +320,10 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): numfacts = self.registryValue('rankListLength', channel) db = self.getDb(channel) cursor = db.cursor() - cursor.execute("""SELECT keys.key, factoids.usage_count - FROM keys, factoids - WHERE factoids.key_id=keys.id - ORDER BY factoids.usage_count DESC + cursor.execute("""SELECT keys.key, relations.usage_count + FROM keys, relations + WHERE relations.key_id=keys.id + ORDER BY relations.usage_count DESC LIMIT ?""", (numfacts,)) factkeys = cursor.fetchall() s = [ "#%d %s (%d)" % (i+1, key[0], key[1]) for i, key in enumerate(factkeys) ] @@ -320,7 +340,10 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): """ db = self.getDb(channel) cursor = db.cursor() - cursor.execute("UPDATE keys SET locked=1 WHERE key LIKE ?", (key,)) + cursor.execute("UPDATE factoids, keys, relations " + "SET factoids.locked=1 WHERE key LIKE ? AND " + "factoids.id=relations.fact_id AND " + "keys.id=relations.key_id", (key,)) db.commit() irc.replySuccess() lock = wrap(lock, ['channel', 'text']) @@ -335,19 +358,48 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): """ db = self.getDb(channel) cursor = db.cursor() - cursor.execute("UPDATE keys SET locked=0 WHERE key LIKE ?", (key,)) + cursor.execute("""UPDATE factoids, keys, relations + SET factoids.locked=1 WHERE key LIKE ? AND + factoids.id=relations.fact_id AND + keys.id=relations.key_id""", (key,)) db.commit() irc.replySuccess() unlock = wrap(unlock, ['channel', 'text']) + def _deleteRelation(self, channel, relationlist): + db = self.getDb(channel) + cursor = db.cursor() + for (keyid, factid, relationid) in relationlist: + cursor.execute("""DELETE FROM relations where relations.id=?""", + (relationid,)) + db.commit() + + cursor.execute("""SELECT id FROM relations + WHERE relations.key_id=?""", (keyid,)) + remaining_key_relations = cursor.fetchall() + if len(remaining_key_relations) == 0: + cursor.execute("""DELETE FROM keys where id=?""", (keyid,)) + + cursor.execute("""SELECT id FROM relations + WHERE relations.fact_id=?""", (factid,)) + remaining_fact_relations = cursor.fetchall() + if len(remaining_fact_relations) == 0: + cursor.execute("""DELETE FROM factoids where id=?""", (factid,)) + db.commit() + @internationalizeDocstring def forget(self, irc, msg, args, channel, words): """[] [|*] - Removes the factoid from the factoids database. If there are - more than one factoid with such a key, a number is necessary to - determine which one should be removed. A * can be used to remove all - factoids associated with a key. is only necessary if + Removes a key-fact relationship for key from the factoids + database. If there is more than one such relationship for this key, + a number is necessary to determine which one should be removed. + A * can be used to remove all relationships for . + + If as a result, the key (factoid) remains without any relationships to + a factoid (key), it shall be removed from the database. + + is only necessary if the message isn't sent in the channel itself. """ number = None @@ -362,29 +414,26 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): key = ' '.join(words) db = self.getDb(channel) cursor = db.cursor() - cursor.execute("""SELECT keys.id, factoids.id - FROM keys, factoids - WHERE key LIKE ? AND - factoids.key_id=keys.id""", (key,)) + cursor.execute("""SELECT keys.id, factoids.id, relations.id + FROM keys, factoids, relations + WHERE key LIKE ? AND + relations.key_id=keys.id AND + relations.fact_id=factoids.id""", (key,)) results = cursor.fetchall() if len(results) == 0: irc.error(_('There is no such factoid.')) elif len(results) == 1 or number is True: - (id, _) = results[0] - cursor.execute("""DELETE FROM factoids WHERE key_id=?""", (id,)) - cursor.execute("""DELETE FROM keys WHERE key LIKE ?""", (key,)) - db.commit() + self._deleteRelation(channel, results) irc.replySuccess() else: if number is not None: #results = cursor.fetchall() try: - (foo, id) = results[number-1] + arelation = results[number-1] except IndexError: irc.error(_('Invalid factoid number.')) return - cursor.execute("DELETE FROM factoids WHERE id=?", (id,)) - db.commit() + self._deleteRelation(channel, [arelation,]) irc.replySuccess() else: irc.error(_('%s factoids have that key. ' @@ -402,15 +451,18 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): """ db = self.getDb(channel) cursor = db.cursor() - cursor.execute("""SELECT fact, key_id FROM factoids + cursor.execute("""SELECT id, key_id, fact_id FROM relations ORDER BY random() LIMIT 3""") results = cursor.fetchall() if len(results) != 0: L = [] - for (factoid, id) in results: - cursor.execute("""SELECT key FROM keys WHERE id=?""", (id,)) - (key,) = cursor.fetchone() + for (relationid, keyid, factid) in results: + cursor.execute("""SELECT keys.key, factoids.fact + FROM keys, factoids + WHERE factoids.id=? AND + keys.id=?""", (factid,keyid,)) + (key,factoid) = cursor.fetchall()[0] L.append('"%s": %s' % (ircutils.bold(key), factoid)) irc.reply('; '.join(L)) else: @@ -427,19 +479,21 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): """ db = self.getDb(channel) cursor = db.cursor() - cursor.execute("SELECT id, locked FROM keys WHERE key LIKE ?", (key,)) + cursor.execute("SELECT id FROM keys WHERE key LIKE ?", (key,)) results = cursor.fetchall() if len(results) == 0: irc.error(_('No factoid matches that key.')) return - (id, locked) = map(int, results[0]) - cursor.execute("""SELECT added_by, added_at, usage_count FROM factoids - WHERE key_id=? - ORDER BY id""", (id,)) + id = results[0][0] + cursor.execute("""SELECT factoids.added_by, factoids.added_at, factoids.locked, relations.usage_count + FROM factoids, relations + WHERE relations.key_id=? AND + relations.fact_id=factoids.id + ORDER BY relations.id""", (id,)) factoids = cursor.fetchall() L = [] counter = 0 - for (added_by, added_at, usage_count) in factoids: + for (added_by, added_at, locked, usage_count) in factoids: counter += 1 added_at = time.strftime(conf.supybot.reply.format.time(), time.localtime(int(added_at))) @@ -463,9 +517,10 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): db = self.getDb(channel) cursor = db.cursor() cursor.execute("""SELECT factoids.id, factoids.fact - FROM keys, factoids - WHERE keys.key LIKE ? AND - keys.id=factoids.key_id""", (key,)) + FROM keys, factoids, relations + WHERE keys.key LIKE ? AND + keys.id=relations.key_id AND + factoids.id=relations.fact_id""", (key,)) results = cursor.fetchall() if len(results) == 0: irc.error(format(_('I couldn\'t find any key %q'), key)) @@ -502,7 +557,8 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): target = 'factoids.fact' if 'factoids' not in tables: tables.append('factoids') - criteria.append('factoids.key_id=keys.id') + tables.append('relations') + criteria.append('factoids.id=relations.fact_id AND keys.id=relations.key_id') elif option == 'regexp': criteria.append('%s(TARGET)' % predicateName) def p(s, r=arg): @@ -516,7 +572,10 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): sql = """SELECT keys.key FROM %s WHERE %s""" % \ (', '.join(tables), ' AND '.join(criteria)) sql = sql + " ORDER BY keys.key" + print sql sql = sql.replace('TARGET', target) + print sql + print formats cursor.execute(sql, formats) if cursor.rowcount == 0: irc.reply(_('No keys matched that query.')) From 366c2b7c170b811c9836e475f85dc4d8bb453672 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Fri, 2 Apr 2010 00:49:43 -0400 Subject: [PATCH 18/85] add factoids alias function, to link more keys to existing factoids. --- plugins/Factoids/plugin.py | 70 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index bb61aede0..273eb9b3a 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -308,6 +308,76 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): self._replyFactoids(irc, msg, key, channel, factoids, number) whatis = wrap(whatis, ['channel', many('something')]) + @internationalizeDocstring + def alias(self, irc, msg, args, channel, oldkey, newkey, number): + """[] [] + + Adds a new key for factoid associated with . + is only necessary if there's more than one factoid associated + with . + + The same action can be accomplished by using the 'learn' function with + a new key but an existing (verbatim) factoid content. + """ + def _getNewKey(channel, newkey, arelation): + db = self.getDb(channel) + cursor = db.cursor() + cursor.execute("""SELECT id FROM keys WHERE key=?""", (newkey,)) + newkey_info = cursor.fetchall() + if len(newkey_info) == 1: + # check if we already have the requested relation + cursor.execute("""SELECT id FROM relations WHERE + key_id=? and fact_id=?""", + (arelation[1], arelation[2])) + existentrelation = cursor.fetchall() + if len(existentrelation) != 0: + newkey_info = False + if len(newkey_info) == 0: + cursor.execute("""INSERT INTO keys VALUES (NULL, ?)""", + (newkey,)) + db.commit() + cursor.execute("""SELECT id FROM keys WHERE key=?""", (newkey,)) + newkey_info = cursor.fetchall() + return newkey_info + + db = self.getDb(channel) + cursor = db.cursor() + cursor.execute("""SELECT relations.id, relations.key_id, relations.fact_id + FROM keys, relations + WHERE keys.key=? AND + relations.key_id=keys.id""", (oldkey,)) + results = cursor.fetchall() + if len(results) == 0: + irc.error(_('No factoid matches that key.')) + return + elif len(results) == 1: + newkey_info = _getNewKey(channel, newkey, results[0]) + if newkey_info is not False: + cursor.execute("""INSERT INTO relations VALUES(NULL, ?, ?, ?)""", + (newkey_info[0][0], results[0][2], 0,)) + irc.replySuccess() + else: + irc.error(_('This key-factoid relationship already exists.')) + elif len(results) > 1: + try: + arelation = results[number-1] + except IndexError: + irc.error(_("That's not a valid number for that key.")) + return + except TypeError: + irc.error(_("This key has more than one factoid associated " + "with it, but you have not provided a number.")) + return + newkey_info = _getNewKey(channel, newkey, arelation) + if newkey_info is not False: + cursor.execute("""INSERT INTO relations VALUES(NULL, ?, ?, ?)""", + (newkey_info[0][0], arelation[2], 0,)) + irc.replySuccess() + else: + irc.error(_('This key-factoid relationship already exists.')) + + alias = wrap(alias, ['channel', 'something', 'something', optional('int')]) + @internationalizeDocstring def rank(self, irc, msg, args, channel): """[] From 50ac5a05e1b591a78ab189a867558bc21a2d37fe Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Fri, 2 Apr 2010 00:51:06 -0400 Subject: [PATCH 19/85] delete leftover debug-prints --- plugins/Factoids/plugin.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index 273eb9b3a..a52c44377 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -642,10 +642,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): sql = """SELECT keys.key FROM %s WHERE %s""" % \ (', '.join(tables), ' AND '.join(criteria)) sql = sql + " ORDER BY keys.key" - print sql sql = sql.replace('TARGET', target) - print sql - print formats cursor.execute(sql, formats) if cursor.rowcount == 0: irc.reply(_('No keys matched that query.')) From 9b516480afb0f39aa5cca3ea8df7a17b70ca5a77 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Fri, 2 Apr 2010 00:55:02 -0400 Subject: [PATCH 20/85] add tests for factoids.alias --- plugins/Factoids/test.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugins/Factoids/test.py b/plugins/Factoids/test.py index 8127c801d..ef9ab6cca 100644 --- a/plugins/Factoids/test.py +++ b/plugins/Factoids/test.py @@ -170,6 +170,15 @@ class FactoidsTestCase(ChannelPluginTestCase): conf.supybot.plugins.Factoids.\ replyWhenInvalidCommand.setValue(orig) + def testAlias(self): + self.assertNotError('learn foo as bar') + self.assertNotError('alias foo zoog') + self.assertRegexp('whatis zoog', 'bar') + self.assertNotError('learn foo as snorp') + self.assertError('alias foo gnoop') + self.assertNotError('alias foo gnoop 2') + self.assertRegexp('whatis gnoop', 'snorp') + def testRank(self): self.assertNotError('learn foo as bar') self.assertNotError('learn moo as cow') From 6b324674a7149eca45b74b3e2f734eb2bd7caf31 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Fri, 2 Apr 2010 01:57:00 -0400 Subject: [PATCH 21/85] enable google translate to autodetect language with 'auto' fromlang. --- plugins/Google/config.py | 3 ++- plugins/Google/plugin.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/Google/config.py b/plugins/Google/config.py index cd1771a6d..cd92d2d9a 100644 --- a/plugins/Google/config.py +++ b/plugins/Google/config.py @@ -72,7 +72,8 @@ class Language(registry.OnlySomeStrings): 'Tamil': 'ta', 'Tagalog': 'tl', 'Telugu': 'te', 'Thai': 'th', 'Tibetan': 'bo', 'Turkish': 'tr', 'Ukranian': 'uk', 'Urdu': 'ur', 'Uzbek': 'uz', - 'Uighur': 'ug', 'Vietnamese': 'vi'} + 'Uighur': 'ug', 'Vietnamese': 'vi', + 'Detect language': 'auto'} validStrings = ['lang_' + s for s in transLangs.values()] validStrings.append('') def normalize(self, s): diff --git a/plugins/Google/plugin.py b/plugins/Google/plugin.py index 3c87e8b0e..422d613b9 100644 --- a/plugins/Google/plugin.py +++ b/plugins/Google/plugin.py @@ -282,6 +282,8 @@ class Google(callbacks.PluginRegexp): lang.transLangs.keys())) else: toLang = lang.normalize('lang_'+toLang)[5:] + if fromLang == 'auto': + fromLang = '' opts['langpair'] = '%s|%s' % (fromLang, toLang) fd = utils.web.getUrlFd('%s?%s' % (self._gtranslateUrl, urllib.urlencode(opts)), From ca045128d7ec498ca47f90b821e186a7399b9aae Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Sat, 3 Apr 2010 23:31:13 -0400 Subject: [PATCH 22/85] add detected source language display for 'auto' google translate, add test for auto translate. --- plugins/Google/plugin.py | 13 ++++++++++++- plugins/Google/test.py | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/plugins/Google/plugin.py b/plugins/Google/plugin.py index 422d613b9..9d5fa42d9 100644 --- a/plugins/Google/plugin.py +++ b/plugins/Google/plugin.py @@ -292,7 +292,18 @@ class Google(callbacks.PluginRegexp): fd.close() if json['responseStatus'] != 200: raise callbacks.Error, 'We broke The Google!' - irc.reply(json['responseData']['translatedText'].encode('utf-8')) + if fromLang != '': + irc.reply(json['responseData']['translatedText'].encode('utf-8')) + else: + detected_language = json['responseData']['detectedSourceLanguage'] + try: + long_lang_name = [k for k,v in lang.transLangs.iteritems() if v == detected_language][0] + except IndexError: #just in case google adds langs we don't know about + long_lang_name = detected_language + responsestring = "(Detected source language: %s) %s" % \ + (long_lang_name, + json['responseData']['translatedText'].encode('utf-8')) + irc.reply(responsestring) translate = wrap(translate, ['something', 'to', 'something', 'text']) def googleSnarfer(self, irc, msg, match): diff --git a/plugins/Google/test.py b/plugins/Google/test.py index 663562a9e..3ef6eca81 100644 --- a/plugins/Google/test.py +++ b/plugins/Google/test.py @@ -61,6 +61,7 @@ class GoogleTestCase(ChannelPluginTestCase): def testTranslate(self): self.assertRegexp('translate en es hello world', 'mundo') + self.assertRegexp('translate auto en ciao', 'Italian.*hello') def testCalcDoesNotHaveExtraSpaces(self): self.assertNotRegexp('google calc 1000^2', r'\s+,\s+') From bdc8fd5285c3597ea522395ed4ff49d653c37530 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Sun, 4 Apr 2010 01:12:50 -0400 Subject: [PATCH 23/85] fix some encoding error for non-ascii langs --- plugins/Google/plugin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/Google/plugin.py b/plugins/Google/plugin.py index 9d5fa42d9..ff056e7ca 100644 --- a/plugins/Google/plugin.py +++ b/plugins/Google/plugin.py @@ -295,14 +295,14 @@ class Google(callbacks.PluginRegexp): if fromLang != '': irc.reply(json['responseData']['translatedText'].encode('utf-8')) else: - detected_language = json['responseData']['detectedSourceLanguage'] + detected_language = json['responseData']['detectedSourceLanguage'].encode('utf-8') + translation = json['responseData']['translatedText'].encode('utf-8') try: long_lang_name = [k for k,v in lang.transLangs.iteritems() if v == detected_language][0] except IndexError: #just in case google adds langs we don't know about long_lang_name = detected_language responsestring = "(Detected source language: %s) %s" % \ - (long_lang_name, - json['responseData']['translatedText'].encode('utf-8')) + (long_lang_name, translation) irc.reply(responsestring) translate = wrap(translate, ['something', 'to', 'something', 'text']) From b306c5440fddf97961376d7f83bcfd8aec32ae1d Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Wed, 7 Apr 2010 12:33:28 -0400 Subject: [PATCH 24/85] add damerau-levenshtein distance to supybot.utils.seq use it in factoids invalid command to match possible typos write tests for same. --- plugins/Factoids/plugin.py | 42 ++++++++++++++++++++++++++++-------- plugins/Factoids/test.py | 4 ++++ src/utils/seq.py | 44 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 9 deletions(-) diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index a52c44377..c09d0fb32 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -54,6 +54,9 @@ try: except ImportError: from pysqlite2 import dbapi2 as sqlite3 # for python2.4 +import re +from supybot.utils.seq import dameraulevenshtein + # these are needed cuz we are overriding getdb import threading import supybot.world as world @@ -219,15 +222,37 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): #return [t[0] for t in cursor.fetchall()] def _searchFactoid(self, channel, key): + """Try to typo-match input to possible factoids. + + Assume first letter is correct, to reduce processing time. + First, try a simple wildcard search. + If that fails, use the Damerau-Levenshtein edit-distance metric. + """ + # if you made a typo in a two-character key, boo on you. + if len(key) < 3: + return [] + db = self.getDb(channel) cursor = db.cursor() - key = '%' + key + '%' - cursor.execute("""SELECT key FROM keys - WHERE key LIKE ? - LIMIT 20""", (key,)) - return cursor.fetchall() - - + cursor.execute("""SELECT key FROM keys WHERE key LIKE ?""", ('%' + key + '%',)) + wildcardkeys = cursor.fetchall() + if len(wildcardkeys) > 0: + return [line[0] for line in wildcardkeys] + + cursor.execute("""SELECT key FROM keys WHERE key LIKE ?""", (key[0] + '%',)) + flkeys = cursor.fetchall() + if len(flkeys) == 0: + return [] + flkeys = [line[0] for line in flkeys] + dl_metrics = [dameraulevenshtein(key, sourcekey) for sourcekey in flkeys] + dict_metrics = dict(zip(flkeys, dl_metrics)) + if min(dl_metrics) <= 2: + return [key for key,item in dict_metrics.iteritems() if item <= 2] + if min(dl_metrics) <= 3: + return [key for key,item in dict_metrics.iteritems() if item <= 3] + + return [] + def _updateRank(self, channel, factoids): if self.registryValue('keepRankInfo', channel): db = self.getDb(channel) @@ -283,9 +308,8 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): else: if self.registryValue('replyWhenInvalidCommandSearchKeys'): factoids = self._searchFactoid(channel, key) - #print 'searchfactoids result:', factoids, '>' if factoids: - keylist = ["'%s'" % (fact[0],) for fact in factoids] + keylist = ["'%s'" % (fact,) for fact in factoids] keylist = ', '.join(keylist) irc.reply("I do not know about '%s', but I do know about these similar topics: %s" % (key, keylist)) diff --git a/plugins/Factoids/test.py b/plugins/Factoids/test.py index ef9ab6cca..1dddfb19c 100644 --- a/plugins/Factoids/test.py +++ b/plugins/Factoids/test.py @@ -165,6 +165,10 @@ class FactoidsTestCase(ChannelPluginTestCase): self.assertRegexp('foo', 'bar') self.assertNotError('learn mooz as cowz') self.assertRegexp('moo', 'mooz') + self.assertRegexp('mzo', 'mooz') + self.assertRegexp('moz', 'mooz') + self.assertNotError('learn moped as pretty fast') + self.assertRegexp('moe', 'mooz.*moped') self.assertError('nosuchthing') finally: conf.supybot.plugins.Factoids.\ diff --git a/src/utils/seq.py b/src/utils/seq.py index 8df15b226..67b0272be 100644 --- a/src/utils/seq.py +++ b/src/utils/seq.py @@ -45,7 +45,51 @@ def renumerate(L): for i in xrange(len(L)-1, -1, -1): yield (i, L[i]) +def dameraulevenshtein(seq1, seq2): + """Calculate the Damerau-Levenshtein distance between sequences. + This distance is the number of additions, deletions, substitutions, + and transpositions needed to transform the first sequence into the + second. Although generally used with strings, any sequences of + comparable objects will work. + + Transpositions are exchanges of *consecutive* characters; all other + operations are self-explanatory. + + This implementation is O(N*M) time and O(M) space, for N and M the + lengths of the two sequences. + + >>> dameraulevenshtein('ba', 'abc') + 2 + >>> dameraulevenshtein('fee', 'deed') + 2 + + It works with arbitrary sequences too: + >>> dameraulevenshtein('abcd', ['b', 'a', 'c', 'd', 'e']) + 2 + """ + # codesnippet:D0DE4716-B6E6-4161-9219-2903BF8F547F + # Conceptually, this is based on a len(seq1) + 1 * len(seq2) + 1 matrix. + # However, only the current and two previous rows are needed at once, + # so we only store those. + # Sourced from http://mwh.geek.nz/2009/04/26/python-damerau-levenshtein-distance/ + oneago = None + thisrow = range(1, len(seq2) + 1) + [0] + for x in xrange(len(seq1)): + # Python lists wrap around for negative indices, so put the + # leftmost column at the *end* of the list. This matches with + # the zero-indexed strings and saves extra calculation. + twoago, oneago, thisrow = oneago, thisrow, [0] * len(seq2) + [x + 1] + for y in xrange(len(seq2)): + delcost = oneago[y] + 1 + addcost = thisrow[y - 1] + 1 + subcost = oneago[y - 1] + (seq1[x] != seq2[y]) + thisrow[y] = min(delcost, addcost, subcost) + # This block deals with transpositions + if (x > 0 and y > 0 and seq1[x] == seq2[y - 1] + and seq1[x-1] == seq2[y] and seq1[x] != seq2[y]): + thisrow[y] = min(thisrow[y], twoago[y - 2] + 1) + return thisrow[len(seq2) - 1] # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: From a2da24d2029e4fcaba62f1676fab0f84502159bf Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Thu, 8 Apr 2010 00:04:44 -0400 Subject: [PATCH 25/85] add a random-synonym getting function to Dict, add tests for it. using the moby-thes database from dict.org. --- plugins/Dict/plugin.py | 29 +++++++++++++++++++++++++++++ plugins/Dict/test.py | 5 +++++ 2 files changed, 34 insertions(+) diff --git a/plugins/Dict/plugin.py b/plugins/Dict/plugin.py index 080edd2d5..087054d17 100644 --- a/plugins/Dict/plugin.py +++ b/plugins/Dict/plugin.py @@ -38,6 +38,8 @@ import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Dict') +import random + try: dictclient = utils.python.universalImport('dictclient', 'local.dictclient') except ImportError: @@ -130,6 +132,33 @@ class Dict(callbacks.Plugin): irc.reply(s) dict = wrap(dict, [many('something')]) + def synonym(self, irc, msg, args, words): + """ [ ...] + Gets a random synonym from the Moby Thesaurus (moby-thes) database. + + If given many words, gets a random synonym for each of them. + + Quote phrases to have them treated as one lookup word. + """ + try: + server = conf.supybot.plugins.Dict.server() + conn = dictclient.Connection(server) + except socket.error, e: + irc.error(utils.web.strError(e), Raise=True) + + dictionary = 'moby-thes' + response = [] + for word in words: + definitions = conn.define(dictionary, word) + if not definitions: + asynonym = word + else: + defstr = definitions[0].getdefstr() + synlist = ' '.join(defstr.split('\n')).split(': ', 1)[1].split(',') + asynonym = random.choice(synlist).strip() + response.append(asynonym) + irc.reply(' '.join(response)) + synonym = wrap(synonym, [many('something')]) Class = Dict diff --git a/plugins/Dict/test.py b/plugins/Dict/test.py index 81c9c52e5..b853b21e8 100644 --- a/plugins/Dict/test.py +++ b/plugins/Dict/test.py @@ -43,5 +43,10 @@ class DictTestCase(PluginTestCase): def testRandomDictionary(self): self.assertNotError('random') self.assertNotError('dict [random] moo') + + def testSynonym(self): + self.assertNotError('synonym stuff') + self.assertNotError('synonym someone goes home') + self.assertRegexp('synonym nanotube', 'nanotube') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: From fcaa7863fa00315df989c5842c716f74d53adf68 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Fri, 9 Apr 2010 00:45:14 -0400 Subject: [PATCH 26/85] add nick validation to later tell this avoids plugging the later db with messages for bogus nicks --- plugins/Later/plugin.py | 26 ++++++++++++++++++++++++-- plugins/Later/test.py | 5 +++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/plugins/Later/plugin.py b/plugins/Later/plugin.py index b382559af..6c423b4e6 100644 --- a/plugins/Later/plugin.py +++ b/plugins/Later/plugin.py @@ -41,7 +41,6 @@ import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Later') - class Later(callbacks.Plugin): """Used to do things later; currently, it only allows the sending of nick-based notes. Do note (haha!) that these notes are *not* private @@ -102,6 +101,25 @@ class Later(callbacks.Plugin): self.wildcards.append(nick) self._flushNotes() + def _validateNick(self, nick): + """Validate nick according to the IRC RFC 2812 spec. + + Reference: http://tools.ietf.org/rfcmarkup?doc=2812#section-2.3.1 + + Some irc clients' tab-completion feature appends 'address' characters + to nick, such as ':' or ','. We try correcting for that by trimming + a char off the end. + + If nick incorrigibly invalid, return False, otherwise, + return (possibly trimmed) nick. + """ + if not ircutils.isNick(nick, strictRfc=True): + if not ircutils.isNick(nick[:-1], strictRfc=True): + return False + else: + return nick[:-1] + return nick + @internationalizeDocstring def tell(self, irc, msg, args, nick, text): """ @@ -113,8 +131,12 @@ class Later(callbacks.Plugin): if ircutils.strEqual(nick, irc.nick): irc.error(_('I can\'t send notes to myself.')) return + validnick = self._validateNick(nick) + if validnick is False: + irc.error('That is an invalid IRC nick. Please check your input.') + return try: - self._addNote(nick, msg.nick, text) + self._addNote(validnick, msg.nick, text) irc.replySuccess() except ValueError: irc.error(_('That person\'s message queue is already full.')) diff --git a/plugins/Later/test.py b/plugins/Later/test.py index dc3a95069..d91070620 100644 --- a/plugins/Later/test.py +++ b/plugins/Later/test.py @@ -43,6 +43,11 @@ class LaterTestCase(PluginTestCase): self.assertNotRegexp('later notes', 'bar.*foo') self.assertRegexp('later notes', 'foo') + def testNickValidation(self): + self.assertError('later tell 1foo bar') + self.assertError('later tell foo$moo zoob') + self.assertNotError('later tell foo: baz') + self.assertRegexp('later notes', 'foo\.') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: From 7ec1ca207036a94d042aa847241d5dbf0562aa9f Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Fri, 9 Apr 2010 13:34:39 -0400 Subject: [PATCH 27/85] make later plugin send waiting messages on user join also, not just on privmsg. --- plugins/Later/plugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/Later/plugin.py b/plugins/Later/plugin.py index 6c423b4e6..109cc65a1 100644 --- a/plugins/Later/plugin.py +++ b/plugins/Later/plugin.py @@ -202,6 +202,8 @@ class Later(callbacks.Plugin): def _formatNote(self, when, whence, note): return _('Sent %s: <%s> %s') % (self._timestamp(when), whence, note) + + doJoin = doPrivmsg Later = internationalizeDocstring(Later) From c0986e112256dd1c6f85c8eeb42f484df3bbb8e2 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Fri, 9 Apr 2010 15:56:16 -0400 Subject: [PATCH 28/85] change Topic to have a default required capability set, for all write operations. by default, now only allows chanops, and users with admin or channel,op capability to change topics --- plugins/Topic/config.py | 7 +++---- plugins/Topic/plugin.py | 7 +++++-- plugins/Topic/test.py | 10 +++------- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/plugins/Topic/config.py b/plugins/Topic/config.py index dbef35a33..3ddfff93b 100644 --- a/plugins/Topic/config.py +++ b/plugins/Topic/config.py @@ -66,12 +66,11 @@ conf.registerChannelValue(Topic.undo, 'max', registry.NonNegativeInteger(10, _("""Determines the number of previous topics to keep around in case the undo command is called."""))) conf.registerChannelValue(Topic, 'requireManageCapability', - registry.String('channel,op; channel,halfop', - _("""Determines the + registry.String('admin; channel,op', _("""Determines the capabilities required (if any) to make any topic changes, - (everything except for read-only operations). Use 'channel,capab' for + (everything except for read-only operations). Use 'channel,capab' for channel-level capabilities. - Note that absence of an explicit anticapability means user has + Note that absence of an explicit anticapability means user has capability."""))) diff --git a/plugins/Topic/plugin.py b/plugins/Topic/plugin.py index 0e2d299f0..221919b6b 100644 --- a/plugins/Topic/plugin.py +++ b/plugins/Topic/plugin.py @@ -46,6 +46,7 @@ import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Topic') +import supybot.ircdb as ircdb def canChangeTopic(irc, msg, args, state): assert not state.channel @@ -207,7 +208,6 @@ class Topic(callbacks.Plugin): irc.queueMsg(ircmsgs.topic(channel, newTopic)) irc.noReply() - @internationalizeDocstring def _checkManageCapabilities(self, irc, msg, channel): """Check if the user has any of the required capabilities to manage the channel topic. @@ -219,7 +219,7 @@ class Topic(callbacks.Plugin): manually anyway. """ c = irc.state.channels[channel] - if msg.nick in c.ops or msg.nick in c.halfops or 't' not in c.modes: + if msg.nick in c.ops: return True capabilities = self.registryValue('requireManageCapability') if capabilities: @@ -403,6 +403,9 @@ class Topic(callbacks.Plugin): index into the topics. is only necessary if the message isn't sent in the channel itself. """ + if not self._checkManageCapabilities(irc, msg, channel): + capabilities = self.registryValue('requireManageCapability') + irc.errorNoCapability(capabilities, Raise=True) topics = self._splitTopic(irc.state.getTopic(channel), channel) irc.reply(topics[number]) get = wrap(get, ['inChannel', 'topicNumber']) diff --git a/plugins/Topic/test.py b/plugins/Topic/test.py index 83adcfaee..bb41662da 100644 --- a/plugins/Topic/test.py +++ b/plugins/Topic/test.py @@ -70,18 +70,14 @@ class TopicTestCase(ChannelPluginTestCase): self.assertEqual(m.command, 'TOPIC') self.assertEqual(m.args[0], self.channel) self.assertEqual(m.args[1], 'foo (test) || bar (test)') - + def testManageCapabilities(self): try: - self.irc.feedMsg(ircmsgs.mode(self.channel, args=('+o', self.nick), - prefix=self.prefix)) - self.irc.feedMsg(ircmsgs.mode(self.channel, args=('+t'), - prefix=self.prefix)) world.testing = False origuser = self.prefix self.prefix = 'stuff!stuff@stuff' self.assertNotError('register nottester stuff', private=True) - + self.assertError('topic add foo') origconf = conf.supybot.plugins.Topic.requireManageCapability() conf.supybot.plugins.Topic.requireManageCapability.setValue('') @@ -90,7 +86,7 @@ class TopicTestCase(ChannelPluginTestCase): world.testing = True self.prefix = origuser conf.supybot.plugins.Topic.requireManageCapability.setValue(origconf) - + def testInsert(self): m = self.getMsg('topic add foo') self.assertEqual(m.args[1], 'foo (test)') From 4cd0d19be779387fdd9f5ea61127cf921194c677 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Sun, 11 Apr 2010 00:42:55 -0400 Subject: [PATCH 29/85] commit mtughan's bugfix for this bug: https://sourceforge.net/tracker/?func=detail&aid=2985241&group_id=58965&atid=489447 --- src/registry.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/registry.py b/src/registry.py index 2497b4390..3d3911810 100644 --- a/src/registry.py +++ b/src/registry.py @@ -311,8 +311,10 @@ class Value(Group): if setDefault: self.setValue(default) - def error(self): - if self.__doc__: + def error(self, message=None): + if message: + s = message + elif self.__doc__: s = self.__doc__ else: s = """%s has no docstring. If you're getting this message, @@ -543,10 +545,7 @@ class Regexp(Value): self.__parent.__init__(*args, **kwargs) def error(self, e): - s = 'Value must be a regexp of the form m/.../ or /.../. %s' % e - e = InvalidRegistryValue(s) - e.value = self - raise e + self.__parent.error('%s' % e) def set(self, s): try: From 2d718d31bb0d67b08992b025c28dba37fa810adc Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Sun, 11 Apr 2010 01:23:27 -0400 Subject: [PATCH 30/85] Fix factoids bug ,Factoids.showFactoidIfOnlyOneMatch feature is broken when used off-channel: https://sourceforge.net/tracker/?func=detail&aid=2965589&group_id=58965&atid=489447 --- plugins/Factoids/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index c09d0fb32..00ddee9cf 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -681,7 +681,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): irc.reply(_('No keys matched that query.')) elif len(results) == 1 and \ self.registryValue('showFactoidIfOnlyOneMatch', channel): - self.whatis(irc, msg, [results[0][0]]) + self.whatis(irc, msg, [channel, results[0][0]]) elif len(results) > 100: irc.reply(_('More than 100 keys matched that query; ' 'please narrow your query.')) From dae8dfd2e7c35756a2b11ac6d8ca865a0dad50db Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Sun, 11 Apr 2010 02:27:22 -0400 Subject: [PATCH 31/85] add replace function to format that takes varying-length strings to replace. add test for it add error test for format.translate for different length translate args. --- plugins/Format/plugin.py | 9 +++++++++ plugins/Format/test.py | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/plugins/Format/plugin.py b/plugins/Format/plugin.py index 5e3e4611a..0a00f38cb 100644 --- a/plugins/Format/plugin.py +++ b/plugins/Format/plugin.py @@ -99,6 +99,15 @@ class Format(callbacks.Plugin): translate = wrap(translate, ['something', 'something', 'text']) @internationalizeDocstring + def replace(self, irc, msg, args, bad, good, text): + """ + + Replaces all non-overlapping occurrences of + with in . + """ + irc.reply(text.replace(bad, good)) + replace = wrap(replace, ['something', 'something', 'text']) + def upper(self, irc, msg, args, text): """ diff --git a/plugins/Format/test.py b/plugins/Format/test.py index 3bbf14602..f891e0dd7 100644 --- a/plugins/Format/test.py +++ b/plugins/Format/test.py @@ -54,6 +54,10 @@ class FormatTestCase(PluginTestCase): def testTranslate(self): self.assertResponse('translate 123 456 1234567890', '4564567890') + self.assertError('translate 123 1234 123125151') + + def testReplace(self): + self.assertResponse('replace # %23 bla#foo', 'bla%23foo') def testUpper(self): self.assertResponse('upper foo', 'FOO') From e68704a0fc98a47dccbeae2bacb3bfcf4ad36b1c Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Sun, 11 Apr 2010 12:48:08 -0400 Subject: [PATCH 32/85] topic now checks for +t mode before denying topic changes, and also allows halfops in addition to ops. default required capabilities for topic changes (if topic is +t and user is not an op or halfop), are chan,op and chan,halfop thanks jemfinch for the suggestions. --- plugins/Topic/config.py | 2 +- plugins/Topic/plugin.py | 2 +- plugins/Topic/test.py | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/Topic/config.py b/plugins/Topic/config.py index 3ddfff93b..4a3712ed6 100644 --- a/plugins/Topic/config.py +++ b/plugins/Topic/config.py @@ -66,7 +66,7 @@ conf.registerChannelValue(Topic.undo, 'max', registry.NonNegativeInteger(10, _("""Determines the number of previous topics to keep around in case the undo command is called."""))) conf.registerChannelValue(Topic, 'requireManageCapability', - registry.String('admin; channel,op', _("""Determines the + registry.String('channel,op; channel,halfop', _("""Determines the capabilities required (if any) to make any topic changes, (everything except for read-only operations). Use 'channel,capab' for channel-level capabilities. diff --git a/plugins/Topic/plugin.py b/plugins/Topic/plugin.py index 221919b6b..0ef60cb31 100644 --- a/plugins/Topic/plugin.py +++ b/plugins/Topic/plugin.py @@ -219,7 +219,7 @@ class Topic(callbacks.Plugin): manually anyway. """ c = irc.state.channels[channel] - if msg.nick in c.ops: + if msg.nick in c.ops or msg.nick in c.halfops or 't' not in c.modes: return True capabilities = self.registryValue('requireManageCapability') if capabilities: diff --git a/plugins/Topic/test.py b/plugins/Topic/test.py index bb41662da..d6b7be816 100644 --- a/plugins/Topic/test.py +++ b/plugins/Topic/test.py @@ -73,6 +73,10 @@ class TopicTestCase(ChannelPluginTestCase): def testManageCapabilities(self): try: + self.irc.feedMsg(ircmsgs.mode(self.channel, args=('+o', self.nick), + prefix=self.prefix)) + self.irc.feedMsg(ircmsgs.mode(self.channel, args=('+t'), + prefix=self.prefix)) world.testing = False origuser = self.prefix self.prefix = 'stuff!stuff@stuff' From 1b0aa4836877ef3364f2df6c3f226e02583e5f2d Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Sun, 11 Apr 2010 16:25:07 -0400 Subject: [PATCH 33/85] make on-join telling for later configurable. --- plugins/Later/config.py | 3 +++ plugins/Later/plugin.py | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/Later/config.py b/plugins/Later/config.py index fd1e96a92..eb505a8d6 100644 --- a/plugins/Later/config.py +++ b/plugins/Later/config.py @@ -48,5 +48,8 @@ conf.registerGlobalValue(Later, 'maximum', conf.registerGlobalValue(Later, 'private', registry.Boolean(True, _("""Determines whether users will be notified in the first place in which they're seen, or in private."""))) +conf.registerGlobalValue(Later, 'tellOnJoin', + registry.Boolean(True, _("""Determines whether users will be notified upon + joining any channel the bot is in, or only upon sending a message."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/plugins/Later/plugin.py b/plugins/Later/plugin.py index 109cc65a1..79cb85a87 100644 --- a/plugins/Later/plugin.py +++ b/plugins/Later/plugin.py @@ -203,10 +203,11 @@ class Later(callbacks.Plugin): def _formatNote(self, when, whence, note): return _('Sent %s: <%s> %s') % (self._timestamp(when), whence, note) - doJoin = doPrivmsg + def doJoin(self, irc, msg): + if self.registryValue('tellOnJoin'): + self.doPrivmsg(irc, msg) Later = internationalizeDocstring(Later) - Class = Later # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: From e46948400dd20240835ebcbc2e5c0d1f2c1c2996 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Sun, 11 Apr 2010 16:40:59 -0400 Subject: [PATCH 34/85] take out getDb override from factoids, since i have changed it upstream, in plugins.__init__.py, to use proper sqlite3 syntax. --- plugins/Factoids/plugin.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index 00ddee9cf..407b6929c 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -42,13 +42,6 @@ import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Factoids') -#try: - #import sqlite3 as sqlite -#except ImportError: - #raise callbacks.Error, 'You need to have PySQLite installed to use this ' \ - #'plugin. Download it at ' \ - #'' - try: import sqlite3 except ImportError: @@ -57,10 +50,6 @@ except ImportError: import re from supybot.utils.seq import dameraulevenshtein -# these are needed cuz we are overriding getdb -import threading -import supybot.world as world - def getFactoid(irc, msg, args, state): assert not state.channel callConverter('channel', irc, msg, args, state) @@ -123,20 +112,6 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): db.commit() return db - # override this because sqlite3 doesn't have autocommit - # use isolation_level instead. - def getDb(self, channel): - """Use this to get a database for a specific channel.""" - currentThread = threading.currentThread() - if channel not in self.dbCache and currentThread == world.mainThread: - self.dbCache[channel] = self.makeDb(self.makeFilename(channel)) - if currentThread != world.mainThread: - db = self.makeDb(self.makeFilename(channel)) - else: - db = self.dbCache[channel] - db.isolation_level = None - return db - def getCommandHelp(self, command, simpleSyntax=None): method = self.getCommandMethod(command) if method.im_func.func_name == 'learn': From 261a646451dba3bbdbe5c9df9a899019839586c2 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Sun, 11 Apr 2010 17:01:31 -0400 Subject: [PATCH 35/85] in Later nick validation, use irc.isNick. now instead of forcing strictRfc to true, we are using the config. --- plugins/Later/plugin.py | 8 ++++---- plugins/Later/test.py | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/Later/plugin.py b/plugins/Later/plugin.py index 79cb85a87..434a33511 100644 --- a/plugins/Later/plugin.py +++ b/plugins/Later/plugin.py @@ -101,7 +101,7 @@ class Later(callbacks.Plugin): self.wildcards.append(nick) self._flushNotes() - def _validateNick(self, nick): + def _validateNick(self, irc, nick): """Validate nick according to the IRC RFC 2812 spec. Reference: http://tools.ietf.org/rfcmarkup?doc=2812#section-2.3.1 @@ -113,8 +113,8 @@ class Later(callbacks.Plugin): If nick incorrigibly invalid, return False, otherwise, return (possibly trimmed) nick. """ - if not ircutils.isNick(nick, strictRfc=True): - if not ircutils.isNick(nick[:-1], strictRfc=True): + if not irc.isNick(nick): + if not irc.isNick(nick[:-1]): return False else: return nick[:-1] @@ -131,7 +131,7 @@ class Later(callbacks.Plugin): if ircutils.strEqual(nick, irc.nick): irc.error(_('I can\'t send notes to myself.')) return - validnick = self._validateNick(nick) + validnick = self._validateNick(irc, nick) if validnick is False: irc.error('That is an invalid IRC nick. Please check your input.') return diff --git a/plugins/Later/test.py b/plugins/Later/test.py index d91070620..447f088fd 100644 --- a/plugins/Later/test.py +++ b/plugins/Later/test.py @@ -44,10 +44,13 @@ class LaterTestCase(PluginTestCase): self.assertRegexp('later notes', 'foo') def testNickValidation(self): + origconf = conf.supybot.protocols.irc.strictRfc() + conf.supybot.protocols.irc.strictRfc.setValue('True') self.assertError('later tell 1foo bar') self.assertError('later tell foo$moo zoob') self.assertNotError('later tell foo: baz') self.assertRegexp('later notes', 'foo\.') + conf.supybot.protocols.irc.strictRfc.setValue(origconf) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: From 8d45228642cadeae46256ddaebab1f66d2113014 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Sun, 11 Apr 2010 17:37:09 -0400 Subject: [PATCH 36/85] for google translate, error if destination language is 'auto'. --- plugins/Google/plugin.py | 3 +++ plugins/Google/test.py | 1 + 2 files changed, 4 insertions(+) diff --git a/plugins/Google/plugin.py b/plugins/Google/plugin.py index ff056e7ca..437791cde 100644 --- a/plugins/Google/plugin.py +++ b/plugins/Google/plugin.py @@ -284,6 +284,9 @@ class Google(callbacks.PluginRegexp): toLang = lang.normalize('lang_'+toLang)[5:] if fromLang == 'auto': fromLang = '' + if toLang == 'auto': + irc.error("Destination language cannot be 'auto'.") + return opts['langpair'] = '%s|%s' % (fromLang, toLang) fd = utils.web.getUrlFd('%s?%s' % (self._gtranslateUrl, urllib.urlencode(opts)), diff --git a/plugins/Google/test.py b/plugins/Google/test.py index 3ef6eca81..e3c461aeb 100644 --- a/plugins/Google/test.py +++ b/plugins/Google/test.py @@ -62,6 +62,7 @@ class GoogleTestCase(ChannelPluginTestCase): def testTranslate(self): self.assertRegexp('translate en es hello world', 'mundo') self.assertRegexp('translate auto en ciao', 'Italian.*hello') + self.assertError('translate en to auto stuff') def testCalcDoesNotHaveExtraSpaces(self): self.assertNotRegexp('google calc 1000^2', r'\s+,\s+') From bf6f0e2d18ad053d1375fab446de3d039e399beb Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Sun, 11 Apr 2010 18:27:39 -0400 Subject: [PATCH 37/85] make google translate return detailed google error, instead of the unhelpful "we broke google". --- plugins/Google/plugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/Google/plugin.py b/plugins/Google/plugin.py index 437791cde..8c838185e 100644 --- a/plugins/Google/plugin.py +++ b/plugins/Google/plugin.py @@ -294,7 +294,8 @@ class Google(callbacks.PluginRegexp): json = simplejson.load(fd) fd.close() if json['responseStatus'] != 200: - raise callbacks.Error, 'We broke The Google!' + raise callbacks.Error, 'Google says: Response Status %s: %s.' % \ + (json['responseStatus'], json['responseDetails'],) if fromLang != '': irc.reply(json['responseData']['translatedText'].encode('utf-8')) else: From e939cfd124c91b83fb7f9323881fb3da8cbc412b Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Fri, 16 Apr 2010 16:06:00 -0400 Subject: [PATCH 38/85] update Karma plugin and test code to use sqlite3. This involved also updating src/conf.py to look for sqlite3 and add it to supybot.conf.databases list, since karma uses the plugins.DB() constructor for its database, which checks the available databases list. --- plugins/Karma/plugin.py | 64 ++++---- plugins/Karma/test.py | 321 ++++++++++++++++++++-------------------- src/conf.py | 4 + 3 files changed, 198 insertions(+), 191 deletions(-) diff --git a/plugins/Karma/plugin.py b/plugins/Karma/plugin.py index 606ea57c5..dd2b4e0a6 100644 --- a/plugins/Karma/plugin.py +++ b/plugins/Karma/plugin.py @@ -41,6 +41,11 @@ import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Karma') +try: + import sqlite3 +except ImportError: + from pysqlite2 import dbapi2 as sqlite3 # for python2.4 + class SqliteKarmaDB(object): def __init__(self, filename): self.dbs = ircutils.IrcDict() @@ -51,19 +56,16 @@ class SqliteKarmaDB(object): db.close() def _getDb(self, channel): - try: - import sqlite - except ImportError: - raise callbacks.Error, 'You need to have PySQLite installed to ' \ - 'use Karma. Download it at ' \ - '' filename = plugins.makeChannelFilename(self.filename, channel) if filename in self.dbs: return self.dbs[filename] if os.path.exists(filename): - self.dbs[filename] = sqlite.connect(filename) - return self.dbs[filename] - db = sqlite.connect(filename) + db = sqlite3.connect(filename) + db.text_factory = str + self.dbs[filename] = db + return db + db = sqlite3.connect(filename) + db.text_factory = str self.dbs[filename] = db cursor = db.cursor() cursor.execute("""CREATE TABLE karma ( @@ -84,20 +86,21 @@ class SqliteKarmaDB(object): thing = thing.lower() cursor = db.cursor() cursor.execute("""SELECT added, subtracted FROM karma - WHERE normalized=%s""", thing) - if cursor.rowcount == 0: + WHERE normalized=?""", (thing,)) + results = cursor.fetchall() + if len(results) == 0: return None else: - return map(int, cursor.fetchone()) + return map(int, results[0]) def gets(self, channel, things): db = self._getDb(channel) cursor = db.cursor() normalizedThings = dict(zip(map(lambda s: s.lower(), things), things)) - criteria = ' OR '.join(['normalized=%s'] * len(normalizedThings)) + criteria = ' OR '.join(['normalized=?'] * len(normalizedThings)) sql = """SELECT name, added-subtracted FROM karma WHERE %s ORDER BY added-subtracted DESC""" % criteria - cursor.execute(sql, *normalizedThings) + cursor.execute(sql, normalizedThings.keys()) L = [(name, int(karma)) for (name, karma) in cursor.fetchall()] for (name, _) in L: del normalizedThings[name.lower()] @@ -109,26 +112,27 @@ class SqliteKarmaDB(object): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT name, added-subtracted FROM karma - ORDER BY added-subtracted DESC LIMIT %s""", limit) + ORDER BY added-subtracted DESC LIMIT ?""", (limit,)) return [(t[0], int(t[1])) for t in cursor.fetchall()] def bottom(self, channel, limit): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT name, added-subtracted FROM karma - ORDER BY added-subtracted ASC LIMIT %s""", limit) + ORDER BY added-subtracted ASC LIMIT ?""", (limit,)) return [(t[0], int(t[1])) for t in cursor.fetchall()] def rank(self, channel, thing): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT added-subtracted FROM karma - WHERE name=%s""", thing) - if cursor.rowcount == 0: + WHERE name=?""", (thing,)) + results = cursor.fetchall() + if len(results) == 0: return None - karma = int(cursor.fetchone()[0]) + karma = int(results[0][0]) cursor.execute("""SELECT COUNT(*) FROM karma - WHERE added-subtracted > %s""", karma) + WHERE added-subtracted > ?""", (karma,)) rank = int(cursor.fetchone()[0]) return rank+1 @@ -142,20 +146,20 @@ class SqliteKarmaDB(object): db = self._getDb(channel) cursor = db.cursor() normalized = name.lower() - cursor.execute("""INSERT INTO karma VALUES (NULL, %s, %s, 0, 0)""", - name, normalized) + cursor.execute("""INSERT INTO karma VALUES (NULL, ?, ?, 0, 0)""", + (name, normalized,)) cursor.execute("""UPDATE karma SET added=added+1 - WHERE normalized=%s""", normalized) + WHERE normalized=?""", (normalized,)) db.commit() def decrement(self, channel, name): db = self._getDb(channel) cursor = db.cursor() normalized = name.lower() - cursor.execute("""INSERT INTO karma VALUES (NULL, %s, %s, 0, 0)""", - name, normalized) + cursor.execute("""INSERT INTO karma VALUES (NULL, ?, ?, 0, 0)""", + (name, normalized,)) cursor.execute("""UPDATE karma SET subtracted=subtracted+1 - WHERE normalized=%s""", normalized) + WHERE normalized=?""", (normalized,)) db.commit() def most(self, channel, kind, limit): @@ -179,7 +183,7 @@ class SqliteKarmaDB(object): cursor = db.cursor() normalized = name.lower() cursor.execute("""UPDATE karma SET subtracted=0, added=0 - WHERE normalized=%s""", normalized) + WHERE normalized=?""", (normalized,)) db.commit() def dump(self, channel, filename): @@ -203,13 +207,13 @@ class SqliteKarmaDB(object): for (name, added, subtracted) in reader: normalized = name.lower() cursor.execute("""INSERT INTO karma - VALUES (NULL, %s, %s, %s, %s)""", - name, normalized, added, subtracted) + VALUES (NULL, ?, ?, ?, ?)""", + (name, normalized, added, subtracted,)) db.commit() fd.close() KarmaDB = plugins.DB('Karma', - {'sqlite': SqliteKarmaDB}) + {'sqlite3': SqliteKarmaDB}) class Karma(callbacks.Plugin): callBefore = ('Factoids', 'MoobotFactoids', 'Infobot') diff --git a/plugins/Karma/test.py b/plugins/Karma/test.py index 526dda225..7dbfbdf2c 100644 --- a/plugins/Karma/test.py +++ b/plugins/Karma/test.py @@ -30,182 +30,181 @@ from supybot.test import * try: - import sqlite + import sqlite3 except ImportError: - sqlite = None + from pysqlite2 import dbapi2 as sqlite3 # for python2.4 -if sqlite is not None: - class KarmaTestCase(ChannelPluginTestCase): - plugins = ('Karma',) - def testKarma(self): - self.assertError('karma') - self.assertRegexp('karma foobar', 'neutral karma') - try: - conf.replyWhenNotCommand = True - self.assertNoResponse('foobar++', 2) - finally: - conf.replyWhenNotCommand = False - self.assertRegexp('karma foobar', 'increased 1.*total.*1') - self.assertRegexp('karma FOOBAR', 'increased 1.*total.*1') - self.assertNoResponse('foobar--', 2) - self.assertRegexp('karma foobar', 'decreased 1.*total.*0') - self.assertRegexp('karma FOOBAR', 'decreased 1.*total.*0') - self.assertNoResponse('FOO++', 2) - self.assertNoResponse('BAR--', 2) - self.assertRegexp('karma foo bar foobar', '.*foo.*foobar.*bar.*') - self.assertRegexp('karma FOO BAR FOOBAR', '.*foo.*foobar.*bar.*') - self.assertRegexp('karma FOO BAR FOOBAR', - '.*FOO.*foobar.*BAR.*', flags=0) - self.assertRegexp('karma foo bar foobar asdfjkl', 'asdfjkl') - # Test case-insensitive - self.assertNoResponse('MOO++', 2) - self.assertRegexp('karma moo', - 'Karma for [\'"]moo[\'"].*increased 1.*total.*1') - self.assertRegexp('karma MoO', - 'Karma for [\'"]MoO[\'"].*increased 1.*total.*1') +class KarmaTestCase(ChannelPluginTestCase): + plugins = ('Karma',) + def testKarma(self): + self.assertError('karma') + self.assertRegexp('karma foobar', 'neutral karma') + try: + conf.replyWhenNotCommand = True + self.assertNoResponse('foobar++', 2) + finally: + conf.replyWhenNotCommand = False + self.assertRegexp('karma foobar', 'increased 1.*total.*1') + self.assertRegexp('karma FOOBAR', 'increased 1.*total.*1') + self.assertNoResponse('foobar--', 2) + self.assertRegexp('karma foobar', 'decreased 1.*total.*0') + self.assertRegexp('karma FOOBAR', 'decreased 1.*total.*0') + self.assertNoResponse('FOO++', 2) + self.assertNoResponse('BAR--', 2) + self.assertRegexp('karma foo bar foobar', '.*foo.*foobar.*bar.*') + self.assertRegexp('karma FOO BAR FOOBAR', '.*foo.*foobar.*bar.*') + self.assertRegexp('karma FOO BAR FOOBAR', + '.*FOO.*foobar.*BAR.*', flags=0) + self.assertRegexp('karma foo bar foobar asdfjkl', 'asdfjkl') + # Test case-insensitive + self.assertNoResponse('MOO++', 2) + self.assertRegexp('karma moo', + 'Karma for [\'"]moo[\'"].*increased 1.*total.*1') + self.assertRegexp('karma MoO', + 'Karma for [\'"]MoO[\'"].*increased 1.*total.*1') - def testKarmaRankingDisplayConfigurable(self): - try: - orig = conf.supybot.plugins.Karma.response() - conf.supybot.plugins.Karma.response.setValue(True) - original = conf.supybot.plugins.Karma.rankingDisplay() - self.assertNotError('foo++') - self.assertNotError('foo++') - self.assertNotError('foo++') - self.assertNotError('foo++') - self.assertNotError('bar++') - self.assertNotError('bar++') - self.assertNotError('bar++') - self.assertNotError('baz++') - self.assertNotError('baz++') - self.assertNotError('quux++') - self.assertNotError('xuuq--') - self.assertNotError('zab--') - self.assertNotError('zab--') - self.assertNotError('rab--') - self.assertNotError('rab--') - self.assertNotError('rab--') - self.assertNotError('oof--') - self.assertNotError('oof--') - self.assertNotError('oof--') - self.assertNotError('oof--') - self.assertRegexp('karma', 'foo.*bar.*baz.*oof.*rab.*zab') - conf.supybot.plugins.Karma.rankingDisplay.setValue(4) - self.assertRegexp('karma', 'foo.*bar.*baz.*quux') - finally: - conf.supybot.plugins.Karma.response.setValue(orig) - conf.supybot.plugins.Karma.rankingDisplay.setValue(original) + def testKarmaRankingDisplayConfigurable(self): + try: + orig = conf.supybot.plugins.Karma.response() + conf.supybot.plugins.Karma.response.setValue(True) + original = conf.supybot.plugins.Karma.rankingDisplay() + self.assertNotError('foo++') + self.assertNotError('foo++') + self.assertNotError('foo++') + self.assertNotError('foo++') + self.assertNotError('bar++') + self.assertNotError('bar++') + self.assertNotError('bar++') + self.assertNotError('baz++') + self.assertNotError('baz++') + self.assertNotError('quux++') + self.assertNotError('xuuq--') + self.assertNotError('zab--') + self.assertNotError('zab--') + self.assertNotError('rab--') + self.assertNotError('rab--') + self.assertNotError('rab--') + self.assertNotError('oof--') + self.assertNotError('oof--') + self.assertNotError('oof--') + self.assertNotError('oof--') + self.assertRegexp('karma', 'foo.*bar.*baz.*oof.*rab.*zab') + conf.supybot.plugins.Karma.rankingDisplay.setValue(4) + self.assertRegexp('karma', 'foo.*bar.*baz.*quux') + finally: + conf.supybot.plugins.Karma.response.setValue(orig) + conf.supybot.plugins.Karma.rankingDisplay.setValue(original) - def testMost(self): - self.assertError('most increased') - self.assertError('most decreased') - self.assertError('most active') - self.assertHelp('most aldsfkj') - self.assertNoResponse('foo++', 1) - self.assertNoResponse('foo++', 1) - self.assertNoResponse('bar++', 1) - self.assertNoResponse('bar--', 1) - self.assertNoResponse('bar--', 1) - self.assertRegexp('karma most active', 'bar.*foo') - self.assertRegexp('karma most increased', 'foo.*bar') - self.assertRegexp('karma most decreased', 'bar.*foo') - self.assertNoResponse('foo--', 1) - self.assertNoResponse('foo--', 1) - self.assertNoResponse('foo--', 1) - self.assertNoResponse('foo--', 1) - self.assertRegexp('karma most active', 'foo.*bar') - self.assertRegexp('karma most increased', 'foo.*bar') - self.assertRegexp('karma most decreased', 'foo.*bar') + def testMost(self): + self.assertError('most increased') + self.assertError('most decreased') + self.assertError('most active') + self.assertHelp('most aldsfkj') + self.assertNoResponse('foo++', 1) + self.assertNoResponse('foo++', 1) + self.assertNoResponse('bar++', 1) + self.assertNoResponse('bar--', 1) + self.assertNoResponse('bar--', 1) + self.assertRegexp('karma most active', 'bar.*foo') + self.assertRegexp('karma most increased', 'foo.*bar') + self.assertRegexp('karma most decreased', 'bar.*foo') + self.assertNoResponse('foo--', 1) + self.assertNoResponse('foo--', 1) + self.assertNoResponse('foo--', 1) + self.assertNoResponse('foo--', 1) + self.assertRegexp('karma most active', 'foo.*bar') + self.assertRegexp('karma most increased', 'foo.*bar') + self.assertRegexp('karma most decreased', 'foo.*bar') - def testSimpleOutput(self): - try: - orig = conf.supybot.plugins.Karma.simpleOutput() - conf.supybot.plugins.Karma.simpleOutput.setValue(True) - self.assertNoResponse('foo++', 2) - self.assertResponse('karma foo', 'foo: 1') - self.assertNoResponse('bar--', 2) - self.assertResponse('karma bar', 'bar: -1') - finally: - conf.supybot.plugins.Karma.simpleOutput.setValue(orig) - - def testSelfRating(self): - nick = self.nick - try: - orig = conf.supybot.plugins.Karma.allowSelfRating() - conf.supybot.plugins.Karma.allowSelfRating.setValue(False) - self.assertError('%s++' % nick) - self.assertResponse('karma %s' % nick, - '%s has neutral karma.' % nick) - conf.supybot.plugins.Karma.allowSelfRating.setValue(True) - self.assertNoResponse('%s++' % nick, 2) - self.assertRegexp('karma %s' % nick, - 'Karma for [\'"]%s[\'"].*increased 1.*total.*1' % nick) - finally: - conf.supybot.plugins.Karma.allowSelfRating.setValue(orig) - - def testKarmaOutputConfigurable(self): + def testSimpleOutput(self): + try: + orig = conf.supybot.plugins.Karma.simpleOutput() + conf.supybot.plugins.Karma.simpleOutput.setValue(True) self.assertNoResponse('foo++', 2) - try: - orig = conf.supybot.plugins.Karma.response() - conf.supybot.plugins.Karma.response.setValue(True) - self.assertNotError('foo++') - finally: - conf.supybot.plugins.Karma.response.setValue(orig) + self.assertResponse('karma foo', 'foo: 1') + self.assertNoResponse('bar--', 2) + self.assertResponse('karma bar', 'bar: -1') + finally: + conf.supybot.plugins.Karma.simpleOutput.setValue(orig) - def testKarmaMostDisplayConfigurable(self): - self.assertNoResponse('foo++', 1) - self.assertNoResponse('foo++', 1) - self.assertNoResponse('bar++', 1) - self.assertNoResponse('bar--', 1) - self.assertNoResponse('bar--', 1) - self.assertNoResponse('foo--', 1) - self.assertNoResponse('foo--', 1) - self.assertNoResponse('foo--', 1) - self.assertNoResponse('foo--', 1) - try: - orig = conf.supybot.plugins.Karma.mostDisplay() - conf.supybot.plugins.Karma.mostDisplay.setValue(1) - self.assertRegexp('karma most active', '(?!bar)') - conf.supybot.plugins.Karma.mostDisplay.setValue(25) - self.assertRegexp('karma most active', 'bar') - finally: - conf.supybot.plugins.Karma.mostDisplay.setValue(orig) + def testSelfRating(self): + nick = self.nick + try: + orig = conf.supybot.plugins.Karma.allowSelfRating() + conf.supybot.plugins.Karma.allowSelfRating.setValue(False) + self.assertError('%s++' % nick) + self.assertResponse('karma %s' % nick, + '%s has neutral karma.' % nick) + conf.supybot.plugins.Karma.allowSelfRating.setValue(True) + self.assertNoResponse('%s++' % nick, 2) + self.assertRegexp('karma %s' % nick, + 'Karma for [\'"]%s[\'"].*increased 1.*total.*1' % nick) + finally: + conf.supybot.plugins.Karma.allowSelfRating.setValue(orig) + + def testKarmaOutputConfigurable(self): + self.assertNoResponse('foo++', 2) + try: + orig = conf.supybot.plugins.Karma.response() + conf.supybot.plugins.Karma.response.setValue(True) + self.assertNotError('foo++') + finally: + conf.supybot.plugins.Karma.response.setValue(orig) + + def testKarmaMostDisplayConfigurable(self): + self.assertNoResponse('foo++', 1) + self.assertNoResponse('foo++', 1) + self.assertNoResponse('bar++', 1) + self.assertNoResponse('bar--', 1) + self.assertNoResponse('bar--', 1) + self.assertNoResponse('foo--', 1) + self.assertNoResponse('foo--', 1) + self.assertNoResponse('foo--', 1) + self.assertNoResponse('foo--', 1) + try: + orig = conf.supybot.plugins.Karma.mostDisplay() + conf.supybot.plugins.Karma.mostDisplay.setValue(1) + self.assertRegexp('karma most active', '(?!bar)') + conf.supybot.plugins.Karma.mostDisplay.setValue(25) + self.assertRegexp('karma most active', 'bar') + finally: + conf.supybot.plugins.Karma.mostDisplay.setValue(orig) - def testIncreaseKarmaWithNickNotCallingInvalidCommand(self): - self.assertSnarfNoResponse('%s: foo++' % self.irc.nick, 3) + def testIncreaseKarmaWithNickNotCallingInvalidCommand(self): + self.assertSnarfNoResponse('%s: foo++' % self.irc.nick, 3) - def testClear(self): - self.assertNoResponse('foo++', 1) - self.assertRegexp('karma foo', '1') - self.assertNotError('karma clear foo') - self.assertRegexp('karma foo', '0') - self.assertNotRegexp('karma foo', '1') + def testClear(self): + self.assertNoResponse('foo++', 1) + self.assertRegexp('karma foo', '1') + self.assertNotError('karma clear foo') + self.assertRegexp('karma foo', '0') + self.assertNotRegexp('karma foo', '1') # def testNoKarmaDunno(self): # self.assertNotError('load Infobot') # self.assertNoResponse('foo++') - def testMultiWordKarma(self): - self.assertNoResponse('(foo bar)++', 1) - self.assertRegexp('karma "foo bar"', '1') + def testMultiWordKarma(self): + self.assertNoResponse('(foo bar)++', 1) + self.assertRegexp('karma "foo bar"', '1') - def testUnaddressedKarma(self): - karma = conf.supybot.plugins.Karma - resp = karma.response() - unaddressed = karma.allowUnaddressedKarma() - try: - karma.response.setValue(True) - karma.allowUnaddressedKarma.setValue(True) - for m in ('++', '--'): - self.assertRegexp('foo%s' % m, 'operation') - self.assertSnarfRegexp('foo%s' % m, 'operation') - #self.assertNoResponse('foo bar%s' % m) - #self.assertSnarfNoResponse('foo bar%s' % m) - self.assertRegexp('(foo bar)%s' % m, 'operation') - self.assertSnarfRegexp('(foo bar)%s' % m, 'operation') - finally: - karma.response.setValue(resp) - karma.allowUnaddressedKarma.setValue(unaddressed) + def testUnaddressedKarma(self): + karma = conf.supybot.plugins.Karma + resp = karma.response() + unaddressed = karma.allowUnaddressedKarma() + try: + karma.response.setValue(True) + karma.allowUnaddressedKarma.setValue(True) + for m in ('++', '--'): + self.assertRegexp('foo%s' % m, 'operation') + self.assertSnarfRegexp('foo%s' % m, 'operation') + #self.assertNoResponse('foo bar%s' % m) + #self.assertSnarfNoResponse('foo bar%s' % m) + self.assertRegexp('(foo bar)%s' % m, 'operation') + self.assertSnarfRegexp('(foo bar)%s' % m, 'operation') + finally: + karma.response.setValue(resp) + karma.allowUnaddressedKarma.setValue(unaddressed) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/src/conf.py b/src/conf.py index fee98b7d8..a9e01b892 100644 --- a/src/conf.py +++ b/src/conf.py @@ -775,6 +775,10 @@ class Databases(registry.SpaceSeparatedListOfStrings): v = ['anydbm', 'cdb', 'flat', 'pickle'] if 'sqlite' in sys.modules: v.insert(0, 'sqlite') + if 'sqlite3' in sys.modules: + v.insert(0, 'sqlite3') + if 'pysqlite2' in sys.modules: # for python 2.4 + v.insert(0, 'sqlite3') return v def serialize(self): From d433bfc8f0eee8a3d4814394230be14e4edb1a31 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Sun, 18 Apr 2010 03:33:10 -0400 Subject: [PATCH 39/85] for Factoids: make approximate fuzzy key searches also when invoking whatis directly. also add tests. while there, remove useless config setting for replyinvalidcommand testing, since it is true by default. --- plugins/Factoids/config.py | 10 +++++----- plugins/Factoids/plugin.py | 23 +++++++++++++++-------- plugins/Factoids/test.py | 32 ++++++++++++++++---------------- 3 files changed, 36 insertions(+), 29 deletions(-) diff --git a/plugins/Factoids/config.py b/plugins/Factoids/config.py index d6be96f79..fbf5c8421 100644 --- a/plugins/Factoids/config.py +++ b/plugins/Factoids/config.py @@ -60,11 +60,11 @@ conf.registerChannelValue(Factoids, 'replyWhenInvalidCommand', registry.Boolean(True, _("""Determines whether the bot will reply to invalid commands by searching for a factoid; basically making the whatis unnecessary when you want all factoids for a given key."""))) -conf.registerChannelValue(Factoids, 'replyWhenInvalidCommandSearchKeys', - registry.Boolean(True, _("""If replyWhenInvalidCommand is True, and you - supply a nonexistent factoid as a command, this setting make the bot try a - wildcard search for factoid keys, returning a list of matching keys, - before giving up with an invalid command error."""))) +conf.registerChannelValue(Factoids, 'replyApproximateSearchKeys', + registry.Boolean(True, _("""If you try to look up a nonexistent factoid, + this setting make the bot try to find some possible matching keys through + several approximate matching algorithms and return a list of matching keys, + before giving up."""))) conf.registerChannelValue(Factoids, 'format', FactoidFormat(_('$key could be $value.'), _("""Determines the format of the response given when a factoid's value is requested. All the standard diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index 407b6929c..e967224c1 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -194,7 +194,6 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): ORDER BY factoids.id LIMIT 20""", (key,)) return cursor.fetchall() - #return [t[0] for t in cursor.fetchall()] def _searchFactoid(self, channel, key): """Try to typo-match input to possible factoids. @@ -272,6 +271,16 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): elif error: irc.error(_('No factoid matches that key.')) + def _replyApproximateFactoids(self, irc, msg, channel, key, error=True): + if self.registryValue('replyApproximateSearchKeys'): + factoids = self._searchFactoid(channel, key) + if factoids: + keylist = ["'%s'" % (fact,) for fact in factoids] + keylist = ', '.join(keylist) + irc.reply("I do not know about '%s', but I do know about these similar topics: %s" % (key, keylist)) + elif error: + irc.error('No factoid matches that key.') + def invalidCommand(self, irc, msg, tokens): if irc.isChannel(msg.args[0]): channel = msg.args[0] @@ -281,12 +290,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): if factoids: self._replyFactoids(irc, msg, key, channel, factoids, error=False) else: - if self.registryValue('replyWhenInvalidCommandSearchKeys'): - factoids = self._searchFactoid(channel, key) - if factoids: - keylist = ["'%s'" % (fact,) for fact in factoids] - keylist = ', '.join(keylist) - irc.reply("I do not know about '%s', but I do know about these similar topics: %s" % (key, keylist)) + self._replyApproximateFactoids(irc, msg, channel, key, error=False) @internationalizeDocstring def whatis(self, irc, msg, args, channel, words): @@ -304,7 +308,10 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): irc.errorInvalid(_('key id')) key = ' '.join(words) factoids = self._lookupFactoid(channel, key) - self._replyFactoids(irc, msg, key, channel, factoids, number) + if factoids: + self._replyFactoids(irc, msg, key, channel, factoids, number) + else: + self._replyApproximateFactoids(irc, msg, channel, key) whatis = wrap(whatis, ['channel', many('something')]) @internationalizeDocstring diff --git a/plugins/Factoids/test.py b/plugins/Factoids/test.py index 1dddfb19c..22595c329 100644 --- a/plugins/Factoids/test.py +++ b/plugins/Factoids/test.py @@ -157,22 +157,22 @@ class FactoidsTestCase(ChannelPluginTestCase): showFactoidIfOnlyOneMatch.setValue(orig) def testInvalidCommand(self): - orig = conf.supybot.plugins.Factoids.replyWhenInvalidCommand() - try: - conf.supybot.plugins.Factoids.\ - replyWhenInvalidCommand.setValue(True) - self.assertNotError('learn foo as bar') - self.assertRegexp('foo', 'bar') - self.assertNotError('learn mooz as cowz') - self.assertRegexp('moo', 'mooz') - self.assertRegexp('mzo', 'mooz') - self.assertRegexp('moz', 'mooz') - self.assertNotError('learn moped as pretty fast') - self.assertRegexp('moe', 'mooz.*moped') - self.assertError('nosuchthing') - finally: - conf.supybot.plugins.Factoids.\ - replyWhenInvalidCommand.setValue(orig) + self.assertNotError('learn foo as bar') + self.assertRegexp('foo', 'bar') + self.assertNotError('learn mooz as cowz') + self.assertRegexp('moo', 'mooz') + self.assertRegexp('mzo', 'mooz') + self.assertRegexp('moz', 'mooz') + self.assertNotError('learn moped as pretty fast') + self.assertRegexp('moe', 'mooz.*moped') + self.assertError('nosuchthing') + + def testWhatis(self): + self.assertNotError('learn foo as bar') + self.assertRegexp('whatis foo', 'bar') + self.assertRegexp('whatis foob', 'foo') + self.assertNotError('learn foob as barb') + self.assertRegexp('whatis foom', 'foo.*foob') def testAlias(self): self.assertNotError('learn foo as bar') From 73e822ca567d20a88c7c53256d2c46aaab5236cb Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Mon, 19 Apr 2010 00:53:40 -0400 Subject: [PATCH 40/85] Undo mtughan's bugfix from 7f9a1130605fb6b36967f062d3cbcdb73aff8df6, so i can merge jamessan's fix ef8bd817e8b62ede76aa7501a9a8d69af7408efc --- src/registry.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/registry.py b/src/registry.py index 3d3911810..19b533085 100644 --- a/src/registry.py +++ b/src/registry.py @@ -311,10 +311,8 @@ class Value(Group): if setDefault: self.setValue(default) - def error(self, message=None): - if message: - s = message - elif self.__doc__: + def error(self): + if self.__doc__: s = self.__doc__ else: s = """%s has no docstring. If you're getting this message, @@ -545,7 +543,7 @@ class Regexp(Value): self.__parent.__init__(*args, **kwargs) def error(self, e): - self.__parent.error('%s' % e) + self.__parent.error('Value must be a regexp of the form %s' % e) def set(self, s): try: From 4af9d8735ffaffea2503b6133114d9a1e489f864 Mon Sep 17 00:00:00 2001 From: James Vega Date: Sun, 11 Apr 2010 10:15:39 -0400 Subject: [PATCH 41/85] Make registry.Regexp.error mimic registry.Value.error Regexp.error can't directly call Value.error because it's providing extra information, so it needs to build the InvalidRegistryValue exception itself and raise it. Closes: Sf#2985241 Signed-off-by: James Vega (cherry picked from commit ef8bd817e8b62ede76aa7501a9a8d69af7408efc) Signed-off-by: Daniel Folkinshteyn --- src/registry.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/registry.py b/src/registry.py index 19b533085..2497b4390 100644 --- a/src/registry.py +++ b/src/registry.py @@ -543,7 +543,10 @@ class Regexp(Value): self.__parent.__init__(*args, **kwargs) def error(self, e): - self.__parent.error('Value must be a regexp of the form %s' % e) + s = 'Value must be a regexp of the form m/.../ or /.../. %s' % e + e = InvalidRegistryValue(s) + e.value = self + raise e def set(self, s): try: From 92fc1e308f4e338d589758e3ce946edb9e6d49bf Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Wed, 21 Apr 2010 01:24:13 -0400 Subject: [PATCH 42/85] update quotegrabs to sqlite3 --- plugins/QuoteGrabs/plugin.py | 112 ++++++++++++++++++++--------------- 1 file changed, 64 insertions(+), 48 deletions(-) diff --git a/plugins/QuoteGrabs/plugin.py b/plugins/QuoteGrabs/plugin.py index 9e4f6f77f..d63659966 100644 --- a/plugins/QuoteGrabs/plugin.py +++ b/plugins/QuoteGrabs/plugin.py @@ -43,6 +43,15 @@ import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('QuoteGrabs') +try: + import sqlite3 +except ImportError: + from pysqlite2 import dbapi2 as sqlite3 # for python2.4 + +import traceback + +#sqlite3.register_converter('bool', bool) + class QuoteGrabsRecord(dbi.Record): __fields__ = [ 'by', @@ -67,29 +76,27 @@ class SqliteQuoteGrabsDB(object): db.close() def _getDb(self, channel): - try: - import sqlite - except ImportError: - raise callbacks.Error, 'You need to have PySQLite installed to ' \ - 'use QuoteGrabs. Download it at ' \ - '' filename = plugins.makeChannelFilename(self.filename, channel) def p(s1, s2): - return int(ircutils.nickEqual(s1, s2)) + # text_factory seems to only apply as an output adapter, + # so doesn't apply to created functions; so we use str() + return ircutils.nickEqual(str(s1), str(s2)) if filename in self.dbs: return self.dbs[filename] if os.path.exists(filename): - self.dbs[filename] = sqlite.connect(filename, - converters={'bool': bool}) - self.dbs[filename].create_function('nickeq', 2, p) - return self.dbs[filename] - db = sqlite.connect(filename, converters={'bool': bool}) + db = sqlite3.connect(filename) + db.text_factory = str + db.create_function('nickeq', 2, p) + self.dbs[filename] = db + return db + db = sqlite3.connect(filename) + db.text_factory = str + db.create_function('nickeq', 2, p) self.dbs[filename] = db - self.dbs[filename].create_function('nickeq', 2, p) cursor = db.cursor() cursor.execute("""CREATE TABLE quotegrabs ( id INTEGER PRIMARY KEY, - nick TEXT, + nick BLOB, hostmask TEXT, added_by TEXT, added_at TIMESTAMP, @@ -102,10 +109,11 @@ class SqliteQuoteGrabsDB(object): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT id, nick, quote, hostmask, added_at, added_by - FROM quotegrabs WHERE id = %s""", id) - if cursor.rowcount == 0: + FROM quotegrabs WHERE id = ?""", (id,)) + results = cursor.fetchall() + if len(results) == 0: raise dbi.NoRecordError - (id, by, quote, hostmask, at, grabber) = cursor.fetchone() + (id, by, quote, hostmask, at, grabber) = results[0] return QuoteGrabsRecord(id, by=by, text=quote, hostmask=hostmask, at=int(at), grabber=grabber) @@ -114,46 +122,50 @@ class SqliteQuoteGrabsDB(object): cursor = db.cursor() if nick: cursor.execute("""SELECT quote FROM quotegrabs - WHERE nickeq(nick, %s) + WHERE nickeq(nick, ?) ORDER BY random() LIMIT 1""", - nick) + (nick,)) else: cursor.execute("""SELECT quote FROM quotegrabs ORDER BY random() LIMIT 1""") - if cursor.rowcount == 0: + results = cursor.fetchall() + if len(results) == 0: raise dbi.NoRecordError - return cursor.fetchone()[0] + return results[0][0] def list(self, channel, nick): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT id, quote FROM quotegrabs - WHERE nickeq(nick, %s) - ORDER BY id DESC""", nick) - if cursor.rowcount == 0: + WHERE nickeq(nick, ?) + ORDER BY id DESC""", (nick,)) + results = cursor.fetchall() + if len(results) == 0: raise dbi.NoRecordError return [QuoteGrabsRecord(id, text=quote) - for (id, quote) in cursor.fetchall()] + for (id, quote) in results] def getQuote(self, channel, nick): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT quote FROM quotegrabs - WHERE nickeq(nick, %s) - ORDER BY id DESC LIMIT 1""", nick) - if cursor.rowcount == 0: + WHERE nickeq(nick, ?) + ORDER BY id DESC LIMIT 1""", (nick,)) + results = cursor.fetchall() + if len(results) == 0: raise dbi.NoRecordError - return cursor.fetchone()[0] + return results[0][0] def select(self, channel, nick): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT added_at FROM quotegrabs - WHERE nickeq(nick, %s) - ORDER BY id DESC LIMIT 1""", nick) - if cursor.rowcount == 0: + WHERE nickeq(nick, ?) + ORDER BY id DESC LIMIT 1""", (nick,)) + results = cursor.fetchall() + if len(results) == 0: raise dbi.NoRecordError - return cursor.fetchone()[0] + return results[0][0] def add(self, channel, msg, by): db = self._getDb(channel) @@ -161,14 +173,15 @@ class SqliteQuoteGrabsDB(object): text = ircmsgs.prettyPrint(msg) # Check to see if the latest quotegrab is identical cursor.execute("""SELECT quote FROM quotegrabs - WHERE nick=%s - ORDER BY id DESC LIMIT 1""", msg.nick) - if cursor.rowcount != 0: - if text == cursor.fetchone()[0]: + WHERE nick=? + ORDER BY id DESC LIMIT 1""", (msg.nick,)) + results = cursor.fetchall() + if len(results) != 0: + if text == results[0][0]: return cursor.execute("""INSERT INTO quotegrabs - VALUES (NULL, %s, %s, %s, %s, %s)""", - msg.nick, msg.prefix, by, int(time.time()), text) + VALUES (NULL, ?, ?, ?, ?, ?)""", + (msg.nick, msg.prefix, by, int(time.time()), text,)) db.commit() def remove(self, channel, grab=None): @@ -179,14 +192,16 @@ class SqliteQuoteGrabsDB(object): # strictly unnecessary -- the DELETE operation would "succeed" # anyway, but it's silly to just keep saying 'OK' no matter what, # so... - cursor.execute("""SELECT * FROM quotegrabs WHERE id = %s""", grab) - if cursor.rowcount == 0: + cursor.execute("""SELECT * FROM quotegrabs WHERE id = ?""", (grab,)) + results = cursor.fetchall() + if len(results) == 0: raise dbi.NoRecordError - cursor.execute("""DELETE FROM quotegrabs WHERE id = %s""", grab) + cursor.execute("""DELETE FROM quotegrabs WHERE id = ?""", (grab,)) else: cursor.execute("""SELECT * FROM quotegrabs WHERE id = (SELECT MAX(id) FROM quotegrabs)""") - if cursor.rowcount == 0: + results = cursor.fetchall() + if len(results) == 0: raise dbi.NoRecordError cursor.execute("""DELETE FROM quotegrabs WHERE id = (SELECT MAX(id) FROM quotegrabs)""") @@ -197,14 +212,15 @@ class SqliteQuoteGrabsDB(object): cursor = db.cursor() text = '%' + text + '%' cursor.execute("""SELECT id, nick, quote FROM quotegrabs - WHERE quote LIKE %s - ORDER BY id DESC""", text) - if cursor.rowcount == 0: + WHERE quote LIKE ? + ORDER BY id DESC""", (text,)) + results = cursor.fetchall() + if len(results) == 0: raise dbi.NoRecordError return [QuoteGrabsRecord(id, text=quote, by=nick) - for (id, nick, quote) in cursor.fetchall()] + for (id, nick, quote) in results] -QuoteGrabsDB = plugins.DB('QuoteGrabs', {'sqlite': SqliteQuoteGrabsDB}) +QuoteGrabsDB = plugins.DB('QuoteGrabs', {'sqlite3': SqliteQuoteGrabsDB}) class QuoteGrabs(callbacks.Plugin): """Add the help for "@help QuoteGrabs" here.""" From 7505f839fc54c3ab765e1fd782f0c69a72fbef5b Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Wed, 21 Apr 2010 16:38:25 -0400 Subject: [PATCH 43/85] fix docstring for Plugin.plugin command so it actually says what the command will do. also add a Plugin.plugins command, which always returns a list of all plugins containing a command. add a test for it. --- plugins/Plugin/plugin.py | 38 +++++++++++++++++++++++++++++++++++++- plugins/Plugin/test.py | 8 ++++++-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/plugins/Plugin/plugin.py b/plugins/Plugin/plugin.py index a6f6e5f1a..105a0125f 100644 --- a/plugins/Plugin/plugin.py +++ b/plugins/Plugin/plugin.py @@ -72,7 +72,10 @@ class Plugin(callbacks.Plugin): def plugin(self, irc, msg, args, command): """ - Returns the plugin(s) that is in. + Returns the name of the plugin that would be used to call . + + If it is not uniquely determined, returns list of all plugins that + contain . """ (maxL, cbs) = irc.findCallbacksForArgs(command) L = [] @@ -94,7 +97,40 @@ class Plugin(callbacks.Plugin): irc.error(format(_('There is no command %q.'), command)) plugin = wrap(plugin, [many('something')]) + def _findCallbacks(self, irc, command): + command = map(callbacks.canonicalName, command) + plugin_list = [] + for cb in irc.callbacks: + if not hasattr(cb, 'getCommand'): + continue + commandlist = cb.getCommand(command) + if commandlist: + plugin_list.append(cb.name()) + return plugin_list + @internationalizeDocstring + def plugins(self, irc, msg, args, command): + """ + + Returns the names of all plugins that contain . + """ + L = self._findCallbacks(irc, command) + command = callbacks.formatCommand(command) + if L: + if irc.nested: + irc.reply(format('%L', L)) + else: + if len(L) > 1: + plugin = 'plugins' + else: + plugin = 'plugin' + irc.reply(format('The %q command is available in the %L %s.', + command, L, plugin)) + else: + irc.error(format('There is no command %q.', command)) + plugins = wrap(plugins, [many('something')]) + +>>>>>>> 3005752... fix docstring for Plugin.plugin command so it actually says what the command will do. def author(self, irc, msg, args, cb): """ diff --git a/plugins/Plugin/test.py b/plugins/Plugin/test.py index b5715adea..75bc52a96 100644 --- a/plugins/Plugin/test.py +++ b/plugins/Plugin/test.py @@ -30,11 +30,15 @@ from supybot.test import * class PluginTestCase(PluginTestCase): - plugins = ('Plugin', 'Utilities') + plugins = ('Plugin', 'Utilities', 'Admin', 'Format') def testPlugin(self): self.assertRegexp('plugin plugin', 'available.*Plugin plugin') self.assertResponse('echo [plugin plugin]', 'Plugin') - + + def testPlugins(self): + self.assertRegexp('plugins join', '(Format.*Admin|Admin.*Format)') + self.assertRegexp('plugins plugin', 'Plugin') + def testList(self): self.assertRegexp('plugin list', 'Plugin.*Utilities') From 7c3bc67c86582d7cd05b565e30d432a060f2dc73 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Sun, 25 Apr 2010 00:34:31 -0400 Subject: [PATCH 44/85] Add some sanity checks to RSS plugin: First: if an rss feed is called without the number of headlines argument, we now have a default number of items it will output. before, it tried to stuff the whole rss feed into the channel, which is quite floody, if more than one 'mores' is set, or if oneToOne is false. Second: when adding a new feed to announce, it again, tried to stuff the whole rss feed into the channel, which ran into the same floody conditions as above. now we have a default number of feeds to output when there's no cached history. --- plugins/RSS/config.py | 6 ++++++ plugins/RSS/plugin.py | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/plugins/RSS/config.py b/plugins/RSS/config.py index e4754375b..7029f5448 100644 --- a/plugins/RSS/config.py +++ b/plugins/RSS/config.py @@ -72,6 +72,12 @@ conf.registerChannelValue(RSS, 'showLinks', along with the title of the feed when the rss command is called. supybot.plugins.RSS.announce.showLinks affects whether links will be listed when a feed is automatically announced."""))) +conf.registerGlobalValue(RSS, 'defaultNumberOfHeadlines', + registry.PositiveInteger(1, """Indicates how many headlines an rss feed + will output by default, if no number is provided.""")) +conf.registerChannelValue(RSS, 'initialAnnounceHeadlines', + registry.PositiveInteger(5, """Indicates how many headlines an rss feed + will output when it is first added to announce for a channel.""")) conf.registerGroup(RSS, 'announce') conf.registerChannelValue(RSS.announce, 'showLinks', diff --git a/plugins/RSS/plugin.py b/plugins/RSS/plugin.py index eb0c7af11..e11e23dbb 100644 --- a/plugins/RSS/plugin.py +++ b/plugins/RSS/plugin.py @@ -185,6 +185,8 @@ class RSS(callbacks.Plugin): newheadlines = filter(None, newheadlines) # Removes Nones. if newheadlines: for channel in channels: + if len(oldheadlines) == 0: + newheadlines = newheadlines[:self.registryValue('initialAnnounceHeadlines', channel)] bold = self.registryValue('bold', channel) sep = self.registryValue('headlineSeparator', channel) prefix = self.registryValue('announcementPrefix', channel) @@ -405,6 +407,8 @@ class RSS(callbacks.Plugin): headlines = self.buildHeadlines(headlines, channel, 'showLinks') if n: headlines = headlines[:n] + else: + headlines = headlines[:self.registryValue('defaultNumberOfHeadlines')] sep = self.registryValue('headlineSeparator', channel) if self.registryValue('bold', channel): sep = ircutils.bold(sep) From 8ed94257bac9cdfdc49f98a8c3af6ee4d7b6abb8 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Sun, 25 Apr 2010 02:58:43 -0400 Subject: [PATCH 45/85] some enhancements Factoids.rank: output options: plain key output, and alpha sorting for plain output. allow an optional argument for how many ranked facts to show. --- plugins/Factoids/plugin.py | 44 +++++++++++++++++++++++++++++++------- plugins/Factoids/test.py | 5 +++++ 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index e967224c1..856e74f8a 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -385,28 +385,56 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): alias = wrap(alias, ['channel', 'something', 'something', optional('int')]) @internationalizeDocstring - def rank(self, irc, msg, args, channel): - """[] + def rank(self, irc, msg, args, channel, optlist, number): + """[] [--plain] [--alpha] [] Returns a list of top-ranked factoid keys, sorted by usage count - (rank). The number of factoid keys returned is set by the - rankListLength registry value. is only necessary if the - message isn't sent in the channel itself. + (rank). If is not provided, the default number of factoid keys + returned is set by the rankListLength registry value. + + If --plain option is given, rank numbers and usage counts are not + included in output. + + If --alpha option is given in addition to --plain, keys are sorted + alphabetically, instead of by rank. + + is only necessary if the message isn't sent in the channel + itself. """ - numfacts = self.registryValue('rankListLength', channel) + if not number: + number = self.registryValue('rankListLength', channel) db = self.getDb(channel) cursor = db.cursor() cursor.execute("""SELECT keys.key, relations.usage_count FROM keys, relations WHERE relations.key_id=keys.id ORDER BY relations.usage_count DESC - LIMIT ?""", (numfacts,)) + LIMIT ?""", (number,)) factkeys = cursor.fetchall() - s = [ "#%d %s (%d)" % (i+1, key[0], key[1]) for i, key in enumerate(factkeys) ] + plain=False + alpha=False + for (option, arg) in optlist: + if option == 'plain': + plain = True + elif option =='alpha': + alpha = True + if plain: + s = [ "%s" % (key[0],) for i, key in enumerate(factkeys) ] + if alpha: + s.sort() + else: + s = [ "#%d %s (%d)" % (i+1, key[0], key[1]) for i, key in enumerate(factkeys) ] irc.reply(", ".join(s)) +<<<<<<< HEAD rank = wrap(rank, ['channel']) @internationalizeDocstring +======= + rank = wrap(rank, ['channel', + getopts({'plain': '', 'alpha': '',}), + optional('int')]) + +>>>>>>> f1517a7... some enhancements Factoids.rank: def lock(self, irc, msg, args, channel, key): """[] diff --git a/plugins/Factoids/test.py b/plugins/Factoids/test.py index 22595c329..46782c239 100644 --- a/plugins/Factoids/test.py +++ b/plugins/Factoids/test.py @@ -189,6 +189,11 @@ class FactoidsTestCase(ChannelPluginTestCase): self.assertRegexp('factoids rank', '#1 foo \(0\), #2 moo \(0\)') self.assertRegexp('whatis moo', '.*cow.*') self.assertRegexp('factoids rank', '#1 moo \(1\), #2 foo \(0\)') + self.assertRegexp('factoids rank 1', '#1 moo \(1\)') + self.assertNotRegexp('factoids rank 1', 'foo') + self.assertRegexp('factoids rank --plain', 'moo, foo') + self.assertRegexp('factoids rank --plain --alpha', 'foo, moo') + self.assertResponse('factoids rank --plain 1', 'moo') def testQuoteHandling(self): self.assertNotError('learn foo as "\\"bar\\""') From b12d8a8a04d3270013f638d31e88376f4027e78d Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Wed, 28 Apr 2010 00:10:48 -0400 Subject: [PATCH 46/85] Use the ircutils.standardSubsitute function upon factoid output. This allows inclusion of the usual standardSubstitute vars within factoids. There is no config to disable this, since it is possible to escape the substitutions by simply doubling the dollar signs, as per the python documentation: http://docs.python.org/library/string.html#template-strings Thus, if you want a factoid to output a literal "$channel", for example, all you'd need to do is use "$$channel" in your factoid text, which will come out as "$channel" when said by the bot. Also added tests for this new behavior. --- plugins/Factoids/plugin.py | 10 +++++++--- plugins/Factoids/test.py | 8 ++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index 856e74f8a..aafde816f 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -245,7 +245,8 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): if factoids: if number: try: - irc.reply(factoids[number-1][0]) + irc.reply(ircutils.standardSubstitute(irc, msg, + factoids[number-1][0])) self._updateRank(channel, [factoids[number-1]]) except IndexError: irc.error(_('That\'s not a valid number for that key.')) @@ -258,12 +259,15 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): return ircutils.standardSubstitute(irc, msg, formatter, env) if len(factoids) == 1: - irc.reply(prefixer(factoids[0][0])) + irc.reply(ircutils.standardSubstitute(irc, msg, + prefixer(factoids[0][0]))) else: factoidsS = [] counter = 1 for factoid in factoids: - factoidsS.append(format('(#%i) %s', counter, factoid[0])) + factoidsS.append(format('(#%i) %s', counter, + ircutils.standardSubstitute(irc, msg, + factoid[0]))) counter += 1 irc.replies(factoidsS, prefixer=prefixer, joiner=', or ', onlyPrefixFirst=True) diff --git a/plugins/Factoids/test.py b/plugins/Factoids/test.py index 46782c239..4c56f6698 100644 --- a/plugins/Factoids/test.py +++ b/plugins/Factoids/test.py @@ -174,6 +174,14 @@ class FactoidsTestCase(ChannelPluginTestCase): self.assertNotError('learn foob as barb') self.assertRegexp('whatis foom', 'foo.*foob') + def testStandardSubstitute(self): + self.assertNotError('learn foo as this is $channel, and hour is $hour') + self.assertRegexp('whatis foo', 'this is #test, and hour is \d{1,2}') + self.assertNotError('learn bar as this is $$channel escaped') + self.assertRegexp('whatis bar', 'this is \$channel') + self.assertNotError('learn bar as this is $minute') + self.assertRegexp('whatis bar', '\$channel.*\d{1,2}') + def testAlias(self): self.assertNotError('learn foo as bar') self.assertNotError('alias foo zoog') From dfeb50de2d8e70494c770266928f266333f9375f Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Wed, 28 Apr 2010 15:27:08 -0400 Subject: [PATCH 47/85] Add --raw option to factoids.whatis, which disables variable substitution on the factoid. also add test for this. --- plugins/Factoids/plugin.py | 49 +++++++++++++++++++++++--------------- plugins/Factoids/test.py | 1 + 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index aafde816f..e250f19f0 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -241,12 +241,17 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): db.commit() def _replyFactoids(self, irc, msg, key, channel, factoids, - number=0, error=True): + number=0, error=True, raw=False): + def format_fact(text): + if raw: + return text + else: + return ircutils.standardSubstitute(irc, msg, text) + if factoids: if number: try: - irc.reply(ircutils.standardSubstitute(irc, msg, - factoids[number-1][0])) + irc.reply(format_fact(factoids[number-1][0])) self._updateRank(channel, [factoids[number-1]]) except IndexError: irc.error(_('That\'s not a valid number for that key.')) @@ -259,15 +264,13 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): return ircutils.standardSubstitute(irc, msg, formatter, env) if len(factoids) == 1: - irc.reply(ircutils.standardSubstitute(irc, msg, - prefixer(factoids[0][0]))) + irc.reply(format_fact(prefixer(factoids[0][0]))) else: factoidsS = [] counter = 1 for factoid in factoids: factoidsS.append(format('(#%i) %s', counter, - ircutils.standardSubstitute(irc, msg, - factoid[0]))) + format_fact(factoid[0]))) counter += 1 irc.replies(factoidsS, prefixer=prefixer, joiner=', or ', onlyPrefixFirst=True) @@ -297,13 +300,19 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): self._replyApproximateFactoids(irc, msg, channel, key, error=False) @internationalizeDocstring - def whatis(self, irc, msg, args, channel, words): - """[] [] + def whatis(self, irc, msg, args, channel, optlist, words): + """[] [--raw] [] Looks up the value of in the factoid database. If given a - number, will return only that exact factoid. is only - necessary if the message isn't sent in the channel itself. + number, will return only that exact factoid. If '--raw' option is + given, no variable substitution will take place on the factoid. + is only necessary if the message isn't sent in the channel + itself. """ + raw = False + for (option, arg) in optlist: + if option == 'raw': + raw = True number = None if len(words) > 1: if words[-1].isdigit(): @@ -313,12 +322,19 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): key = ' '.join(words) factoids = self._lookupFactoid(channel, key) if factoids: - self._replyFactoids(irc, msg, key, channel, factoids, number) + self._replyFactoids(irc, msg, key, channel, factoids, number, raw=raw) else: self._replyApproximateFactoids(irc, msg, channel, key) +<<<<<<< HEAD whatis = wrap(whatis, ['channel', many('something')]) @internationalizeDocstring +======= + whatis = wrap(whatis, ['channel', + getopts({'raw': '',}), + many('something')]) + +>>>>>>> e4c51ef... Add --raw option to factoids.whatis, which disables variable substitution on the factoid. def alias(self, irc, msg, args, channel, oldkey, newkey, number): """[] [] @@ -429,16 +445,11 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): else: s = [ "#%d %s (%d)" % (i+1, key[0], key[1]) for i, key in enumerate(factkeys) ] irc.reply(", ".join(s)) -<<<<<<< HEAD - rank = wrap(rank, ['channel']) - - @internationalizeDocstring -======= rank = wrap(rank, ['channel', getopts({'plain': '', 'alpha': '',}), optional('int')]) - ->>>>>>> f1517a7... some enhancements Factoids.rank: + + @internationalizeDocstring def lock(self, irc, msg, args, channel, key): """[] diff --git a/plugins/Factoids/test.py b/plugins/Factoids/test.py index 4c56f6698..035e4efed 100644 --- a/plugins/Factoids/test.py +++ b/plugins/Factoids/test.py @@ -177,6 +177,7 @@ class FactoidsTestCase(ChannelPluginTestCase): def testStandardSubstitute(self): self.assertNotError('learn foo as this is $channel, and hour is $hour') self.assertRegexp('whatis foo', 'this is #test, and hour is \d{1,2}') + self.assertRegexp('whatis --raw foo', 'this is \$channel, and hour is \$hour') self.assertNotError('learn bar as this is $$channel escaped') self.assertRegexp('whatis bar', 'this is \$channel') self.assertNotError('learn bar as this is $minute') From a6d92a70e8a651075c03be564c81f784c5a63d74 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Thu, 29 Apr 2010 20:04:51 -0400 Subject: [PATCH 48/85] Add Later note expiration period, 30 days by default. This should prevent the accumulation of old unclaimed notes in the database, which is possible due to notes left to misspelled nicks, to temporary nicks used by regulars, or to one-time visitor nicks. --- plugins/Later/config.py | 4 ++++ plugins/Later/plugin.py | 27 +++++++++++++++++++++++++++ plugins/Later/test.py | 10 ++++++++++ 3 files changed, 41 insertions(+) diff --git a/plugins/Later/config.py b/plugins/Later/config.py index eb505a8d6..e3d8803ba 100644 --- a/plugins/Later/config.py +++ b/plugins/Later/config.py @@ -51,5 +51,9 @@ conf.registerGlobalValue(Later, 'private', conf.registerGlobalValue(Later, 'tellOnJoin', registry.Boolean(True, _("""Determines whether users will be notified upon joining any channel the bot is in, or only upon sending a message."""))) +conf.registerGlobalValue(Later, 'messageExpiry', + registry.NonNegativeInteger(30, _("""Determines the maximum number of + days that a message will remain queued for a user. After this time elapses, + the message will be deleted. If this value is 0, there is no maximum."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/plugins/Later/plugin.py b/plugins/Later/plugin.py index 434a33511..56731da46 100644 --- a/plugins/Later/plugin.py +++ b/plugins/Later/plugin.py @@ -30,6 +30,7 @@ import csv import time +import datetime import supybot.log as log import supybot.conf as conf @@ -120,6 +121,31 @@ class Later(callbacks.Plugin): return nick[:-1] return nick + def _deleteExpired(self): + expiry = self.registryValue('messageExpiry') + curtime = time.time() + nickremovals=[] + for (nick, notes) in self._notes.iteritems(): + removals = [] + for (notetime, whence, text) in notes: + td = datetime.timedelta(seconds=(curtime - notetime)) + if td.days > expiry: + removals.append((notetime, whence, text)) + for note in removals: + notes.remove(note) + if len(notes) == 0: + nickremovals.append(nick) + for nick in nickremovals: + del self._notes[nick] + self._flushNotes() + + ## Note: we call _deleteExpired from 'tell'. This means that it's possible + ## for expired notes to remain in the database for longer than the maximum, + ## if no tell's are called. + ## However, the whole point of this is to avoid crud accumulation in the + ## database, so it's fine that we only delete expired notes when we try + ## adding new ones. + @internationalizeDocstring def tell(self, irc, msg, args, nick, text): """ @@ -128,6 +154,7 @@ class Later(callbacks.Plugin): contain wildcard characters, and the first matching nick will be given the note. """ + self._deleteExpired() if ircutils.strEqual(nick, irc.nick): irc.error(_('I can\'t send notes to myself.')) return diff --git a/plugins/Later/test.py b/plugins/Later/test.py index 447f088fd..5dd9d5259 100644 --- a/plugins/Later/test.py +++ b/plugins/Later/test.py @@ -28,6 +28,7 @@ ### from supybot.test import * +import time class LaterTestCase(PluginTestCase): plugins = ('Later',) @@ -52,5 +53,14 @@ class LaterTestCase(PluginTestCase): self.assertRegexp('later notes', 'foo\.') conf.supybot.protocols.irc.strictRfc.setValue(origconf) + def testNoteExpiry(self): + cb = self.irc.getCallback('Later') + # add a note 40 days in the past + cb._addNote('foo', 'test', 'some stuff', at=(time.time() - 3456000)) + self.assertRegexp('later notes', 'foo') + self.assertNotError('later tell moo stuff') + self.assertNotRegexp('later notes', 'foo') + self.assertRegexp('later notes', 'moo') + # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: From 9f26bb1420441af0ca3717d74fc6856d6e50870f Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Thu, 29 Apr 2010 20:20:36 -0400 Subject: [PATCH 49/85] for Later plugin, add test for actual sending of notes to nicks upon their being seen. --- plugins/Later/test.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/plugins/Later/test.py b/plugins/Later/test.py index 5dd9d5259..49a5a672e 100644 --- a/plugins/Later/test.py +++ b/plugins/Later/test.py @@ -30,7 +30,7 @@ from supybot.test import * import time -class LaterTestCase(PluginTestCase): +class LaterTestCase(ChannelPluginTestCase): plugins = ('Later',) def testLaterWorksTwice(self): self.assertNotError('later tell foo bar') @@ -62,5 +62,17 @@ class LaterTestCase(PluginTestCase): self.assertNotRegexp('later notes', 'foo') self.assertRegexp('later notes', 'moo') + def testNoteSend(self): + self.assertNotError('later tell foo stuff') + self.assertNotError('later tell bar more stuff') + self.assertRegexp('later notes', 'bar.*foo') + testPrefix = 'foo!bar@baz' + self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'something', + prefix=testPrefix)) + m = self.getMsg(' ') + self.failUnless(str(m).startswith('PRIVMSG foo :Sent just now: stuff')) + self.assertNotRegexp('later notes', 'foo') + self.assertRegexp('later notes', 'bar') + # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: From 2925c45b51c2d6f8df9c26a068131a6c1115c8a1 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Thu, 13 May 2010 00:52:58 -0400 Subject: [PATCH 50/85] restore the 'import re' to Topic plugin, which was for some reason taken out two commits ago, in commit 51cabeea33f3dab3d7cf42496c10bd660b212bc7 it is necessary for the operation of the plugin (specifically, in the _checkManageCapabilities function) the tests didn't catch that because it apparently imports re separately. --- plugins/Topic/plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/Topic/plugin.py b/plugins/Topic/plugin.py index 0ef60cb31..757ed3625 100644 --- a/plugins/Topic/plugin.py +++ b/plugins/Topic/plugin.py @@ -27,6 +27,7 @@ # POSSIBILITY OF SUCH DAMAGE. ### +import re import os import re import random From 8e80e7b65793f225177dad77e4e10b2c39ec2fac Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Wed, 2 Jun 2010 18:36:27 -0400 Subject: [PATCH 51/85] fix google calc to work when doing a currency conversion. made the calcre more generic, so it finds stuff on both math and currency. nothing a little exploration of google html page source couldn't solve. --- plugins/Google/plugin.py | 3 +-- plugins/Google/test.py | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/Google/plugin.py b/plugins/Google/plugin.py index 8c838185e..685e907c7 100644 --- a/plugins/Google/plugin.py +++ b/plugins/Google/plugin.py @@ -327,8 +327,7 @@ class Google(callbacks.PluginRegexp): url = r'http://google.com/search?q=' + s return url - _calcRe1 = re.compile(r']*>(.*?)', re.I) - _calcRe2 = re.compile(r'(.*?)', re.I) + _calcRe = re.compile(r'(.*?)', re.I) _calcSupRe = re.compile(r'(.*?)', re.I) _calcFontRe = re.compile(r'(.*?)') _calcTimesRe = re.compile(r'&(?:times|#215);') diff --git a/plugins/Google/test.py b/plugins/Google/test.py index e3c461aeb..c4c1233cb 100644 --- a/plugins/Google/test.py +++ b/plugins/Google/test.py @@ -39,7 +39,6 @@ class GoogleTestCase(ChannelPluginTestCase): def testCalc(self): self.assertNotRegexp('google calc e^(i*pi)+1', r'didn\'t') self.assertNotRegexp('google calc 1 usd in gbp', r'didn\'t') - self.assertRegexp('google calc current time in usa', r'Time in.*USA') def testHtmlHandled(self): self.assertNotRegexp('google calc ' From da25be73d439550c83e311d61362824d8699c6c0 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Sun, 13 Jun 2010 02:36:18 -0400 Subject: [PATCH 52/85] Channel: nicks: add --count argument, which outputs only the count of nicks in channel. also add tests for the nick command. --- plugins/Channel/plugin.py | 18 ++++++++++++------ plugins/Channel/test.py | 4 ++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/plugins/Channel/plugin.py b/plugins/Channel/plugin.py index 982c9017a..f483c800b 100644 --- a/plugins/Channel/plugin.py +++ b/plugins/Channel/plugin.py @@ -814,11 +814,12 @@ class Channel(callbacks.Plugin): additional('commandName')]) @internationalizeDocstring - def nicks(self, irc, msg, args, channel): - """[] + def nicks(self, irc, msg, args, channel, optlist): + """[] [--count] Returns the nicks in . is only necessary if the - message isn't sent in the channel itself. + message isn't sent in the channel itself. Returns only the number of + nicks if --count option is provided. """ # Make sure we don't elicit information about private channels to # people or channels that shouldn't know @@ -828,9 +829,14 @@ class Channel(callbacks.Plugin): msg.nick not in irc.state.channels[channel].users): irc.error(_('You don\'t have access to that information.')) L = list(irc.state.channels[channel].users) - utils.sortBy(str.lower, L) - irc.reply(utils.str.commaAndify(L)) - nicks = wrap(nicks, ['inChannel']) + keys = [option for (option, arg) in optlist] + if 'count' not in keys: + utils.sortBy(str.lower, L) + irc.reply(utils.str.commaAndify(L)) + else: + irc.reply(str(len(L))) + nicks = wrap(nicks, ['inChannel', + getopts({'count':''})]) @internationalizeDocstring def alertOps(self, irc, channel, s, frm=None): diff --git a/plugins/Channel/test.py b/plugins/Channel/test.py index 67ba108a3..54ce68ac5 100644 --- a/plugins/Channel/test.py +++ b/plugins/Channel/test.py @@ -214,5 +214,9 @@ class ChannelTestCase(ChannelPluginTestCase): finally: conf.supybot.protocols.irc.banmask.setValue(orig) + def testNicks(self): + self.assertResponse('channel nicks', 'bar, foo, and test') + self.assertResponse('channel nicks --count', '3') + # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: From 6ccd1ce3d6e1c2986a2aadd05aae5bd7d1bb011f Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Tue, 15 Jun 2010 23:53:15 -0400 Subject: [PATCH 53/85] ChannelLogger: include in logs the reasons for parts and quits --- plugins/ChannelLogger/plugin.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/plugins/ChannelLogger/plugin.py b/plugins/ChannelLogger/plugin.py index 6296cccbc..ac08631b7 100644 --- a/plugins/ChannelLogger/plugin.py +++ b/plugins/ChannelLogger/plugin.py @@ -226,10 +226,14 @@ class ChannelLogger(callbacks.Plugin): '*** %s was kicked by %s\n', target, msg.nick) def doPart(self, irc, msg): + if len(msg.args) > 1: + reason = " (%s)" % msg.args[1] + else: + reason = "" for channel in msg.args[0].split(','): self.doLog(irc, channel, - '*** %s <%s> has left %s\n', - msg.nick, msg.prefix, channel) + '*** %s <%s> has left %s%s\n', + msg.nick, msg.prefix, channel, reason) def doMode(self, irc, msg): channel = msg.args[0] @@ -247,13 +251,17 @@ class ChannelLogger(callbacks.Plugin): '*** %s changes topic to "%s"\n', msg.nick, msg.args[1]) def doQuit(self, irc, msg): + if len(msg.args) == 1: + reason = " (%s)" % msg.args[0] + else: + reason = "" if not isinstance(irc, irclib.Irc): irc = irc.getRealIrc() for (channel, chan) in self.lastStates[irc].channels.iteritems(): if msg.nick in chan.users: self.doLog(irc, channel, - '*** %s <%s> has quit IRC\n', - msg.nick, msg.prefix) + '*** %s <%s> has quit IRC%s\n', + msg.nick, msg.prefix, reason) def outFilter(self, irc, msg): # Gotta catch my own messages *somehow* :) From f6c9543dc3a99f15d52f2987082a80eebc299464 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Thu, 1 Jul 2010 15:44:53 -0400 Subject: [PATCH 54/85] Filter: add unbinary command, as counterpart to binary command. --- plugins/Filter/plugin.py | 11 +++++++++++ plugins/Filter/test.py | 3 +++ 2 files changed, 14 insertions(+) diff --git a/plugins/Filter/plugin.py b/plugins/Filter/plugin.py index 0e126312c..fd469c64d 100644 --- a/plugins/Filter/plugin.py +++ b/plugins/Filter/plugin.py @@ -163,6 +163,17 @@ class Filter(callbacks.Plugin): irc.reply(''.join(L)) binary = wrap(binary, ['text']) + @internationalizeDocstring + def unbinary(self, irc, msg, args, text): + """ + + Returns the character representation of binary . + Assumes ASCII, 8 digits per character. + """ + L = [chr(int(text[i:(i+8)], 2)) for i in xrange(0, len(text), 8)] + irc.reply(''.join(L)) + unbinary = wrap(unbinary, ['text']) + @internationalizeDocstring def hexlify(self, irc, msg, args, text): """ diff --git a/plugins/Filter/test.py b/plugins/Filter/test.py index d31959f66..9c76e4331 100644 --- a/plugins/Filter/test.py +++ b/plugins/Filter/test.py @@ -86,6 +86,9 @@ class FilterTest(ChannelPluginTestCase): def testBinary(self): self.assertResponse('binary A', '01000001') + + def testUnbinary(self): + self.assertResponse('unbinary 011011010110111101101111', 'moo') def testRot13(self): for s in map(str, range(1000, 1010)): From 390b3ec15f786c4c4eb0af177205255fffd000e3 Mon Sep 17 00:00:00 2001 From: "oevna@users.sourceforge.net" Date: Thu, 8 Jul 2010 17:17:37 -0400 Subject: [PATCH 55/85] Added ping command to Unix plugin Signed-off-by: Daniel Folkinshteyn --- plugins/Unix/config.py | 4 ++++ plugins/Unix/plugin.py | 31 +++++++++++++++++++++++++++++++ plugins/Unix/test.py | 5 +++++ 3 files changed, 40 insertions(+) diff --git a/plugins/Unix/config.py b/plugins/Unix/config.py index 69b77ef23..f37cf37e1 100644 --- a/plugins/Unix/config.py +++ b/plugins/Unix/config.py @@ -87,5 +87,9 @@ conf.registerGlobalValue(Unix.wtf, 'command', registry.String(utils.findBinaryInPath('wtf') or '', _("""Determines what command will be called for the wtf command."""))) +conf.registerGroup(Unix, 'ping') +conf.registerGlobalValue(Unix.ping, 'command', + registry.String(utils.findBinaryInPath('ping') or '', """Determines what + command will be called for the ping command.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/plugins/Unix/plugin.py b/plugins/Unix/plugin.py index c4d64c0b9..c69644847 100644 --- a/plugins/Unix/plugin.py +++ b/plugins/Unix/plugin.py @@ -256,5 +256,36 @@ class Unix(callbacks.Plugin): 'variable appropriately.')) wtf = wrap(wtf, [optional(('literal', ['is'])), 'something']) + def ping(self, irc, msg, args, host): + """ + Sends an ICMP echo request to the specified host + """ + pingCmd = self.registryValue('ping.command') + if not pingCmd: + irc.error('The ping command is not configured. If one ' + 'is installed, reconfigure ' + 'supybot.plugins.Unix.ping.command appropriately.', + Raise=True) + else: + try: host = host.group(0) + except AttributeError: pass + + inst = subprocess.Popen([pingCmd,'-c','1', host], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + stdin=file(os.devnull)) + + result = inst.communicate() + + if result[1]: # stderr + irc.reply(' '.join(result[1].split())) + else: + response = result[0].split("\n"); + irc.reply(' '.join(response[1].split()[3:5]).split(':')[0] + + ': ' + ' '.join(response[-3:])) + + _hostExpr = re.compile(r'^[a-z0-9][a-z0-9\.-]*$', re.I) + ping = wrap(ping, [first('ip', ('matches', _hostExpr, 'Invalid hostname'))]) + Class = Unix # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/plugins/Unix/test.py b/plugins/Unix/test.py index d554d90c7..93b6c0cf8 100644 --- a/plugins/Unix/test.py +++ b/plugins/Unix/test.py @@ -65,5 +65,10 @@ if os.name == 'posix': def testFortune(self): self.assertNotError('fortune') + if utils.findBinaryInPath('ping') is not None: + def testPing(self): + self.assertNotError('ping') + + # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: From f69c47b092faf83a21045495b664d457631827f4 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Thu, 8 Jul 2010 23:29:01 -0400 Subject: [PATCH 56/85] Unix: fix test for the ping command, call unix ping instead of the default misc ping. --- plugins/Unix/test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/Unix/test.py b/plugins/Unix/test.py index 93b6c0cf8..6a67638c0 100644 --- a/plugins/Unix/test.py +++ b/plugins/Unix/test.py @@ -67,7 +67,8 @@ if os.name == 'posix': if utils.findBinaryInPath('ping') is not None: def testPing(self): - self.assertNotError('ping') + self.assertNotError('unix ping localhost') + self.assertError('unix ping') From 61167ce909a2099980b6cba31fbfb3590616f0e8 Mon Sep 17 00:00:00 2001 From: "oevna@users.sourceforge.net" Date: Fri, 9 Jul 2010 10:20:41 -0400 Subject: [PATCH 57/85] Unix: fixed uncaught OSError exception raised when executing ping fails. Signed-off-by: Daniel Folkinshteyn --- plugins/Unix/plugin.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/plugins/Unix/plugin.py b/plugins/Unix/plugin.py index c69644847..88f033a73 100644 --- a/plugins/Unix/plugin.py +++ b/plugins/Unix/plugin.py @@ -269,12 +269,16 @@ class Unix(callbacks.Plugin): else: try: host = host.group(0) except AttributeError: pass - - inst = subprocess.Popen([pingCmd,'-c','1', host], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - stdin=file(os.devnull)) + try: + inst = subprocess.Popen([pingCmd,'-c','1', host], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + stdin=file(os.devnull)) + except OSError, e: + irc.error('It seems the configured ping command was ' + 'not available (%s).' % e, Raise=True) + result = inst.communicate() if result[1]: # stderr From cc5f3c104923aac0a3c31ea53c589f2a64a2db2c Mon Sep 17 00:00:00 2001 From: brian c Date: Mon, 12 Jul 2010 16:42:03 -0400 Subject: [PATCH 58/85] Unix: various enhancements to the ping command: Fixed bug in 100% packet loss response. Errors from ping are now sent to irc.error(). Added packet count, interval, ttl, and wait options. Added additional test cases. Also, Enabled threading for Unix plugin, and for wtf, spell, and ping commands. Signed-off-by: Daniel Folkinshteyn --- plugins/Unix/plugin.py | 55 ++++++++++++++++++++++++++++++------------ plugins/Unix/test.py | 22 ++++++++++++++--- 2 files changed, 58 insertions(+), 19 deletions(-) diff --git a/plugins/Unix/plugin.py b/plugins/Unix/plugin.py index 88f033a73..28b79989d 100644 --- a/plugins/Unix/plugin.py +++ b/plugins/Unix/plugin.py @@ -68,6 +68,7 @@ def pipeReadline(fd, timeout=2): raise TimeoutError class Unix(callbacks.Plugin): + threaded = True @internationalizeDocstring def errno(self, irc, msg, args, s): """ @@ -186,7 +187,7 @@ class Unix(callbacks.Plugin): else: resp = 'Something unexpected was seen in the [ai]spell output.' irc.reply(resp) - spell = wrap(spell, ['somethingWithoutSpaces']) + spell = thread(wrap(spell, ['something'])) @internationalizeDocstring def fortune(self, irc, msg, args): @@ -253,12 +254,20 @@ class Unix(callbacks.Plugin): irc.error(_('The wtf command is not configured. If it is installed ' 'on this system, reconfigure the ' 'supybot.plugins.Unix.wtf.command configuration ' +<<<<<<< HEAD 'variable appropriately.')) wtf = wrap(wtf, [optional(('literal', ['is'])), 'something']) +======= + 'variable appropriately.') + wtf = thread(wrap(wtf, [optional(('literal', ['is'])), 'something'])) +>>>>>>> edc4d86... Unix: various enhancements to the ping command: - def ping(self, irc, msg, args, host): - """ - Sends an ICMP echo request to the specified host + def ping(self, irc, msg, args, optlist, host): + """[--c ] [--i ] [--t ] [--W ] + Sends an ICMP echo request to the specified host. + The arguments correspond with those listed in ping(8). --c is + limited to 10 packets or less (default is 5). --i is limited to 5 + or less. --W is limited to 10 or less. """ pingCmd = self.registryValue('ping.command') if not pingCmd: @@ -270,26 +279,40 @@ class Unix(callbacks.Plugin): try: host = host.group(0) except AttributeError: pass + args = [pingCmd] + for opt, val in optlist: + if opt == 'c' and val > 10: val = 10 + if opt == 'i' and val > 5: val = 5 + if opt == 'W' and val > 10: val = 10 + args.append('-%s' % opt) + args.append(str(val)) + if '-c' not in args: + args.append('-c') + args.append('5') + args.append(host) try: - inst = subprocess.Popen([pingCmd,'-c','1', host], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - stdin=file(os.devnull)) + inst = subprocess.Popen(args, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + stdin=file(os.devnull)) except OSError, e: irc.error('It seems the configured ping command was ' 'not available (%s).' % e, Raise=True) - result = inst.communicate() - if result[1]: # stderr - irc.reply(' '.join(result[1].split())) + irc.error(' '.join(result[1].split())) else: response = result[0].split("\n"); - irc.reply(' '.join(response[1].split()[3:5]).split(':')[0] - + ': ' + ' '.join(response[-3:])) - - _hostExpr = re.compile(r'^[a-z0-9][a-z0-9\.-]*$', re.I) - ping = wrap(ping, [first('ip', ('matches', _hostExpr, 'Invalid hostname'))]) + if response[1]: + irc.reply(' '.join(response[1].split()[3:5]).split(':')[0] + + ': ' + ' '.join(response[-3:])) + else: + irc.reply(' '.join(response[0].split()[1:3]) + + ': ' + ' '.join(response[-3:])) + + _hostExpr = re.compile(r'^[a-z0-9][a-z0-9\.-]*[a-z0-9]$', re.I) + ping = thread(wrap(ping, [getopts({'c':'positiveInt','i':'float', + 't':'positiveInt','W':'positiveInt'}), + first('ip', ('matches', _hostExpr, 'Invalid hostname'))])) Class = Unix # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/plugins/Unix/test.py b/plugins/Unix/test.py index 6a67638c0..e46dcb7e7 100644 --- a/plugins/Unix/test.py +++ b/plugins/Unix/test.py @@ -67,9 +67,25 @@ if os.name == 'posix': if utils.findBinaryInPath('ping') is not None: def testPing(self): - self.assertNotError('unix ping localhost') + self.assertNotError('unix ping 127.0.0.1') self.assertError('unix ping') - - + self.assertError('unix ping -localhost') + self.assertError('unix ping local%host') + def testPingCount(self): + self.assertNotError('unix ping --c 1 127.0.0.1') + self.assertError('unix ping --c a 127.0.0.1') + self.assertRegexp('unix ping --c 11 127.0.0.1','10 packets') + self.assertRegexp('unix ping 127.0.0.1','5 packets') + def testPingInterval(self): + self.assertNotError('unix ping --i 1 --c 1 127.0.0.1') + self.assertError('unix ping --i a --c 1 127.0.0.1') + # Super-user privileged interval setting + self.assertError('unix ping --i 0.1 --c 1 127.0.0.1') + def testPingTtl(self): + self.assertNotError('unix ping --t 64 --c 1 127.0.0.1') + self.assertError('unix ping --t a --c 1 127.0.0.1') + def testPingWait(self): + self.assertNotError('unix ping --W 1 --c 1 127.0.0.1') + self.assertError('unix ping --W a --c 1 127.0.0.1') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: From e35bf94600f0f1c3e47352a474763bba34c3d05f Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Wed, 21 Jul 2010 12:48:46 -0400 Subject: [PATCH 59/85] Unix: add call command, giving owner ability to call any system command. --- plugins/Unix/plugin.py | 27 +++++++++++++++++++++++++++ plugins/Unix/test.py | 4 ++++ 2 files changed, 31 insertions(+) diff --git a/plugins/Unix/plugin.py b/plugins/Unix/plugin.py index 28b79989d..92c249747 100644 --- a/plugins/Unix/plugin.py +++ b/plugins/Unix/plugin.py @@ -38,6 +38,7 @@ import random import select import struct import subprocess +import shlex import supybot.utils as utils from supybot.commands import * @@ -314,5 +315,31 @@ class Unix(callbacks.Plugin): 't':'positiveInt','W':'positiveInt'}), first('ip', ('matches', _hostExpr, 'Invalid hostname'))])) + def call(self, irc, msg, args, text): + """ + Calls any command available on the system, and returns its output. + Requires owner capability. + Note that being restricted to owner, this command does not do any + sanity checking on input/output. So it is up to you to make sure + you don't run anything that will spamify your channel or that + will bring your machine to its knees. + """ + args = shlex.split(text) + try: + inst = subprocess.Popen(args, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + stdin=file(os.devnull)) + except OSError, e: + irc.error('It seems the requested command was ' + 'not available (%s).' % e, Raise=True) + result = inst.communicate() + if result[1]: # stderr + irc.error(' '.join(result[1].split())) + if result[0]: # stdout + response = result[0].split("\n"); + response = [l for l in response if l] + irc.replies(response) + call = thread(wrap(call, ["owner", "text"])) + Class = Unix # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/plugins/Unix/test.py b/plugins/Unix/test.py index e46dcb7e7..42b8d4108 100644 --- a/plugins/Unix/test.py +++ b/plugins/Unix/test.py @@ -88,4 +88,8 @@ if os.name == 'posix': self.assertNotError('unix ping --W 1 --c 1 127.0.0.1') self.assertError('unix ping --W a --c 1 127.0.0.1') + def testCall(self): + self.assertNotError('unix call /bin/ping -c 1 localhost') + self.assertRegexp('unix call /bin/ping -c 1 localhost', 'ping statistics') + self.assertError('unix call /usr/bin/nosuchcommandaoeuaoeu') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: From 2d56eba17ec2fc92df2b5c3ece6affe0dd76508a Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 26 Feb 2011 09:50:15 +0100 Subject: [PATCH 60/85] Unix: fix merge of @ping --- plugins/Unix/plugin.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/plugins/Unix/plugin.py b/plugins/Unix/plugin.py index 92c249747..cb7027c46 100644 --- a/plugins/Unix/plugin.py +++ b/plugins/Unix/plugin.py @@ -255,19 +255,15 @@ class Unix(callbacks.Plugin): irc.error(_('The wtf command is not configured. If it is installed ' 'on this system, reconfigure the ' 'supybot.plugins.Unix.wtf.command configuration ' -<<<<<<< HEAD 'variable appropriately.')) - wtf = wrap(wtf, [optional(('literal', ['is'])), 'something']) -======= - 'variable appropriately.') wtf = thread(wrap(wtf, [optional(('literal', ['is'])), 'something'])) ->>>>>>> edc4d86... Unix: various enhancements to the ping command: + @internationalizeDocstring def ping(self, irc, msg, args, optlist, host): - """[--c ] [--i ] [--t ] [--W ] - Sends an ICMP echo request to the specified host. - The arguments correspond with those listed in ping(8). --c is - limited to 10 packets or less (default is 5). --i is limited to 5 + """[--c ] [--i ] [--t ] [--W ] + Sends an ICMP echo request to the specified host. + The arguments correspond with those listed in ping(8). --c is + limited to 10 packets or less (default is 5). --i is limited to 5 or less. --W is limited to 10 or less. """ pingCmd = self.registryValue('ping.command') @@ -279,11 +275,11 @@ class Unix(callbacks.Plugin): else: try: host = host.group(0) except AttributeError: pass - + args = [pingCmd] for opt, val in optlist: if opt == 'c' and val > 10: val = 10 - if opt == 'i' and val > 5: val = 5 + if opt == 'i' and val > 5: val = 5 if opt == 'W' and val > 10: val = 10 args.append('-%s' % opt) args.append(str(val)) @@ -292,7 +288,7 @@ class Unix(callbacks.Plugin): args.append('5') args.append(host) try: - inst = subprocess.Popen(args, stdout=subprocess.PIPE, + inst = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=file(os.devnull)) except OSError, e: @@ -304,10 +300,10 @@ class Unix(callbacks.Plugin): else: response = result[0].split("\n"); if response[1]: - irc.reply(' '.join(response[1].split()[3:5]).split(':')[0] + irc.reply(' '.join(response[1].split()[3:5]).split(':')[0] + ': ' + ' '.join(response[-3:])) else: - irc.reply(' '.join(response[0].split()[1:3]) + irc.reply(' '.join(response[0].split()[1:3]) + ': ' + ' '.join(response[-3:])) _hostExpr = re.compile(r'^[a-z0-9][a-z0-9\.-]*[a-z0-9]$', re.I) From 0d7e3d3759f852929e25dd2064e310cf43f2f988 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 26 Feb 2011 10:40:50 +0100 Subject: [PATCH 61/85] (Re)generate all plugin's .pot file --- plugins/Admin/messages.pot | 2 +- plugins/Alias/messages.pot | 6 +- plugins/Anonymous/messages.pot | 8 +- plugins/AutoMode/messages.pot | 2 +- plugins/BadWords/messages.pot | 10 +- plugins/Channel/messages.pot | 17 +-- plugins/ChannelLogger/messages.pot | 2 +- plugins/ChannelStats/messages.pot | 2 +- plugins/Conditional/messages.pot | 199 +++++++++++++++++++++++++++ plugins/Config/messages.pot | 2 +- plugins/Ctcp/messages.pot | 45 +++++- plugins/Dict/messages.pot | 28 ++-- plugins/Dunno/messages.pot | 4 +- plugins/Factoids/messages.pot | 148 ++++++++++++++------ plugins/Filter/messages.pot | 204 +++++++++++++++------------- plugins/Format/messages.pot | 32 +++-- plugins/Games/messages.pot | 2 +- plugins/Google/messages.pot | 30 ++-- plugins/Herald/messages.pot | 2 +- plugins/Internet/messages.pot | 28 ++-- plugins/Karma/messages.pot | 28 ++-- plugins/Lart/messages.pot | 2 +- plugins/Later/messages.pot | 51 +++++-- plugins/Limiter/messages.pot | 4 +- plugins/Math/messages.pot | 2 +- plugins/MessageParser/messages.pot | 152 +++++++++++++++++++++ plugins/Misc/messages.pot | 72 +++++----- plugins/MoobotFactoids/messages.pot | 88 ++++++------ plugins/Network/messages.pot | 2 +- plugins/News/messages.pot | 2 +- plugins/NickCapture/messages.pot | 6 +- plugins/Nickometer/messages.pot | 2 +- plugins/Note/messages.pot | 16 +-- plugins/Owner/messages.pot | 40 +++--- plugins/Plugin/messages.pot | 86 +++++++----- plugins/Praise/messages.pot | 14 +- plugins/Protector/messages.pot | 2 +- plugins/Quote/messages.pot | 2 +- plugins/QuoteGrabs/messages.pot | 40 +++--- plugins/RSS/messages.pot | 50 ++++--- plugins/Relay/messages.pot | 2 +- plugins/Reply/messages.pot | 22 ++- plugins/Scheduler/messages.pot | 20 +-- plugins/Seen/messages.pot | 2 +- plugins/Services/messages.pot | 38 +++--- plugins/ShrinkUrl/messages.pot | 2 +- plugins/Status/messages.pot | 6 +- plugins/String/messages.pot | 2 +- plugins/Success/messages.pot | 4 +- plugins/Time/messages.pot | 2 +- plugins/Todo/messages.pot | 2 +- plugins/Topic/messages.pot | 88 ++++++------ plugins/URL/messages.pot | 2 +- plugins/Unix/messages.pot | 66 ++++++--- plugins/User/messages.pot | 2 +- plugins/Utilities/messages.pot | 2 +- plugins/Web/messages.pot | 8 +- 57 files changed, 1150 insertions(+), 554 deletions(-) create mode 100644 plugins/Conditional/messages.pot create mode 100644 plugins/MessageParser/messages.pot diff --git a/plugins/Admin/messages.pot b/plugins/Admin/messages.pot index 673d31155..4b4e9ec5b 100644 --- a/plugins/Admin/messages.pot +++ b/plugins/Admin/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-16 10:43+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/Alias/messages.pot b/plugins/Alias/messages.pot index ab8d0ac06..373c8de0e 100644 --- a/plugins/Alias/messages.pot +++ b/plugins/Alias/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-16 14:10+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -36,6 +36,10 @@ msgid "" "Alias for %q." msgstr "" +#: plugin.py:166 +msgid "argument" +msgstr "" + #: plugin.py:220 #, docstring msgid "" diff --git a/plugins/Anonymous/messages.pot b/plugins/Anonymous/messages.pot index 53bc52bb4..299a93fe9 100644 --- a/plugins/Anonymous/messages.pot +++ b/plugins/Anonymous/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-16 15:14+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -42,7 +42,7 @@ msgid "" " messages to other users." msgstr "" -#: plugin.py:41 +#: plugin.py:40 #, docstring msgid "" "This plugin allows users to act through the bot anonymously. The 'do'\n" @@ -58,7 +58,7 @@ msgid "" " " msgstr "" -#: plugin.py:81 +#: plugin.py:80 #, docstring msgid "" " \n" @@ -68,7 +68,7 @@ msgid "" " " msgstr "" -#: plugin.py:95 +#: plugin.py:94 #, docstring msgid "" " \n" diff --git a/plugins/AutoMode/messages.pot b/plugins/AutoMode/messages.pot index d1330c69b..b6f4770f0 100644 --- a/plugins/AutoMode/messages.pot +++ b/plugins/AutoMode/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-16 18:48+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/BadWords/messages.pot b/plugins/BadWords/messages.pot index 5bc528674..767fc700a 100644 --- a/plugins/BadWords/messages.pot +++ b/plugins/BadWords/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2011-01-29 11:48+CET\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -15,14 +15,6 @@ msgstr "" "Generated-By: pygettext.py 1.5\n" -#: __init__.py:30 -#, docstring -msgid "" -"\n" -"Filters bad words on outgoing messages from the bot, so the bot can't be made\n" -"to say bad words.\n" -msgstr "" - #: config.py:40 msgid "Would you like to add some bad words?" msgstr "" diff --git a/plugins/Channel/messages.pot b/plugins/Channel/messages.pot index ee821ac23..b539e379c 100644 --- a/plugins/Channel/messages.pot +++ b/plugins/Channel/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-25 13:10+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -579,33 +579,34 @@ msgstr "" #: plugin.py:818 #, docstring msgid "" -"[]\n" +"[] [--count]\n" "\n" " Returns the nicks in . is only necessary if the\n" -" message isn't sent in the channel itself.\n" +" message isn't sent in the channel itself. Returns only the number of\n" +" nicks if --count option is provided.\n" " " msgstr "" -#: plugin.py:829 +#: plugin.py:830 msgid "You don't have access to that information." msgstr "" -#: plugin.py:837 +#: plugin.py:843 #, docstring msgid "" "Internal message for notifying all the #channel,ops in a channel of\n" " a given situation." msgstr "" -#: plugin.py:840 +#: plugin.py:846 msgid "Alert to all %s ops: %s" msgstr "" -#: plugin.py:842 +#: plugin.py:848 msgid " (from %s)" msgstr "" -#: plugin.py:850 +#: plugin.py:856 #, docstring msgid "" "[] \n" diff --git a/plugins/ChannelLogger/messages.pot b/plugins/ChannelLogger/messages.pot index 57b359a3c..33971af4c 100644 --- a/plugins/ChannelLogger/messages.pot +++ b/plugins/ChannelLogger/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-17 10:02+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/ChannelStats/messages.pot b/plugins/ChannelStats/messages.pot index 0c664ce18..0c5062eea 100644 --- a/plugins/ChannelStats/messages.pot +++ b/plugins/ChannelStats/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-16 09:41+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/Conditional/messages.pot b/plugins/Conditional/messages.pot new file mode 100644 index 000000000..2eb00e392 --- /dev/null +++ b/plugins/Conditional/messages.pot @@ -0,0 +1,199 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: ENCODING\n" +"Generated-By: pygettext.py 1.5\n" + + +#: plugin.py:54 +#, docstring +msgid "" +"Add the help for \"@plugin help Conditional\" here\n" +" This should describe *how* to use this plugin." +msgstr "" + +#: plugin.py:61 +#, docstring +msgid "Run a command from message, as if command was sent over IRC." +msgstr "" + +#: plugin.py:69 +#, docstring +msgid "" +" \n" +" \n" +" Runs if evaluates to true, runs \n" +" if it evaluates to false.\n" +" \n" +" Use other logical operators defined in this plugin and command nesting\n" +" to your advantage here.\n" +" " +msgstr "" + +#: plugin.py:85 +#, docstring +msgid "" +" [ ... ]\n" +" \n" +" Returns true if all conditions supplied evaluate to true.\n" +" " +msgstr "" + +#: plugin.py:96 +#, docstring +msgid "" +" [ ... ]\n" +" \n" +" Returns true if any one of conditions supplied evaluates to true.\n" +" " +msgstr "" + +#: plugin.py:107 +#, docstring +msgid "" +" [ ... ]\n" +" \n" +" Returns true if only one of conditions supplied evaluates to true.\n" +" " +msgstr "" + +#: plugin.py:118 +#, docstring +msgid "" +" \n" +" \n" +" Does a string comparison on and . \n" +" Returns true if they are equal.\n" +" " +msgstr "" + +#: plugin.py:130 +#, docstring +msgid "" +" \n" +" \n" +" Does a string comparison on and . \n" +" Returns true if they are not equal.\n" +" " +msgstr "" + +#: plugin.py:142 +#, docstring +msgid "" +" \n" +" \n" +" Does a string comparison on and . \n" +" Returns true if is greater than .\n" +" " +msgstr "" + +#: plugin.py:154 +#, docstring +msgid "" +" \n" +" \n" +" Does a string comparison on and . \n" +" Returns true if is greater than or equal to .\n" +" " +msgstr "" + +#: plugin.py:166 +#, docstring +msgid "" +" \n" +" \n" +" Does a string comparison on and . \n" +" Returns true if is less than .\n" +" " +msgstr "" + +#: plugin.py:178 +#, docstring +msgid "" +" \n" +" \n" +" Does a string comparison on and . \n" +" Returns true if is less than or equal to .\n" +" " +msgstr "" + +#: plugin.py:190 +#, docstring +msgid "" +" \n" +" \n" +" Determines if is a substring of . \n" +" Returns true if is contained in .\n" +" " +msgstr "" + +#: plugin.py:202 +#, docstring +msgid "" +" \n" +" \n" +" Does a numeric comparison on and . \n" +" Returns true if they are equal.\n" +" " +msgstr "" + +#: plugin.py:214 +#, docstring +msgid "" +" \n" +" \n" +" Does a numeric comparison on and . \n" +" Returns true if they are not equal.\n" +" " +msgstr "" + +#: plugin.py:226 +#, docstring +msgid "" +" \n" +" \n" +" Does a numeric comparison on and . \n" +" Returns true if they is greater than .\n" +" " +msgstr "" + +#: plugin.py:238 +#, docstring +msgid "" +" \n" +" \n" +" Does a numeric comparison on and . \n" +" Returns true if is greater than or equal to .\n" +" " +msgstr "" + +#: plugin.py:250 +#, docstring +msgid "" +" \n" +" \n" +" Does a numeric comparison on and . \n" +" Returns true if is less than .\n" +" " +msgstr "" + +#: plugin.py:262 +#, docstring +msgid "" +" \n" +" \n" +" Does a numeric comparison on and . \n" +" Returns true if is less than or equal to .\n" +" " +msgstr "" + diff --git a/plugins/Config/messages.pot b/plugins/Config/messages.pot index 91f5aa6d6..cb2fba16c 100644 --- a/plugins/Config/messages.pot +++ b/plugins/Config/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-12-12 15:02+CET\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/Ctcp/messages.pot b/plugins/Ctcp/messages.pot index 61064f706..2d026233a 100644 --- a/plugins/Ctcp/messages.pot +++ b/plugins/Ctcp/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-26 18:57+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -15,7 +15,50 @@ msgstr "" "Generated-By: pygettext.py 1.5\n" +#: plugin.py:77 +#, docstring +msgid "\001PING ?(.*)\001" +msgstr "" + +#: plugin.py:86 +#, docstring +msgid "\001VERSION\001" +msgstr "" + +#: plugin.py:91 +#, docstring +msgid "\001USERINFO\001" +msgstr "" + +#: plugin.py:96 +#, docstring +msgid "\001TIME\001" +msgstr "" + +#: plugin.py:101 +#, docstring +msgid "\001FINGER\001" +msgstr "" + #: plugin.py:104 msgid "Supybot, the best Python IRC bot in existence!" msgstr "" +#: plugin.py:107 +#, docstring +msgid "\001SOURCE\001" +msgstr "" + +#: plugin.py:123 +#, docstring +msgid "" +"[] [--nicks]\n" +"\n" +" Sends a CTCP VERSION to , returning the various\n" +" version strings returned. It waits for 10 seconds before returning\n" +" the versions received at that point. If --nicks is given, nicks are\n" +" associated with the version strings; otherwise, only the version\n" +" strings are given.\n" +" " +msgstr "" + diff --git a/plugins/Dict/messages.pot b/plugins/Dict/messages.pot index 86e447f6f..42338f3a2 100644 --- a/plugins/Dict/messages.pot +++ b/plugins/Dict/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-17 10:39+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -36,7 +36,7 @@ msgid "" " will use all dictionaries to define words." msgstr "" -#: plugin.py:52 +#: plugin.py:54 #, docstring msgid "" "takes no arguments\n" @@ -45,7 +45,7 @@ msgid "" " " msgstr "" -#: plugin.py:68 +#: plugin.py:70 #, docstring msgid "" "takes no arguments\n" @@ -54,7 +54,7 @@ msgid "" " " msgstr "" -#: plugin.py:83 +#: plugin.py:85 #, docstring msgid "" "[] \n" @@ -64,19 +64,31 @@ msgid "" " " msgstr "" -#: plugin.py:106 +#: plugin.py:108 msgid "You must give a word to define." msgstr "" -#: plugin.py:112 +#: plugin.py:114 msgid "No definition for %q could be found." msgstr "" -#: plugin.py:115 +#: plugin.py:117 msgid "No definition for %q could be found in %s" msgstr "" -#: plugin.py:127 +#: plugin.py:129 msgid "%L responded: %s" msgstr "" +#: plugin.py:136 +#, docstring +msgid "" +" [ ...]\n" +" Gets a random synonym from the Moby Thesaurus (moby-thes) database.\n" +" \n" +" If given many words, gets a random synonym for each of them.\n" +" \n" +" Quote phrases to have them treated as one lookup word.\n" +" " +msgstr "" + diff --git a/plugins/Dunno/messages.pot b/plugins/Dunno/messages.pot index 96ba98add..d25a8258f 100644 --- a/plugins/Dunno/messages.pot +++ b/plugins/Dunno/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-17 10:48+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -21,7 +21,7 @@ msgid "" " of the user giving an invalid command to the \"dunno\" response." msgstr "" -#: plugin.py:38 +#: plugin.py:37 #, docstring msgid "" "This plugin was written initially to work with MoobotFactoids, the two\n" diff --git a/plugins/Factoids/messages.pot b/plugins/Factoids/messages.pot index 5bcd6f497..d7f98ee95 100644 --- a/plugins/Factoids/messages.pot +++ b/plugins/Factoids/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-11-11 12:37+CET\n" +"POT-Creation-Date: 2011-02-26 09:58+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -45,10 +45,18 @@ msgid "" msgstr "" #: config.py:64 +msgid "" +"If you try to look up a nonexistent factoid,\n" +" this setting make the bot try to find some possible matching keys through\n" +" several approximate matching algorithms and return a list of matching keys,\n" +" before giving up." +msgstr "" + +#: config.py:69 msgid "$key could be $value." msgstr "" -#: config.py:64 +#: config.py:69 msgid "" "Determines the format of\n" " the response given when a factoid's value is requested. All the standard\n" @@ -56,7 +64,7 @@ msgid "" " for the factoid's value." msgstr "" -#: plugin.py:153 +#: plugin.py:179 msgid "" "[] %s \n" "\n" @@ -68,30 +76,85 @@ msgid "" " " msgstr "" -#: plugin.py:179 -msgid "That's not a valid number for that key." -msgstr "" - -#: plugin.py:199 plugin.py:345 -msgid "No factoid matches that key." -msgstr "" - -#: plugin.py:211 +#: plugin.py:199 #, docstring msgid "" -"[] []\n" -"\n" -" Looks up the value of in the factoid database. If given a\n" -" number, will return only that exact factoid. is only\n" -" necessary if the message isn't sent in the channel itself.\n" +"Try to typo-match input to possible factoids.\n" +" \n" +" Assume first letter is correct, to reduce processing time. \n" +" First, try a simple wildcard search.\n" +" If that fails, use the Damerau-Levenshtein edit-distance metric.\n" " " msgstr "" -#: plugin.py:222 plugin.py:273 plugin.py:384 +#: plugin.py:257 plugin.py:391 +msgid "That's not a valid number for that key." +msgstr "" + +#: plugin.py:279 plugin.py:377 plugin.py:604 +msgid "No factoid matches that key." +msgstr "" + +#: plugin.py:304 +#, docstring +msgid "" +"[] [--raw] []\n" +"\n" +" Looks up the value of in the factoid database. If given a\n" +" number, will return only that exact factoid. If '--raw' option is\n" +" given, no variable substitution will take place on the factoid.\n" +" is only necessary if the message isn't sent in the channel\n" +" itself.\n" +" " +msgstr "" + +#: plugin.py:321 plugin.py:529 msgid "key id" msgstr "" -#: plugin.py:230 +#: plugin.py:339 +#, docstring +msgid "" +"[] []\n" +"\n" +" Adds a new key for factoid associated with .\n" +" is only necessary if there's more than one factoid associated\n" +" with .\n" +"\n" +" The same action can be accomplished by using the 'learn' function with\n" +" a new key but an existing (verbatim) factoid content.\n" +" " +msgstr "" + +#: plugin.py:386 plugin.py:403 +msgid "This key-factoid relationship already exists." +msgstr "" + +#: plugin.py:394 +msgid "This key has more than one factoid associated with it, but you have not provided a number." +msgstr "" + +#: plugin.py:409 +#, docstring +msgid "" +"[] [--plain] [--alpha] []\n" +"\n" +" Returns a list of top-ranked factoid keys, sorted by usage count\n" +" (rank). If is not provided, the default number of factoid keys\n" +" returned is set by the rankListLength registry value.\n" +"\n" +" If --plain option is given, rank numbers and usage counts are not\n" +" included in output.\n" +"\n" +" If --alpha option is given in addition to --plain, keys are sorted\n" +" alphabetically, instead of by rank.\n" +"\n" +" is only necessary if the message isn't sent in the channel\n" +" itself.\n" +" " +msgstr "" + +#: plugin.py:454 #, docstring msgid "" "[] \n" @@ -102,7 +165,7 @@ msgid "" " " msgstr "" -#: plugin.py:245 +#: plugin.py:472 #, docstring msgid "" "[] \n" @@ -113,32 +176,37 @@ msgid "" " " msgstr "" -#: plugin.py:260 +#: plugin.py:511 #, docstring msgid "" "[] [|*]\n" "\n" -" Removes the factoid from the factoids database. If there are\n" -" more than one factoid with such a key, a number is necessary to\n" -" determine which one should be removed. A * can be used to remove all\n" -" factoids associated with a key. is only necessary if\n" +" Removes a key-fact relationship for key from the factoids\n" +" database. If there is more than one such relationship for this key,\n" +" a number is necessary to determine which one should be removed.\n" +" A * can be used to remove all relationships for .\n" +"\n" +" If as a result, the key (factoid) remains without any relationships to\n" +" a factoid (key), it shall be removed from the database.\n" +"\n" +" is only necessary if\n" " the message isn't sent in the channel itself.\n" " " msgstr "" -#: plugin.py:285 +#: plugin.py:543 msgid "There is no such factoid." msgstr "" -#: plugin.py:298 +#: plugin.py:553 msgid "Invalid factoid number." msgstr "" -#: plugin.py:304 +#: plugin.py:558 msgid "%s factoids have that key. Please specify which one to remove, or use * to designate all of them." msgstr "" -#: plugin.py:312 +#: plugin.py:566 #, docstring msgid "" "[]\n" @@ -148,11 +216,11 @@ msgid "" " " msgstr "" -#: plugin.py:330 +#: plugin.py:588 msgid "I couldn't find a factoid." msgstr "" -#: plugin.py:335 +#: plugin.py:593 #, docstring msgid "" "[] \n" @@ -163,11 +231,15 @@ msgid "" " " msgstr "" -#: plugin.py:358 -msgid "#%i was added by %s at %s" +#: plugin.py:619 +msgid "#%i was added by %s at %s, and has been recalled %n" msgstr "" -#: plugin.py:369 +#: plugin.py:622 +msgid "time" +msgstr "" + +#: plugin.py:632 #, docstring msgid "" "[] \n" @@ -177,11 +249,11 @@ msgid "" " " msgstr "" -#: plugin.py:381 +#: plugin.py:646 msgid "I couldn't find any key %q" msgstr "" -#: plugin.py:396 +#: plugin.py:661 #, docstring msgid "" "[] [--values] [--{regexp} ] [ ...]\n" @@ -192,11 +264,11 @@ msgid "" " " msgstr "" -#: plugin.py:431 +#: plugin.py:698 plugin.py:707 msgid "No keys matched that query." msgstr "" -#: plugin.py:436 +#: plugin.py:703 plugin.py:712 msgid "More than 100 keys matched that query; please narrow your query." msgstr "" diff --git a/plugins/Filter/messages.pot b/plugins/Filter/messages.pot index ffde04b05..1793959ea 100644 --- a/plugins/Filter/messages.pot +++ b/plugins/Filter/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-17 11:48+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -39,7 +39,7 @@ msgid "" " in a word before it will be shrunken by the shrink command/filter." msgstr "" -#: plugin.py:51 +#: plugin.py:50 #, docstring msgid "" "This plugin offers several commands which transform text in some way.\n" @@ -49,7 +49,7 @@ msgid "" " Not very useful, but definitely quite fun :)" msgstr "" -#: plugin.py:85 +#: plugin.py:84 #, docstring msgid "" "[] []\n" @@ -60,11 +60,11 @@ msgid "" " " msgstr "" -#: plugin.py:98 +#: plugin.py:97 msgid "That's not a valid filter command." msgstr "" -#: plugin.py:108 +#: plugin.py:107 #, docstring msgid "" "\n" @@ -75,7 +75,7 @@ msgid "" " " msgstr "" -#: plugin.py:120 +#: plugin.py:119 #, docstring msgid "" "\n" @@ -84,7 +84,7 @@ msgid "" " " msgstr "" -#: plugin.py:130 +#: plugin.py:129 #, docstring msgid "" "\n" @@ -93,7 +93,7 @@ msgid "" " " msgstr "" -#: plugin.py:143 +#: plugin.py:142 #, docstring msgid "" "\n" @@ -102,7 +102,17 @@ msgid "" " " msgstr "" -#: plugin.py:169 +#: plugin.py:168 +#, docstring +msgid "" +"\n" +"\n" +" Returns the character representation of binary .\n" +" Assumes ASCII, 8 digits per character.\n" +" " +msgstr "" + +#: plugin.py:179 #, docstring msgid "" "\n" @@ -112,7 +122,7 @@ msgid "" " " msgstr "" -#: plugin.py:179 +#: plugin.py:189 #, docstring msgid "" "\n" @@ -122,11 +132,11 @@ msgid "" " " msgstr "" -#: plugin.py:187 +#: plugin.py:197 msgid "Invalid input." msgstr "" -#: plugin.py:192 +#: plugin.py:202 #, docstring msgid "" "\n" @@ -137,7 +147,7 @@ msgid "" " " msgstr "" -#: plugin.py:203 +#: plugin.py:213 #, docstring msgid "" "\n" @@ -146,7 +156,7 @@ msgid "" " " msgstr "" -#: plugin.py:234 +#: plugin.py:244 #, docstring msgid "" "\n" @@ -155,7 +165,7 @@ msgid "" " " msgstr "" -#: plugin.py:254 +#: plugin.py:264 #, docstring msgid "" "\n" @@ -164,7 +174,7 @@ msgid "" " " msgstr "" -#: plugin.py:270 +#: plugin.py:280 #, docstring msgid "" "\n" @@ -174,7 +184,7 @@ msgid "" " " msgstr "" -#: plugin.py:335 +#: plugin.py:345 #, docstring msgid "" "\n" @@ -183,7 +193,7 @@ msgid "" " " msgstr "" -#: plugin.py:352 +#: plugin.py:362 #, docstring msgid "" "\n" @@ -192,7 +202,7 @@ msgid "" " " msgstr "" -#: plugin.py:364 +#: plugin.py:374 #, docstring msgid "" "\n" @@ -201,7 +211,7 @@ msgid "" " " msgstr "" -#: plugin.py:381 +#: plugin.py:391 #, docstring msgid "" "\n" @@ -210,7 +220,7 @@ msgid "" " " msgstr "" -#: plugin.py:391 +#: plugin.py:401 #, docstring msgid "" "\n" @@ -219,7 +229,7 @@ msgid "" " " msgstr "" -#: plugin.py:402 +#: plugin.py:412 #, docstring msgid "" "\n" @@ -228,7 +238,7 @@ msgid "" " " msgstr "" -#: plugin.py:411 +#: plugin.py:421 #, docstring msgid "" "\n" @@ -237,7 +247,7 @@ msgid "" " " msgstr "" -#: plugin.py:438 +#: plugin.py:448 #, docstring msgid "" "\n" @@ -246,279 +256,279 @@ msgid "" " " msgstr "" -#: plugin.py:534 +#: plugin.py:544 msgid "ay" msgstr "" -#: plugin.py:534 +#: plugin.py:544 msgid "bee" msgstr "" -#: plugin.py:534 +#: plugin.py:544 msgid "dee" msgstr "" -#: plugin.py:534 +#: plugin.py:544 msgid "see" msgstr "" -#: plugin.py:535 +#: plugin.py:545 msgid "aych" msgstr "" -#: plugin.py:535 +#: plugin.py:545 msgid "ee" msgstr "" -#: plugin.py:535 +#: plugin.py:545 msgid "eff" msgstr "" -#: plugin.py:535 +#: plugin.py:545 msgid "gee" msgstr "" -#: plugin.py:536 +#: plugin.py:546 msgid "ell" msgstr "" -#: plugin.py:536 +#: plugin.py:546 msgid "eye" msgstr "" -#: plugin.py:536 +#: plugin.py:546 msgid "jay" msgstr "" -#: plugin.py:536 +#: plugin.py:546 msgid "kay" msgstr "" -#: plugin.py:537 +#: plugin.py:547 msgid "cue" msgstr "" -#: plugin.py:537 +#: plugin.py:547 msgid "em" msgstr "" -#: plugin.py:537 +#: plugin.py:547 msgid "en" msgstr "" -#: plugin.py:537 +#: plugin.py:547 msgid "oh" msgstr "" -#: plugin.py:537 +#: plugin.py:547 msgid "pee" msgstr "" -#: plugin.py:538 +#: plugin.py:548 msgid "arr" msgstr "" -#: plugin.py:538 +#: plugin.py:548 msgid "ess" msgstr "" -#: plugin.py:538 +#: plugin.py:548 msgid "tee" msgstr "" -#: plugin.py:538 +#: plugin.py:548 msgid "you" msgstr "" -#: plugin.py:539 +#: plugin.py:549 msgid "double-you" msgstr "" -#: plugin.py:539 +#: plugin.py:549 msgid "ecks" msgstr "" -#: plugin.py:539 +#: plugin.py:549 msgid "vee" msgstr "" -#: plugin.py:539 +#: plugin.py:549 msgid "why" msgstr "" -#: plugin.py:540 +#: plugin.py:550 msgid "zee" msgstr "" -#: plugin.py:545 +#: plugin.py:555 msgid "exclamation point" msgstr "" -#: plugin.py:546 +#: plugin.py:556 msgid "quote" msgstr "" -#: plugin.py:547 +#: plugin.py:557 msgid "pound" msgstr "" -#: plugin.py:548 +#: plugin.py:558 msgid "dollar sign" msgstr "" -#: plugin.py:549 +#: plugin.py:559 msgid "percent" msgstr "" -#: plugin.py:550 +#: plugin.py:560 msgid "ampersand" msgstr "" -#: plugin.py:551 +#: plugin.py:561 msgid "single quote" msgstr "" -#: plugin.py:552 +#: plugin.py:562 msgid "left paren" msgstr "" -#: plugin.py:553 +#: plugin.py:563 msgid "right paren" msgstr "" -#: plugin.py:554 +#: plugin.py:564 msgid "asterisk" msgstr "" -#: plugin.py:555 +#: plugin.py:565 msgid "plus" msgstr "" -#: plugin.py:556 +#: plugin.py:566 msgid "comma" msgstr "" -#: plugin.py:557 +#: plugin.py:567 msgid "minus" msgstr "" -#: plugin.py:558 +#: plugin.py:568 msgid "period" msgstr "" -#: plugin.py:559 +#: plugin.py:569 msgid "slash" msgstr "" -#: plugin.py:560 +#: plugin.py:570 msgid "colon" msgstr "" -#: plugin.py:561 +#: plugin.py:571 msgid "semicolon" msgstr "" -#: plugin.py:562 +#: plugin.py:572 msgid "less than" msgstr "" -#: plugin.py:563 +#: plugin.py:573 msgid "equals" msgstr "" -#: plugin.py:564 +#: plugin.py:574 msgid "greater than" msgstr "" -#: plugin.py:565 +#: plugin.py:575 msgid "question mark" msgstr "" -#: plugin.py:566 +#: plugin.py:576 msgid "at" msgstr "" -#: plugin.py:567 +#: plugin.py:577 msgid "left bracket" msgstr "" -#: plugin.py:568 +#: plugin.py:578 msgid "backslash" msgstr "" -#: plugin.py:569 +#: plugin.py:579 msgid "right bracket" msgstr "" -#: plugin.py:570 +#: plugin.py:580 msgid "caret" msgstr "" -#: plugin.py:571 +#: plugin.py:581 msgid "underscore" msgstr "" -#: plugin.py:572 +#: plugin.py:582 msgid "backtick" msgstr "" -#: plugin.py:573 +#: plugin.py:583 msgid "left brace" msgstr "" -#: plugin.py:574 +#: plugin.py:584 msgid "pipe" msgstr "" -#: plugin.py:575 +#: plugin.py:585 msgid "right brace" msgstr "" -#: plugin.py:576 +#: plugin.py:586 msgid "tilde" msgstr "" -#: plugin.py:579 +#: plugin.py:589 msgid "one" msgstr "" -#: plugin.py:579 +#: plugin.py:589 msgid "three" msgstr "" -#: plugin.py:579 +#: plugin.py:589 msgid "two" msgstr "" -#: plugin.py:579 +#: plugin.py:589 msgid "zero" msgstr "" -#: plugin.py:580 +#: plugin.py:590 msgid "five" msgstr "" -#: plugin.py:580 +#: plugin.py:590 msgid "four" msgstr "" -#: plugin.py:580 +#: plugin.py:590 msgid "seven" msgstr "" -#: plugin.py:580 +#: plugin.py:590 msgid "six" msgstr "" -#: plugin.py:581 +#: plugin.py:591 msgid "eight" msgstr "" -#: plugin.py:581 +#: plugin.py:591 msgid "nine" msgstr "" -#: plugin.py:585 +#: plugin.py:595 #, docstring msgid "" "\n" @@ -527,7 +537,7 @@ msgid "" " " msgstr "" -#: plugin.py:615 +#: plugin.py:625 #, docstring msgid "" "\n" @@ -536,7 +546,7 @@ msgid "" " " msgstr "" -#: plugin.py:624 +#: plugin.py:634 #, docstring msgid "" "\n" @@ -547,7 +557,7 @@ msgid "" " " msgstr "" -#: plugin.py:643 +#: plugin.py:653 #, docstring msgid "" "\n" @@ -556,7 +566,7 @@ msgid "" " " msgstr "" -#: plugin.py:692 +#: plugin.py:702 #, docstring msgid "" "\n" diff --git a/plugins/Format/messages.pot b/plugins/Format/messages.pot index 960c03a14..26459323f 100644 --- a/plugins/Format/messages.pot +++ b/plugins/Format/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-17 12:46+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -79,9 +79,10 @@ msgstr "" #: plugin.py:103 #, docstring msgid "" -"\n" +" \n" "\n" -" Returns uppercased.\n" +" Replaces all non-overlapping occurrences of \n" +" with in .\n" " " msgstr "" @@ -90,7 +91,7 @@ msgstr "" msgid "" "\n" "\n" -" Returns lowercased.\n" +" Returns uppercased.\n" " " msgstr "" @@ -99,7 +100,7 @@ msgstr "" msgid "" "\n" "\n" -" Returns capitalized.\n" +" Returns lowercased.\n" " " msgstr "" @@ -108,7 +109,7 @@ msgstr "" msgid "" "\n" "\n" -" Returns titlecased.\n" +" Returns capitalized.\n" " " msgstr "" @@ -117,13 +118,22 @@ msgstr "" msgid "" "\n" "\n" -" Returns the text surrounded by double quotes.\n" +" Returns titlecased.\n" " " msgstr "" #: plugin.py:148 #, docstring msgid "" +"\n" +"\n" +" Returns the text surrounded by double quotes.\n" +" " +msgstr "" + +#: plugin.py:157 +#, docstring +msgid "" " \n" "\n" " Concatenates two strings. Do keep in mind that this is *not* the same\n" @@ -132,7 +142,7 @@ msgid "" " " msgstr "" -#: plugin.py:159 +#: plugin.py:168 #, docstring msgid "" " \n" @@ -143,7 +153,7 @@ msgid "" " " msgstr "" -#: plugin.py:170 +#: plugin.py:179 #, docstring msgid "" " \n" @@ -153,7 +163,7 @@ msgid "" " " msgstr "" -#: plugin.py:183 +#: plugin.py:192 #, docstring msgid "" " [ ...]\n" @@ -164,7 +174,7 @@ msgid "" " " msgstr "" -#: plugin.py:197 +#: plugin.py:206 msgid "Not enough arguments for the format string." msgstr "" diff --git a/plugins/Games/messages.pot b/plugins/Games/messages.pot index 98aa2cc61..3f15674d2 100644 --- a/plugins/Games/messages.pot +++ b/plugins/Games/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-17 13:16+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/Google/messages.pot b/plugins/Google/messages.pot index 460da8a27..d2ce05407 100644 --- a/plugins/Google/messages.pot +++ b/plugins/Google/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2011-02-14 18:30+CET\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -27,12 +27,12 @@ msgstr "" msgid "Do you want the Google search snarfer enabled by default?" msgstr "" -#: config.py:88 +#: config.py:89 #, docstring msgid "Value must be 1 <= n <= 8" msgstr "" -#: config.py:99 +#: config.py:100 msgid "" "Determines the URL that will be sent to Google for\n" " the Referer field of the search requests. If this value is empty, a\n" @@ -40,7 +40,7 @@ msgid "" " http://$server/$botName" msgstr "" -#: config.py:104 +#: config.py:105 msgid "" "Determines whether the search snarfer is\n" " enabled. If so, messages (even unaddressed ones) beginning with the word\n" @@ -48,33 +48,33 @@ msgid "" " channel." msgstr "" -#: config.py:109 +#: config.py:110 msgid "" "Determines whether the word 'google' in the\n" " bot's output will be made colorful (like Google's logo)." msgstr "" -#: config.py:112 +#: config.py:113 msgid "Determines whether results are bolded." msgstr "" -#: config.py:114 +#: config.py:115 msgid "" "Determines the maximum number of results returned\n" " from the google command." msgstr "" -#: config.py:117 +#: config.py:118 msgid "" "Determines what default language is used in\n" " searches. If left empty, no specific language will be requested." msgstr "" -#: config.py:117 +#: config.py:118 msgid "en" msgstr "" -#: config.py:120 +#: config.py:121 msgid "" "Determines what level of search filtering to use\n" " by default. 'active' - most filtering, 'moderate' - default filtering,\n" @@ -174,12 +174,12 @@ msgstr "" msgid "to language" msgstr "" -#: plugin.py:297 +#: plugin.py:314 #, docstring msgid "^google\\s+(.*)$" msgstr "" -#: plugin.py:320 +#: plugin.py:336 #, docstring msgid "" "\n" @@ -188,11 +188,11 @@ msgid "" " " msgstr "" -#: plugin.py:337 +#: plugin.py:353 msgid "Google's calculator didn't come up with anything." msgstr "" -#: plugin.py:343 +#: plugin.py:359 #, docstring msgid "" "\n" @@ -201,7 +201,7 @@ msgid "" " " msgstr "" -#: plugin.py:357 +#: plugin.py:373 msgid "Google's phonebook didn't come up with anything." msgstr "" diff --git a/plugins/Herald/messages.pot b/plugins/Herald/messages.pot index 61aec5e7d..50ebc64fc 100644 --- a/plugins/Herald/messages.pot +++ b/plugins/Herald/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-17 15:21+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/Internet/messages.pot b/plugins/Internet/messages.pot index 5f6f82699..22b7025df 100644 --- a/plugins/Internet/messages.pot +++ b/plugins/Internet/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-17 15:20+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -15,12 +15,12 @@ msgstr "" "Generated-By: pygettext.py 1.5\n" -#: plugin.py:43 +#: plugin.py:42 #, docstring msgid "Add the help for \"@help Internet\" here." msgstr "" -#: plugin.py:47 +#: plugin.py:46 #, docstring msgid "" "\n" @@ -29,11 +29,11 @@ msgid "" " " msgstr "" -#: plugin.py:54 plugin.py:61 plugin.py:65 +#: plugin.py:53 plugin.py:60 plugin.py:64 msgid "Host not found." msgstr "" -#: plugin.py:77 +#: plugin.py:76 #, docstring msgid "" "\n" @@ -42,39 +42,39 @@ msgid "" " " msgstr "" -#: plugin.py:83 +#: plugin.py:82 msgid "domain" msgstr "" -#: plugin.py:112 +#: plugin.py:111 msgid "updated %s" msgstr "" -#: plugin.py:115 +#: plugin.py:114 msgid "registered %s" msgstr "" -#: plugin.py:118 +#: plugin.py:117 msgid "expires %s" msgstr "" -#: plugin.py:138 +#: plugin.py:137 msgid " " msgstr "" -#: plugin.py:140 +#: plugin.py:139 msgid " " msgstr "" -#: plugin.py:145 +#: plugin.py:144 msgid "%s%s is %L." msgstr "" -#: plugin.py:148 +#: plugin.py:147 msgid "I couldn't find such a domain." msgstr "" -#: plugin.py:153 +#: plugin.py:152 #, docstring msgid "" "\n" diff --git a/plugins/Karma/messages.pot b/plugins/Karma/messages.pot index 265354adb..19a85d552 100644 --- a/plugins/Karma/messages.pot +++ b/plugins/Karma/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-28 15:19+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -51,11 +51,11 @@ msgid "" " increase/decrease karma without being addressed." msgstr "" -#: plugin.py:243 plugin.py:251 +#: plugin.py:247 plugin.py:255 msgid "You're not allowed to adjust your own karma." msgstr "" -#: plugin.py:280 +#: plugin.py:284 #, docstring msgid "" "[] [ ...]\n" @@ -69,35 +69,35 @@ msgid "" " " msgstr "" -#: plugin.py:293 +#: plugin.py:297 msgid "%s has neutral karma." msgstr "" -#: plugin.py:300 +#: plugin.py:304 msgid "Karma for %q has been increased %n and decreased %n for a total karma of %s." msgstr "" -#: plugin.py:302 plugin.py:303 +#: plugin.py:306 plugin.py:307 msgid "time" msgstr "" -#: plugin.py:316 +#: plugin.py:320 msgid "I didn't know the karma for any of those things." msgstr "" -#: plugin.py:326 plugin.py:355 +#: plugin.py:330 plugin.py:359 msgid "I have no karma for this channel." msgstr "" -#: plugin.py:331 +#: plugin.py:335 msgid " You (%s) are ranked %i out of %i." msgstr "" -#: plugin.py:335 +#: plugin.py:339 msgid "Highest karma: %L. Lowest karma: %L.%s" msgstr "" -#: plugin.py:343 +#: plugin.py:347 #, docstring msgid "" "[] {increased,decreased,active}\n" @@ -108,7 +108,7 @@ msgid "" " " msgstr "" -#: plugin.py:361 +#: plugin.py:365 #, docstring msgid "" "[] \n" @@ -117,7 +117,7 @@ msgid "" " " msgstr "" -#: plugin.py:371 +#: plugin.py:375 #, docstring msgid "" "[] \n" @@ -128,7 +128,7 @@ msgid "" " " msgstr "" -#: plugin.py:383 +#: plugin.py:387 #, docstring msgid "" "[] \n" diff --git a/plugins/Lart/messages.pot b/plugins/Lart/messages.pot index a290a2605..52f05d432 100644 --- a/plugins/Lart/messages.pot +++ b/plugins/Lart/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-17 15:21+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/Later/messages.pot b/plugins/Later/messages.pot index 1e4023bc7..f2a8597af 100644 --- a/plugins/Later/messages.pot +++ b/plugins/Later/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-28 16:15+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -28,6 +28,19 @@ msgid "" " the first place in which they're seen, or in private." msgstr "" +#: config.py:52 +msgid "" +"Determines whether users will be notified upon\n" +" joining any channel the bot is in, or only upon sending a message." +msgstr "" + +#: config.py:55 +msgid "" +"Determines the maximum number of\n" +" days that a message will remain queued for a user. After this time elapses,\n" +" the message will be deleted. If this value is 0, there is no maximum." +msgstr "" + #: plugin.py:46 #, docstring msgid "" @@ -45,7 +58,23 @@ msgstr "" msgid "just now" msgstr "" -#: plugin.py:107 +#: plugin.py:106 +#, docstring +msgid "" +"Validate nick according to the IRC RFC 2812 spec.\n" +"\n" +" Reference: http://tools.ietf.org/rfcmarkup?doc=2812#section-2.3.1\n" +"\n" +" Some irc clients' tab-completion feature appends 'address' characters\n" +" to nick, such as ':' or ','. We try correcting for that by trimming\n" +" a char off the end.\n" +"\n" +" If nick incorrigibly invalid, return False, otherwise,\n" +" return (possibly trimmed) nick.\n" +" " +msgstr "" + +#: plugin.py:151 #, docstring msgid "" " \n" @@ -56,15 +85,15 @@ msgid "" " " msgstr "" -#: plugin.py:114 +#: plugin.py:159 msgid "I can't send notes to myself." msgstr "" -#: plugin.py:120 +#: plugin.py:169 msgid "That person's message queue is already full." msgstr "" -#: plugin.py:125 +#: plugin.py:174 #, docstring msgid "" "[]\n" @@ -74,19 +103,19 @@ msgid "" " " msgstr "" -#: plugin.py:136 +#: plugin.py:185 msgid "I have no notes for that nick." msgstr "" -#: plugin.py:141 +#: plugin.py:190 msgid "I currently have notes waiting for %L." msgstr "" -#: plugin.py:144 +#: plugin.py:193 msgid "I have no notes waiting to be delivered." msgstr "" -#: plugin.py:149 +#: plugin.py:198 #, docstring msgid "" "\n" @@ -95,11 +124,11 @@ msgid "" " " msgstr "" -#: plugin.py:158 +#: plugin.py:207 msgid "There were no notes for %r" msgstr "" -#: plugin.py:182 +#: plugin.py:231 msgid "Sent %s: <%s> %s" msgstr "" diff --git a/plugins/Limiter/messages.pot b/plugins/Limiter/messages.pot index 4ec916ea3..3dcf93fe1 100644 --- a/plugins/Limiter/messages.pot +++ b/plugins/Limiter/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-17 15:35+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -36,7 +36,7 @@ msgid "" " larger than supybot.plugins.Limiter.limit.minimumExcess." msgstr "" -#: plugin.py:40 +#: plugin.py:39 #, docstring msgid "" "In order to use this plugin, its config values need to be properly\n" diff --git a/plugins/Math/messages.pot b/plugins/Math/messages.pot index 9f2932cc0..a781772bb 100644 --- a/plugins/Math/messages.pot +++ b/plugins/Math/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-17 15:35+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/MessageParser/messages.pot b/plugins/MessageParser/messages.pot new file mode 100644 index 000000000..b220ea4eb --- /dev/null +++ b/plugins/MessageParser/messages.pot @@ -0,0 +1,152 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: ENCODING\n" +"Generated-By: pygettext.py 1.5\n" + + +#: plugin.py:65 +#, docstring +msgid "" +"This plugin can set regexp triggers to activate the bot.\n" +" Use 'add' command to add regexp trigger, 'remove' to remove." +msgstr "" + +#: plugin.py:73 +#, docstring +msgid "Create the database and connect to it." +msgstr "" + +#: plugin.py:96 +#, docstring +msgid "Use this to get a database for a specific channel." +msgstr "" + +#: plugin.py:119 +#, docstring +msgid "Run a command from message, as if command was sent over IRC." +msgstr "" + +#: plugin.py:127 +#, docstring +msgid "" +"Check if the user has any of the required capabilities to manage\n" +" the regexp database." +msgstr "" + +#: plugin.py:168 +#, docstring +msgid "" +"[] \n" +"\n" +" Associates with . is only\n" +" necessary if the message isn't sent on the channel\n" +" itself. Action is echoed upon regexp match, with variables $1, $2, \n" +" etc. being interpolated from the regexp match groups." +msgstr "" + +#: plugin.py:207 +#, docstring +msgid "" +"[] [--id] ]\n" +"\n" +" Removes the trigger for from the triggers database. \n" +" is only necessary if\n" +" the message isn't sent in the channel itself.\n" +" If option --id specified, will retrieve by regexp id, not content.\n" +" " +msgstr "" + +#: plugin.py:244 +#, docstring +msgid "" +"[] \n" +"\n" +" Locks the so that it cannot be\n" +" removed or overwritten to. is only necessary if the message isn't\n" +" sent in the channel itself.\n" +" " +msgstr "" + +#: plugin.py:266 +#, docstring +msgid "" +"[] \n" +"\n" +" Unlocks the entry associated with so that it can be\n" +" removed or overwritten. is only necessary if the message isn't\n" +" sent in the channel itself.\n" +" " +msgstr "" + +#: plugin.py:288 +#, docstring +msgid "" +"[] [--id] \n" +"\n" +" Looks up the value of in the triggers database.\n" +" is only necessary if the message isn't sent in the channel \n" +" itself.\n" +" If option --id specified, will retrieve by regexp id, not content.\n" +" " +msgstr "" + +#: plugin.py:316 +#, docstring +msgid "" +"[] [--id] \n" +"\n" +" Display information about in the triggers database.\n" +" is only necessary if the message isn't sent in the channel \n" +" itself.\n" +" If option --id specified, will retrieve by regexp id, not content.\n" +" " +msgstr "" + +#: plugin.py:354 +#, docstring +msgid "" +"[]\n" +"\n" +" Lists regexps present in the triggers database.\n" +" is only necessary if the message isn't sent in the channel \n" +" itself. Regexp ID listed in paretheses.\n" +" " +msgstr "" + +#: plugin.py:376 +#, docstring +msgid "" +"[]\n" +" \n" +" Returns a list of top-ranked regexps, sorted by usage count \n" +" (rank). The number of regexps returned is set by the \n" +" rankListLength registry value. is only necessary if the \n" +" message isn't sent in the channel itself.\n" +" " +msgstr "" + +#: plugin.py:399 +#, docstring +msgid "" +"[]\n" +" \n" +" Vacuums the database for .\n" +" See SQLite vacuum doc here: http://www.sqlite.org/lang_vacuum.html\n" +" is only necessary if the message isn't sent in \n" +" the channel itself.\n" +" First check if user has the required capability specified in plugin \n" +" config requireVacuumCapability.\n" +" " +msgstr "" + diff --git a/plugins/Misc/messages.pot b/plugins/Misc/messages.pot index 59d3b15a6..61ee56d81 100644 --- a/plugins/Misc/messages.pot +++ b/plugins/Misc/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-17 15:35+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -45,15 +45,15 @@ msgid "" " command" msgstr "" -#: plugin.py:81 +#: plugin.py:80 msgid "You've given me %s invalid commands within the last minute; I'm now ignoring you for %s." msgstr "" -#: plugin.py:93 +#: plugin.py:92 msgid "The %q plugin is loaded, but there is no command named %q in it. Try \"list %s\" to see the commands in the %q plugin." msgstr "" -#: plugin.py:119 +#: plugin.py:118 #, docstring msgid "" "[--private] []\n" @@ -64,19 +64,19 @@ msgid "" " " msgstr "" -#: plugin.py:144 +#: plugin.py:143 msgid "There are no private plugins." msgstr "" -#: plugin.py:146 +#: plugin.py:145 msgid "There are no public plugins." msgstr "" -#: plugin.py:153 +#: plugin.py:152 msgid "That plugin exists, but has no commands. This probably means that it has some configuration variables that can be changed in order to modify its behavior. Try \"config list supybot.plugins.%s\" to see what configuration variables it has." msgstr "" -#: plugin.py:164 +#: plugin.py:163 #, docstring msgid "" "\n" @@ -86,11 +86,11 @@ msgid "" " " msgstr "" -#: plugin.py:183 +#: plugin.py:182 msgid "No appropriate commands were found." msgstr "" -#: plugin.py:188 +#: plugin.py:187 #, docstring msgid "" "[] []\n" @@ -100,15 +100,15 @@ msgid "" " " msgstr "" -#: plugin.py:198 +#: plugin.py:197 msgid "That command exists in the %L plugins. Please specify exactly which plugin command you want help with." msgstr "" -#: plugin.py:205 +#: plugin.py:204 msgid "There is no command %q." msgstr "" -#: plugin.py:211 +#: plugin.py:210 #, docstring msgid "" "takes no arguments\n" @@ -117,19 +117,19 @@ msgid "" " " msgstr "" -#: plugin.py:217 +#: plugin.py:216 msgid "The newest version available online is %s." msgstr "" -#: plugin.py:221 +#: plugin.py:220 msgid "I couldn't fetch the newest version from the Supybot website." msgstr "" -#: plugin.py:223 +#: plugin.py:222 msgid "The current (running) version of this Supybot is %s. %s" msgstr "" -#: plugin.py:230 +#: plugin.py:229 #, docstring msgid "" "takes no arguments\n" @@ -138,11 +138,11 @@ msgid "" " " msgstr "" -#: plugin.py:234 +#: plugin.py:233 msgid "My source is at http://supybot.com/" msgstr "" -#: plugin.py:239 +#: plugin.py:238 #, docstring msgid "" "[]\n" @@ -154,23 +154,31 @@ msgid "" " " msgstr "" -#: plugin.py:253 +#: plugin.py:252 msgid "%s has no public mores." msgstr "" -#: plugin.py:256 +#: plugin.py:255 msgid "Sorry, I can't find any mores for %s" msgstr "" -#: plugin.py:265 +#: plugin.py:262 +msgid "more message" +msgstr "" + +#: plugin.py:264 +msgid "more messages" +msgstr "" + +#: plugin.py:268 msgid "You haven't asked me a command; perhaps you want to see someone else's more. To do so, call this command with that person's nick." msgstr "" -#: plugin.py:269 +#: plugin.py:272 msgid "That's all, there is no more." msgstr "" -#: plugin.py:279 +#: plugin.py:282 #, docstring msgid "" "[--{from,in,on,with,without,regexp} ] [--nolimit]\n" @@ -185,11 +193,11 @@ msgid "" " " msgstr "" -#: plugin.py:373 +#: plugin.py:376 msgid "I couldn't find a message matching that criteria in my history of %s messages." msgstr "" -#: plugin.py:388 +#: plugin.py:391 #, docstring msgid "" " \n" @@ -199,23 +207,23 @@ msgid "" " " msgstr "" -#: plugin.py:396 +#: plugin.py:399 msgid "Dude, just give the command. No need for the tell." msgstr "" -#: plugin.py:401 +#: plugin.py:404 msgid "You just told me, why should I tell myself?" msgstr "" -#: plugin.py:406 +#: plugin.py:409 msgid "I haven't seen %s, I'll let you do the telling." msgstr "" -#: plugin.py:411 +#: plugin.py:414 msgid "%s wants me to tell you: %s" msgstr "" -#: plugin.py:417 +#: plugin.py:420 #, docstring msgid "" "takes no arguments\n" @@ -224,7 +232,7 @@ msgid "" " " msgstr "" -#: plugin.py:421 +#: plugin.py:424 msgid "pong" msgstr "" diff --git a/plugins/MoobotFactoids/messages.pot b/plugins/MoobotFactoids/messages.pot index ce53fb9a3..d3f78b91f 100644 --- a/plugins/MoobotFactoids/messages.pot +++ b/plugins/MoobotFactoids/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-29 12:02+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -28,38 +28,38 @@ msgid "" " when the 'most' command is called." msgstr "" -#: plugin.py:292 +#: plugin.py:289 #, docstring msgid "" "Add the help for \"@help MoobotFactoids\" here (assuming you don't implement a MoobotFactoids\n" " command). This should describe *how* to use this plugin." msgstr "" -#: plugin.py:349 +#: plugin.py:346 msgid "%s is %s" msgstr "" -#: plugin.py:368 +#: plugin.py:365 msgid "Factoid %q is locked." msgstr "" -#: plugin.py:375 +#: plugin.py:372 msgid "Factoid %q not found." msgstr "" -#: plugin.py:385 +#: plugin.py:382 msgid "Missing an 'is' or '_is_'." msgstr "" -#: plugin.py:401 +#: plugin.py:398 msgid "Factoid %q already exists." msgstr "" -#: plugin.py:435 +#: plugin.py:432 msgid "%s, or %s" msgstr "" -#: plugin.py:456 +#: plugin.py:453 #, docstring msgid "" "[] \n" @@ -70,7 +70,7 @@ msgid "" " " msgstr "" -#: plugin.py:469 +#: plugin.py:466 #, docstring msgid "" "[] \n" @@ -81,39 +81,39 @@ msgid "" " " msgstr "" -#: plugin.py:480 plugin.py:520 +#: plugin.py:477 plugin.py:517 msgid "No such factoid: %q" msgstr "" -#: plugin.py:489 +#: plugin.py:486 msgid "Created by %s on %s." msgstr "" -#: plugin.py:495 +#: plugin.py:492 msgid " Last modified by %s on %s." msgstr "" -#: plugin.py:503 +#: plugin.py:500 msgid " Last requested by %s on %s, requested %n." msgstr "" -#: plugin.py:510 +#: plugin.py:507 msgid " Locked by %s on %s." msgstr "" -#: plugin.py:525 +#: plugin.py:522 msgid "Factoid %q is already locked." msgstr "" -#: plugin.py:528 +#: plugin.py:525 msgid "Factoid %q is not locked." msgstr "" -#: plugin.py:538 +#: plugin.py:535 msgid "Cannot %s someone else's factoid unless you are an admin." msgstr "" -#: plugin.py:550 +#: plugin.py:547 #, docstring msgid "" "[] \n" @@ -124,7 +124,7 @@ msgid "" " " msgstr "" -#: plugin.py:561 +#: plugin.py:558 #, docstring msgid "" "[] \n" @@ -135,7 +135,7 @@ msgid "" " " msgstr "" -#: plugin.py:572 +#: plugin.py:569 #, docstring msgid "" "[] {popular|authored|recent}\n" @@ -148,51 +148,51 @@ msgid "" " " msgstr "" -#: plugin.py:594 +#: plugin.py:591 msgid "author" msgstr "" -#: plugin.py:596 +#: plugin.py:593 msgid "authors" msgstr "" -#: plugin.py:597 +#: plugin.py:594 msgid "Most prolific %s: %L" msgstr "" -#: plugin.py:599 plugin.py:611 +#: plugin.py:596 plugin.py:608 msgid "There are no factoids in my database." msgstr "" -#: plugin.py:606 +#: plugin.py:603 msgid "latest factoid" msgstr "" -#: plugin.py:608 +#: plugin.py:605 msgid "latest factoids" msgstr "" -#: plugin.py:609 +#: plugin.py:606 msgid "%s: %L" msgstr "" -#: plugin.py:618 +#: plugin.py:615 msgid "requested factoid" msgstr "" -#: plugin.py:620 +#: plugin.py:617 msgid "requested factoids" msgstr "" -#: plugin.py:621 +#: plugin.py:618 msgid "Top %s: %L" msgstr "" -#: plugin.py:623 +#: plugin.py:620 msgid "No factoids have been requested from my database." msgstr "" -#: plugin.py:627 +#: plugin.py:624 #, docstring msgid "" "[] \n" @@ -204,15 +204,15 @@ msgid "" " " msgstr "" -#: plugin.py:640 +#: plugin.py:637 msgid "No factoids by %q found." msgstr "" -#: plugin.py:643 +#: plugin.py:640 msgid "Author search for %q (%i found): %L" msgstr "" -#: plugin.py:650 +#: plugin.py:647 #, docstring msgid "" "[] \n" @@ -223,15 +223,15 @@ msgid "" " " msgstr "" -#: plugin.py:658 +#: plugin.py:655 msgid "No keys matching %q found." msgstr "" -#: plugin.py:665 +#: plugin.py:662 msgid "Key search for %q (%i found): %L" msgstr "" -#: plugin.py:672 +#: plugin.py:669 #, docstring msgid "" "[] \n" @@ -242,15 +242,15 @@ msgid "" " " msgstr "" -#: plugin.py:680 +#: plugin.py:677 msgid "No values matching %q found." msgstr "" -#: plugin.py:683 +#: plugin.py:680 msgid "Value search for %q (%i found): %L" msgstr "" -#: plugin.py:690 +#: plugin.py:687 #, docstring msgid "" "[] \n" @@ -260,7 +260,7 @@ msgid "" " " msgstr "" -#: plugin.py:703 +#: plugin.py:700 #, docstring msgid "" "[]\n" @@ -271,7 +271,7 @@ msgid "" " " msgstr "" -#: plugin.py:711 +#: plugin.py:708 msgid "No factoids in the database." msgstr "" diff --git a/plugins/Network/messages.pot b/plugins/Network/messages.pot index e368ae1f0..f896b6813 100644 --- a/plugins/Network/messages.pot +++ b/plugins/Network/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-16 12:52+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/News/messages.pot b/plugins/News/messages.pot index 9a77314ef..daf0616b5 100644 --- a/plugins/News/messages.pot +++ b/plugins/News/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-17 16:53+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/NickCapture/messages.pot b/plugins/NickCapture/messages.pot index 46813d8b7..8029cb756 100644 --- a/plugins/NickCapture/messages.pot +++ b/plugins/NickCapture/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-17 16:58+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -27,7 +27,7 @@ msgid "" " will check whether its nick ISON." msgstr "" -#: plugin.py:41 +#: plugin.py:40 #, docstring msgid "" "This module constantly tries to take whatever nick is configured as\n" @@ -35,7 +35,7 @@ msgid "" " will do the rest." msgstr "" -#: plugin.py:90 +#: plugin.py:89 #, docstring msgid "This is returned by the ISON command." msgstr "" diff --git a/plugins/Nickometer/messages.pot b/plugins/Nickometer/messages.pot index fd8dfe20c..42e1d7d36 100644 --- a/plugins/Nickometer/messages.pot +++ b/plugins/Nickometer/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-17 18:28+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/Note/messages.pot b/plugins/Note/messages.pot index 51be4c044..6e7837a06 100644 --- a/plugins/Note/messages.pot +++ b/plugins/Note/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-29 13:54+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -15,7 +15,7 @@ msgstr "" "Generated-By: pygettext.py 1.5\n" -#: plugin.py:179 +#: plugin.py:181 #, docstring msgid "" ",[,[...]] \n" @@ -25,7 +25,7 @@ msgid "" " " msgstr "" -#: plugin.py:195 +#: plugin.py:197 #, docstring msgid "" " \n" @@ -34,7 +34,7 @@ msgid "" " " msgstr "" -#: plugin.py:219 +#: plugin.py:221 #, docstring msgid "" "\n" @@ -44,7 +44,7 @@ msgid "" " " msgstr "" -#: plugin.py:251 +#: plugin.py:253 #, docstring msgid "" "\n" @@ -54,7 +54,7 @@ msgid "" " " msgstr "" -#: plugin.py:281 +#: plugin.py:283 #, docstring msgid "" "[--{regexp} ] [--sent] []\n" @@ -65,7 +65,7 @@ msgid "" " " msgstr "" -#: plugin.py:320 +#: plugin.py:322 #, docstring msgid "" "[--{old,sent}] [--{from,to} ]\n" @@ -77,7 +77,7 @@ msgid "" " " msgstr "" -#: plugin.py:361 +#: plugin.py:363 #, docstring msgid "" "takes no arguments\n" diff --git a/plugins/Owner/messages.pot b/plugins/Owner/messages.pot index 537cb4a05..8f5e189d3 100644 --- a/plugins/Owner/messages.pot +++ b/plugins/Owner/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-29 14:47+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -15,7 +15,7 @@ msgstr "" "Generated-By: pygettext.py 1.5\n" -#: plugin.py:270 +#: plugin.py:273 #, docstring msgid "" "\n" @@ -25,7 +25,7 @@ msgid "" " " msgstr "" -#: plugin.py:280 +#: plugin.py:283 #, docstring msgid "" "\n" @@ -35,7 +35,7 @@ msgid "" " " msgstr "" -#: plugin.py:295 +#: plugin.py:298 #, docstring msgid "" "[--remove] []\n" @@ -47,7 +47,7 @@ msgid "" " " msgstr "" -#: plugin.py:333 +#: plugin.py:336 #, docstring msgid "" "\n" @@ -56,7 +56,7 @@ msgid "" " " msgstr "" -#: plugin.py:347 +#: plugin.py:350 #, docstring msgid "" "[]\n" @@ -67,7 +67,7 @@ msgid "" " " msgstr "" -#: plugin.py:363 +#: plugin.py:366 #, docstring msgid "" "takes no arguments\n" @@ -77,7 +77,7 @@ msgid "" " " msgstr "" -#: plugin.py:373 +#: plugin.py:376 #, docstring msgid "" "[]\n" @@ -89,7 +89,7 @@ msgid "" " " msgstr "" -#: plugin.py:412 +#: plugin.py:415 #, docstring msgid "" "[--deprecated] \n" @@ -101,7 +101,7 @@ msgid "" " " msgstr "" -#: plugin.py:447 +#: plugin.py:450 #, docstring msgid "" "\n" @@ -111,7 +111,7 @@ msgid "" " " msgstr "" -#: plugin.py:476 +#: plugin.py:479 #, docstring msgid "" "\n" @@ -122,7 +122,7 @@ msgid "" " " msgstr "" -#: plugin.py:500 +#: plugin.py:503 #, docstring msgid "" "{add|remove} \n" @@ -133,7 +133,7 @@ msgid "" " " msgstr "" -#: plugin.py:525 +#: plugin.py:528 #, docstring msgid "" "[] \n" @@ -146,7 +146,7 @@ msgid "" " " msgstr "" -#: plugin.py:552 +#: plugin.py:555 #, docstring msgid "" "[] \n" @@ -157,7 +157,7 @@ msgid "" " " msgstr "" -#: plugin.py:571 +#: plugin.py:574 #, docstring msgid "" " \n" @@ -166,7 +166,7 @@ msgid "" " " msgstr "" -#: plugin.py:588 +#: plugin.py:591 #, docstring msgid "" "\n" @@ -176,3 +176,11 @@ msgid "" " " msgstr "" +#: plugin.py:604 +#, docstring +msgid "" +"takes no argument\n" +"\n" +" Reloads the locale of the bot." +msgstr "" + diff --git a/plugins/Plugin/messages.pot b/plugins/Plugin/messages.pot index 3e978d574..6aa2d618f 100644 --- a/plugins/Plugin/messages.pot +++ b/plugins/Plugin/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-16 13:50+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -15,7 +15,7 @@ msgstr "" "Generated-By: pygettext.py 1.5\n" -#: plugin.py:43 +#: plugin.py:42 #, docstring msgid "" "This plugin exists to help users manage their plugins. Use 'plugin\n" @@ -24,7 +24,7 @@ msgid "" " command exists in." msgstr "" -#: plugin.py:49 +#: plugin.py:48 #, docstring msgid "" "\n" @@ -34,11 +34,11 @@ msgid "" " " msgstr "" -#: plugin.py:58 +#: plugin.py:57 msgid "That plugin is loaded, but has no plugin help." msgstr "" -#: plugin.py:63 +#: plugin.py:62 #, docstring msgid "" "takes no arguments\n" @@ -47,32 +47,44 @@ msgid "" " " msgstr "" -#: plugin.py:74 +#: plugin.py:73 #, docstring msgid "" "\n" "\n" -" Returns the plugin(s) that is in.\n" +" Returns the name of the plugin that would be used to call .\n" +" \n" +" If it is not uniquely determined, returns list of all plugins that\n" +" contain .\n" " " msgstr "" -#: plugin.py:89 +#: plugin.py:91 msgid "plugins" msgstr "" -#: plugin.py:91 +#: plugin.py:93 msgid "plugin" msgstr "" -#: plugin.py:92 +#: plugin.py:94 msgid "The %q command is available in the %L %s." msgstr "" -#: plugin.py:95 +#: plugin.py:97 msgid "There is no command %q." msgstr "" -#: plugin.py:100 +#: plugin.py:113 +#, docstring +msgid "" +"\n" +"\n" +" Returns the names of all plugins that contain .\n" +" " +msgstr "" + +#: plugin.py:135 #, docstring msgid "" "\n" @@ -82,15 +94,15 @@ msgid "" " " msgstr "" -#: plugin.py:106 +#: plugin.py:141 msgid "That plugin does not seem to be loaded." msgstr "" -#: plugin.py:112 +#: plugin.py:147 msgid "That plugin doesn't have an author that claims it." msgstr "" -#: plugin.py:117 +#: plugin.py:152 #, docstring msgid "" " []\n" @@ -102,7 +114,7 @@ msgid "" " " msgstr "" -#: plugin.py:125 +#: plugin.py:160 #, docstring msgid "" "\n" @@ -111,7 +123,7 @@ msgid "" " " msgstr "" -#: plugin.py:131 +#: plugin.py:166 #, docstring msgid "" "\n" @@ -120,7 +132,7 @@ msgid "" " " msgstr "" -#: plugin.py:138 +#: plugin.py:173 #, docstring msgid "" "\n" @@ -129,7 +141,7 @@ msgid "" " " msgstr "" -#: plugin.py:148 +#: plugin.py:183 #, docstring msgid "" "\n" @@ -138,39 +150,39 @@ msgid "" " " msgstr "" -#: plugin.py:152 +#: plugin.py:187 msgid "The %s plugin" msgstr "" -#: plugin.py:153 +#: plugin.py:188 msgid "has not been claimed by an author" msgstr "" -#: plugin.py:154 +#: plugin.py:189 msgid "and" msgstr "" -#: plugin.py:155 +#: plugin.py:190 msgid "has no contributors listed." msgstr "" -#: plugin.py:160 +#: plugin.py:195 msgid "was written by %s" msgstr "" -#: plugin.py:171 +#: plugin.py:206 msgid "%s %h contributed to it." msgstr "" -#: plugin.py:176 +#: plugin.py:211 msgid "has no additional contributors listed." msgstr "" -#: plugin.py:178 +#: plugin.py:213 msgid "but" msgstr "" -#: plugin.py:181 +#: plugin.py:216 #, docstring msgid "" "\n" @@ -179,39 +191,39 @@ msgid "" " " msgstr "" -#: plugin.py:195 +#: plugin.py:230 msgid "The nick specified (%s) is not a registered contributor." msgstr "" -#: plugin.py:201 +#: plugin.py:236 msgid "The %s plugin does not have '%s' listed as a contributor." msgstr "" -#: plugin.py:209 +#: plugin.py:244 msgid "command" msgstr "" -#: plugin.py:212 +#: plugin.py:247 msgid "the %L %s" msgstr "" -#: plugin.py:214 +#: plugin.py:249 msgid "the %L" msgstr "" -#: plugin.py:217 +#: plugin.py:252 msgid "%s wrote the %s plugin and also contributed %L." msgstr "" -#: plugin.py:220 +#: plugin.py:255 msgid "%s contributed %L to the %s plugin." msgstr "" -#: plugin.py:223 +#: plugin.py:258 msgid "%s wrote the %s plugin" msgstr "" -#: plugin.py:226 +#: plugin.py:261 msgid "%s has no listed contributions for the %s plugin." msgstr "" diff --git a/plugins/Praise/messages.pot b/plugins/Praise/messages.pot index 81a2b8502..f17b44cfb 100644 --- a/plugins/Praise/messages.pot +++ b/plugins/Praise/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-17 18:33+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -21,7 +21,7 @@ msgid "" " a praise when the praise is given." msgstr "" -#: plugin.py:40 +#: plugin.py:39 #, docstring msgid "" "Praise is a plugin for ... well, praising things. Feel free to add\n" @@ -31,11 +31,11 @@ msgid "" " " msgstr "" -#: plugin.py:54 +#: plugin.py:53 msgid "Praises must contain $who." msgstr "" -#: plugin.py:58 +#: plugin.py:57 #, docstring msgid "" "[] [] [for ]\n" @@ -46,15 +46,15 @@ msgid "" " " msgstr "" -#: plugin.py:74 +#: plugin.py:73 msgid "There is no praise with id #%i." msgstr "" -#: plugin.py:79 +#: plugin.py:78 msgid "There are no praises in my database for %s." msgstr "" -#: plugin.py:87 +#: plugin.py:86 msgid " for " msgstr "" diff --git a/plugins/Protector/messages.pot b/plugins/Protector/messages.pot index 0bb646f0a..fa97aea1f 100644 --- a/plugins/Protector/messages.pot +++ b/plugins/Protector/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-17 18:34+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/Quote/messages.pot b/plugins/Quote/messages.pot index 7d85fdf6e..fe018e400 100644 --- a/plugins/Quote/messages.pot +++ b/plugins/Quote/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-17 18:34+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/QuoteGrabs/messages.pot b/plugins/QuoteGrabs/messages.pot index 9b365e4ee..3f67237ac 100644 --- a/plugins/QuoteGrabs/messages.pot +++ b/plugins/QuoteGrabs/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-17 18:36+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -44,16 +44,16 @@ msgid "" " random grabbing." msgstr "" -#: plugin.py:57 +#: plugin.py:66 msgid "%s (Said by: %s; grabbed by %s at %t)" msgstr "" -#: plugin.py:210 +#: plugin.py:226 #, docstring msgid "Add the help for \"@help QuoteGrabs\" here." msgstr "" -#: plugin.py:249 +#: plugin.py:265 #, docstring msgid "" "[] \n" @@ -64,15 +64,15 @@ msgid "" " " msgstr "" -#: plugin.py:262 +#: plugin.py:278 msgid "You can't quote grab yourself." msgstr "" -#: plugin.py:269 +#: plugin.py:285 msgid "I couldn't find a proper message to grab." msgstr "" -#: plugin.py:274 +#: plugin.py:290 #, docstring msgid "" "[] \n" @@ -83,15 +83,15 @@ msgid "" " " msgstr "" -#: plugin.py:285 +#: plugin.py:301 msgid "Nothing to ungrab." msgstr "" -#: plugin.py:287 +#: plugin.py:303 msgid "Invalid grab number." msgstr "" -#: plugin.py:292 +#: plugin.py:308 #, docstring msgid "" "[] \n" @@ -101,11 +101,11 @@ msgid "" " " msgstr "" -#: plugin.py:300 +#: plugin.py:316 msgid "I couldn't find a matching quotegrab for %s." msgstr "" -#: plugin.py:306 +#: plugin.py:322 #, docstring msgid "" "[] \n" @@ -117,11 +117,11 @@ msgid "" " " msgstr "" -#: plugin.py:323 +#: plugin.py:339 msgid "I couldn't find any quotegrabs for %s." msgstr "" -#: plugin.py:329 +#: plugin.py:345 #, docstring msgid "" "[] []\n" @@ -132,15 +132,15 @@ msgid "" " " msgstr "" -#: plugin.py:339 +#: plugin.py:355 msgid "Couldn't get a random quote for that nick." msgstr "" -#: plugin.py:341 +#: plugin.py:357 msgid "Couldn't get a random quote. Are there any grabbed quotes in the database?" msgstr "" -#: plugin.py:347 +#: plugin.py:363 #, docstring msgid "" "[] \n" @@ -150,11 +150,11 @@ msgid "" " " msgstr "" -#: plugin.py:355 +#: plugin.py:371 msgid "No quotegrab for id %s" msgstr "" -#: plugin.py:361 +#: plugin.py:377 #, docstring msgid "" "[] \n" @@ -164,7 +164,7 @@ msgid "" " " msgstr "" -#: plugin.py:376 +#: plugin.py:392 msgid "No quotegrabs matching %s" msgstr "" diff --git a/plugins/RSS/messages.pot b/plugins/RSS/messages.pot index 4a2b76686..9885ca62f 100644 --- a/plugins/RSS/messages.pot +++ b/plugins/RSS/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-19 19:27+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,20 +17,20 @@ msgstr "" #: config.py:50 msgid "" -"Determines whether the bot will bold the title of the feed when it\n" -" announces new news." +"Determines whether the bot will bold the title of the feed when\n" +" it announces new news." msgstr "" #: config.py:53 msgid "" -"Determines what string is used\n" -" to separate headlines in new feeds." +"Determines what string is\n" +" used to separate headlines in new feeds." msgstr "" #: config.py:56 msgid "" -"Determines what prefix\n" -" is prepended (if any) to the new news item announcements made in the\n" +"Determines what\n" +" prefix is prepended (if any) to the new news item announcements made in the\n" " channel." msgstr "" @@ -62,14 +62,14 @@ msgid "" " listed when a feed is automatically announced." msgstr "" -#: config.py:78 +#: config.py:84 msgid "" "Determines whether the bot will list the link\n" " along with the title of the feed when a feed is automatically\n" " announced." msgstr "" -#: plugin.py:63 +#: plugin.py:62 #, docstring msgid "" "This plugin is useful both for announcing updates to RSS feeds in a\n" @@ -78,7 +78,7 @@ msgid "" " command to determine what feeds should be announced in a given channel." msgstr "" -#: plugin.py:311 +#: plugin.py:316 #, docstring msgid "" " \n" @@ -88,7 +88,7 @@ msgid "" " " msgstr "" -#: plugin.py:322 +#: plugin.py:327 #, docstring msgid "" "\n" @@ -98,15 +98,25 @@ msgid "" " " msgstr "" -#: plugin.py:328 +#: plugin.py:333 msgid "That's not a valid RSS feed command name." msgstr "" -#: plugin.py:346 -msgid "I am currently not announcing any feeds." +#: plugin.py:344 +#, docstring +msgid "" +"[]\n" +"\n" +" Returns the list of feeds announced in . is\n" +" only necessary if the message isn't sent in the channel itself.\n" +" " msgstr "" #: plugin.py:351 +msgid "I am currently not announcing any feeds." +msgstr "" + +#: plugin.py:356 #, docstring msgid "" "[] [ ...]\n" @@ -118,7 +128,7 @@ msgid "" " " msgstr "" -#: plugin.py:369 +#: plugin.py:374 #, docstring msgid "" "[] [ ...]\n" @@ -130,7 +140,7 @@ msgid "" " " msgstr "" -#: plugin.py:387 +#: plugin.py:392 #, docstring msgid "" " []\n" @@ -140,11 +150,11 @@ msgid "" " " msgstr "" -#: plugin.py:400 +#: plugin.py:405 msgid "Couldn't get RSS feed." msgstr "" -#: plugin.py:413 +#: plugin.py:420 #, docstring msgid "" "\n" @@ -154,11 +164,11 @@ msgid "" " " msgstr "" -#: plugin.py:426 +#: plugin.py:433 msgid "I couldn't retrieve that RSS feed." msgstr "" -#: plugin.py:439 +#: plugin.py:446 msgid "Title: %s; URL: %u; Description: %s; Last updated: %s." msgstr "" diff --git a/plugins/Relay/messages.pot b/plugins/Relay/messages.pot index 5757bcdc1..6c44750bc 100644 --- a/plugins/Relay/messages.pot +++ b/plugins/Relay/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-19 19:27+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/Reply/messages.pot b/plugins/Reply/messages.pot index fac59c514..29b3d2c2a 100644 --- a/plugins/Reply/messages.pot +++ b/plugins/Reply/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-19 19:27+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -15,7 +15,7 @@ msgstr "" "Generated-By: pygettext.py 1.5\n" -#: plugin.py:38 +#: plugin.py:37 #, docstring msgid "" "This plugins contains a few commands that construct various types of\n" @@ -24,7 +24,7 @@ msgid "" " " msgstr "" -#: plugin.py:44 +#: plugin.py:43 #, docstring msgid "" "\n" @@ -34,7 +34,7 @@ msgid "" " " msgstr "" -#: plugin.py:54 +#: plugin.py:53 #, docstring msgid "" "\n" @@ -44,7 +44,7 @@ msgid "" " " msgstr "" -#: plugin.py:67 +#: plugin.py:66 #, docstring msgid "" "\n" @@ -54,7 +54,7 @@ msgid "" " " msgstr "" -#: plugin.py:77 +#: plugin.py:76 #, docstring msgid "" "\n" @@ -63,3 +63,13 @@ msgid "" " " msgstr "" +#: plugin.py:85 +#, docstring +msgid "" +" [ ...]\n" +"\n" +" Replies with each of its arguments in separate replies, depending\n" +" the configuration of supybot.reply.oneToOne.\n" +" " +msgstr "" + diff --git a/plugins/Scheduler/messages.pot b/plugins/Scheduler/messages.pot index 226ac4fe6..1ee1c9364 100644 --- a/plugins/Scheduler/messages.pot +++ b/plugins/Scheduler/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-19 19:28+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -15,12 +15,12 @@ msgstr "" "Generated-By: pygettext.py 1.5\n" -#: plugin.py:47 +#: plugin.py:111 #, docstring msgid "Makes a function suitable for scheduling from command." msgstr "" -#: plugin.py:57 +#: plugin.py:131 #, docstring msgid "" " \n" @@ -33,11 +33,11 @@ msgid "" " " msgstr "" -#: plugin.py:69 +#: plugin.py:141 msgid "Event #%i added." msgstr "" -#: plugin.py:74 +#: plugin.py:146 #, docstring msgid "" "\n" @@ -46,11 +46,11 @@ msgid "" " " msgstr "" -#: plugin.py:88 plugin.py:90 +#: plugin.py:160 plugin.py:162 msgid "Invalid event id." msgstr "" -#: plugin.py:95 +#: plugin.py:176 #, docstring msgid "" " \n" @@ -62,11 +62,11 @@ msgid "" " " msgstr "" -#: plugin.py:104 +#: plugin.py:185 msgid "There is already an event with that name, please choose another name." msgstr "" -#: plugin.py:117 +#: plugin.py:195 #, docstring msgid "" "takes no arguments\n" @@ -75,7 +75,7 @@ msgid "" " " msgstr "" -#: plugin.py:128 +#: plugin.py:209 msgid "There are currently no scheduled commands." msgstr "" diff --git a/plugins/Seen/messages.pot b/plugins/Seen/messages.pot index e1186b0f1..24e0ab3bd 100644 --- a/plugins/Seen/messages.pot +++ b/plugins/Seen/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-20 08:52+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/Services/messages.pot b/plugins/Services/messages.pot index 05b81d37a..27bdb4754 100644 --- a/plugins/Services/messages.pot +++ b/plugins/Services/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-20 08:55+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -101,7 +101,7 @@ msgid "" " voiced by the ChanServ when it joins the channel." msgstr "" -#: plugin.py:48 +#: plugin.py:47 #, docstring msgid "" "This plugin handles dealing with Services on networks that provide them.\n" @@ -114,11 +114,11 @@ msgid "" " necessary if the bot is properly configured." msgstr "" -#: plugin.py:396 +#: plugin.py:397 msgid "You must set supybot.plugins.Services.ChanServ before I'm able to send the %s command." msgstr "" -#: plugin.py:402 +#: plugin.py:403 #, docstring msgid "" "[]\n" @@ -128,11 +128,11 @@ msgid "" " " msgstr "" -#: plugin.py:408 +#: plugin.py:409 msgid "I'm already opped in %s." msgstr "" -#: plugin.py:415 +#: plugin.py:416 #, docstring msgid "" "[]\n" @@ -142,11 +142,11 @@ msgid "" " " msgstr "" -#: plugin.py:421 +#: plugin.py:422 msgid "I'm already voiced in %s." msgstr "" -#: plugin.py:438 +#: plugin.py:439 #, docstring msgid "" "[]\n" @@ -158,7 +158,7 @@ msgid "" " " msgstr "" -#: plugin.py:459 +#: plugin.py:460 #, docstring msgid "" "[]\n" @@ -170,7 +170,7 @@ msgid "" " " msgstr "" -#: plugin.py:480 +#: plugin.py:481 #, docstring msgid "" "takes no arguments\n" @@ -179,15 +179,15 @@ msgid "" " " msgstr "" -#: plugin.py:489 +#: plugin.py:490 msgid "I don't have a configured password for my current nick." msgstr "" -#: plugin.py:492 +#: plugin.py:493 msgid "You must set supybot.plugins.Services.NickServ before I'm able to do identify." msgstr "" -#: plugin.py:498 +#: plugin.py:499 #, docstring msgid "" "[]\n" @@ -197,15 +197,15 @@ msgid "" " " msgstr "" -#: plugin.py:507 +#: plugin.py:508 msgid "I cowardly refuse to ghost myself." msgstr "" -#: plugin.py:512 +#: plugin.py:513 msgid "You must set supybot.plugins.Services.NickServ before I'm able to ghost a nick." msgstr "" -#: plugin.py:518 +#: plugin.py:519 #, docstring msgid "" " []\n" @@ -215,11 +215,11 @@ msgid "" " " msgstr "" -#: plugin.py:528 +#: plugin.py:529 msgid "That nick was not configured with a password." msgstr "" -#: plugin.py:539 +#: plugin.py:540 #, docstring msgid "" "takes no arguments\n" @@ -229,7 +229,7 @@ msgid "" " " msgstr "" -#: plugin.py:549 +#: plugin.py:550 msgid "I'm not currently configured for any nicks." msgstr "" diff --git a/plugins/ShrinkUrl/messages.pot b/plugins/ShrinkUrl/messages.pot index 433b2cd45..25532222d 100644 --- a/plugins/ShrinkUrl/messages.pot +++ b/plugins/ShrinkUrl/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-20 08:55+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/Status/messages.pot b/plugins/Status/messages.pot index c186802a1..cdbf6390e 100644 --- a/plugins/Status/messages.pot +++ b/plugins/Status/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-20 09:05+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -81,7 +81,7 @@ msgid "an indeterminate amount of time" msgstr "" #: plugin.py:112 -msgid "I have received %s messages for a total of %s bytes. I have sent %s messages for a total of %s bytes. I have been connected to %s for %s." +msgid "I have received %s messages for a total of %S. I have sent %s messages for a total of %S. I have been connected to %s for %s." msgstr "" #: plugin.py:121 @@ -106,7 +106,7 @@ msgid "Unable to run ps command." msgstr "" #: plugin.py:166 -msgid " I'm taking up %s kB of memory." +msgid " I'm taking up %S of memory." msgstr "" #: plugin.py:174 diff --git a/plugins/String/messages.pot b/plugins/String/messages.pot index 1d593ab4b..dd1a9db63 100644 --- a/plugins/String/messages.pot +++ b/plugins/String/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-20 09:08+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/Success/messages.pot b/plugins/Success/messages.pot index 361535e0e..0ebb40756 100644 --- a/plugins/Success/messages.pot +++ b/plugins/Success/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-20 09:09+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -21,7 +21,7 @@ msgid "" " of the user giving an invalid command to the success response." msgstr "" -#: plugin.py:39 +#: plugin.py:38 #, docstring msgid "" "This plugin was written initially to work with MoobotFactoids, the two\n" diff --git a/plugins/Time/messages.pot b/plugins/Time/messages.pot index 8a3c7f883..fd786294f 100644 --- a/plugins/Time/messages.pot +++ b/plugins/Time/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-11-17 09:46+CET\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/Todo/messages.pot b/plugins/Todo/messages.pot index d4830d9e9..b4df3840c 100644 --- a/plugins/Todo/messages.pot +++ b/plugins/Todo/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-20 09:38+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/Topic/messages.pot b/plugins/Topic/messages.pot index 5b224355c..b6eb7aa78 100644 --- a/plugins/Topic/messages.pot +++ b/plugins/Topic/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-30 12:23+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -53,41 +53,41 @@ msgid "" " topics to keep around in case the undo command is called." msgstr "" -#: config.py:70 +#: config.py:69 msgid "" -"Determines the \n" +"Determines the\n" " capabilities required (if any) to make any topic changes,\n" -" (everything except for read-only operations). Use 'channel,capab' for \n" +" (everything except for read-only operations). Use 'channel,capab' for\n" " channel-level capabilities.\n" -" Note that absence of an explicit anticapability means user has \n" +" Note that absence of an explicit anticapability means user has\n" " capability." msgstr "" -#: plugin.py:55 +#: plugin.py:57 msgid "I'm not currently in %s." msgstr "" -#: plugin.py:59 +#: plugin.py:61 msgid "I can't change the topic, I'm not opped and %s is +t." msgstr "" -#: plugin.py:66 +#: plugin.py:68 msgid "The topic must not include %q." msgstr "" -#: plugin.py:77 +#: plugin.py:79 msgid "topic number" msgstr "" -#: plugin.py:90 +#: plugin.py:92 msgid "There are no topics in %s." msgstr "" -#: plugin.py:198 +#: plugin.py:200 msgid "That topic is too long for this server (maximum length: %i; this topic: %i)." msgstr "" -#: plugin.py:212 +#: plugin.py:213 #, docstring msgid "" "Check if the user has any of the required capabilities to manage\n" @@ -101,7 +101,7 @@ msgid "" " " msgstr "" -#: plugin.py:264 +#: plugin.py:265 #, docstring msgid "" "[]\n" @@ -111,7 +111,7 @@ msgid "" " " msgstr "" -#: plugin.py:275 +#: plugin.py:276 #, docstring msgid "" "[] \n" @@ -121,7 +121,7 @@ msgid "" " " msgstr "" -#: plugin.py:290 +#: plugin.py:291 #, docstring msgid "" "[] \n" @@ -133,7 +133,7 @@ msgid "" " " msgstr "" -#: plugin.py:307 +#: plugin.py:308 #, docstring msgid "" "[] \n" @@ -142,7 +142,7 @@ msgid "" " " msgstr "" -#: plugin.py:321 +#: plugin.py:322 #, docstring msgid "" "[] \n" @@ -153,7 +153,7 @@ msgid "" " " msgstr "" -#: plugin.py:337 +#: plugin.py:338 #, docstring msgid "" "[]\n" @@ -163,11 +163,11 @@ msgid "" " " msgstr "" -#: plugin.py:347 +#: plugin.py:348 msgid "I can't shuffle 1 or fewer topics." msgstr "" -#: plugin.py:359 +#: plugin.py:360 #, docstring msgid "" "[] [ ...]\n" @@ -179,19 +179,19 @@ msgid "" " " msgstr "" -#: plugin.py:372 +#: plugin.py:373 msgid "I cannot reorder 1 or fewer topics." msgstr "" -#: plugin.py:374 +#: plugin.py:375 msgid "All topic numbers must be specified." msgstr "" -#: plugin.py:376 +#: plugin.py:377 msgid "Duplicate topic numbers cannot be specified." msgstr "" -#: plugin.py:384 +#: plugin.py:385 #, docstring msgid "" "[]\n" @@ -202,11 +202,11 @@ msgid "" " " msgstr "" -#: plugin.py:393 +#: plugin.py:394 msgid "%i: %s" msgstr "" -#: plugin.py:400 +#: plugin.py:401 #, docstring msgid "" "[] \n" @@ -217,7 +217,7 @@ msgid "" " " msgstr "" -#: plugin.py:415 +#: plugin.py:416 #, docstring msgid "" "[] \n" @@ -230,7 +230,7 @@ msgid "" " " msgstr "" -#: plugin.py:433 +#: plugin.py:434 #, docstring msgid "" "[] [] \n" @@ -241,7 +241,7 @@ msgid "" " " msgstr "" -#: plugin.py:454 +#: plugin.py:455 #, docstring msgid "" "[] \n" @@ -253,7 +253,7 @@ msgid "" " " msgstr "" -#: plugin.py:471 +#: plugin.py:472 #, docstring msgid "" "[]\n" @@ -263,11 +263,11 @@ msgid "" " " msgstr "" -#: plugin.py:481 +#: plugin.py:482 msgid "lock the topic" msgstr "" -#: plugin.py:485 +#: plugin.py:486 #, docstring msgid "" "[]\n" @@ -277,11 +277,11 @@ msgid "" " " msgstr "" -#: plugin.py:495 +#: plugin.py:496 msgid "unlock the topic" msgstr "" -#: plugin.py:499 +#: plugin.py:500 #, docstring msgid "" "[]\n" @@ -291,11 +291,11 @@ msgid "" " " msgstr "" -#: plugin.py:510 +#: plugin.py:511 msgid "I haven't yet set the topic in %s." msgstr "" -#: plugin.py:518 +#: plugin.py:519 #, docstring msgid "" "[]\n" @@ -306,11 +306,11 @@ msgid "" " " msgstr "" -#: plugin.py:532 +#: plugin.py:533 msgid "There are no more undos for %s." msgstr "" -#: plugin.py:537 +#: plugin.py:538 #, docstring msgid "" "[]\n" @@ -320,11 +320,11 @@ msgid "" " " msgstr "" -#: plugin.py:549 +#: plugin.py:550 msgid "There are no redos for %s." msgstr "" -#: plugin.py:554 +#: plugin.py:555 #, docstring msgid "" "[] \n" @@ -335,11 +335,11 @@ msgid "" " " msgstr "" -#: plugin.py:565 +#: plugin.py:566 msgid "I refuse to swap the same topic with itself." msgstr "" -#: plugin.py:575 +#: plugin.py:576 #, docstring msgid "" "[]\n" @@ -350,11 +350,11 @@ msgid "" " " msgstr "" -#: plugin.py:588 +#: plugin.py:589 msgid "There is no default topic configured for %s." msgstr "" -#: plugin.py:594 +#: plugin.py:595 #, docstring msgid "" "[] \n" diff --git a/plugins/URL/messages.pot b/plugins/URL/messages.pot index 574c38160..e9581dddb 100644 --- a/plugins/URL/messages.pot +++ b/plugins/URL/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-20 09:38+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/Unix/messages.pot b/plugins/Unix/messages.pot index f828bf19b..d871bdd01 100644 --- a/plugins/Unix/messages.pot +++ b/plugins/Unix/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-20 09:38+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -74,7 +74,7 @@ msgid "" " command will be called for the wtf command." msgstr "" -#: plugin.py:73 +#: plugin.py:75 #, docstring msgid "" "\n" @@ -83,19 +83,19 @@ msgid "" " " msgstr "" -#: plugin.py:85 +#: plugin.py:87 msgid "I can't find the errno number for that code." msgstr "" -#: plugin.py:88 +#: plugin.py:90 msgid "(unknown)" msgstr "" -#: plugin.py:89 +#: plugin.py:91 msgid "%s (#%i): %s" msgstr "" -#: plugin.py:94 +#: plugin.py:96 #, docstring msgid "" "takes no arguments\n" @@ -104,7 +104,7 @@ msgid "" " " msgstr "" -#: plugin.py:102 +#: plugin.py:104 #, docstring msgid "" "takes no arguments\n" @@ -113,7 +113,7 @@ msgid "" " " msgstr "" -#: plugin.py:112 +#: plugin.py:114 #, docstring msgid "" " []\n" @@ -125,7 +125,7 @@ msgid "" " " msgstr "" -#: plugin.py:131 +#: plugin.py:133 #, docstring msgid "" "\n" @@ -136,31 +136,31 @@ msgid "" " " msgstr "" -#: plugin.py:140 +#: plugin.py:142 msgid "The spell checking command is not configured. If one is installed, reconfigure supybot.plugins.Unix.spell.command appropriately." msgstr "" -#: plugin.py:145 +#: plugin.py:147 msgid " must begin with an alphabet character." msgstr "" -#: plugin.py:167 +#: plugin.py:169 msgid "No results found." msgstr "" -#: plugin.py:178 +#: plugin.py:180 msgid "%q may be spelled correctly." msgstr "" -#: plugin.py:180 +#: plugin.py:182 msgid "I could not find an alternate spelling for %q" msgstr "" -#: plugin.py:184 +#: plugin.py:186 msgid "Possible spellings for %q: %L." msgstr "" -#: plugin.py:193 +#: plugin.py:195 #, docstring msgid "" "takes no arguments\n" @@ -169,15 +169,15 @@ msgid "" " " msgstr "" -#: plugin.py:213 +#: plugin.py:215 msgid "It seems the configured fortune command was not available." msgstr "" -#: plugin.py:222 +#: plugin.py:224 msgid "The fortune command is not configured. If fortune is installed on this system, reconfigure the supybot.plugins.Unix.fortune.command configuration variable appropriately." msgstr "" -#: plugin.py:229 +#: plugin.py:231 #, docstring msgid "" "[is] \n" @@ -188,11 +188,35 @@ msgid "" " " msgstr "" -#: plugin.py:244 +#: plugin.py:246 msgid "It seems the configured wtf command was not available." msgstr "" -#: plugin.py:253 +#: plugin.py:255 msgid "The wtf command is not configured. If it is installed on this system, reconfigure the supybot.plugins.Unix.wtf.command configuration variable appropriately." msgstr "" +#: plugin.py:263 +#, docstring +msgid "" +"[--c ] [--i ] [--t ] [--W ] \n" +" Sends an ICMP echo request to the specified host.\n" +" The arguments correspond with those listed in ping(8). --c is\n" +" limited to 10 packets or less (default is 5). --i is limited to 5\n" +" or less. --W is limited to 10 or less.\n" +" " +msgstr "" + +#: plugin.py:315 +#, docstring +msgid "" +" \n" +" Calls any command available on the system, and returns its output.\n" +" Requires owner capability.\n" +" Note that being restricted to owner, this command does not do any\n" +" sanity checking on input/output. So it is up to you to make sure\n" +" you don't run anything that will spamify your channel or that \n" +" will bring your machine to its knees. \n" +" " +msgstr "" + diff --git a/plugins/User/messages.pot b/plugins/User/messages.pot index 6ddaeff76..4f2171590 100644 --- a/plugins/User/messages.pot +++ b/plugins/User/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-30 11:23+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/Utilities/messages.pot b/plugins/Utilities/messages.pot index f31c687e6..ce46a62da 100644 --- a/plugins/Utilities/messages.pot +++ b/plugins/Utilities/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2011-02-20 11:29+CET\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/plugins/Web/messages.pot b/plugins/Web/messages.pot index fcd22e9c4..a3b21333f 100644 --- a/plugins/Web/messages.pot +++ b/plugins/Web/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2010-10-20 09:39+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -79,11 +79,11 @@ msgid "" msgstr "" #: plugin.py:157 plugin.py:162 -msgid "%u is %i bytes long." +msgid "%u is %S long." msgstr "" #: plugin.py:164 -msgid "The server didn't tell me how long %u is but it's longer than %i bytes." +msgid "The server didn't tell me how long %u is but it's longer than %S." msgstr "" #: plugin.py:173 @@ -100,7 +100,7 @@ msgid "That URL appears to have no HTML title." msgstr "" #: plugin.py:190 -msgid "That URL appears to have no HTML title within the first %i bytes." +msgid "That URL appears to have no HTML title within the first %S." msgstr "" #: plugin.py:198 From 2de895cf9be6e3eb585d5ac1947b7d6fc3e130ba Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 26 Feb 2011 10:43:12 +0100 Subject: [PATCH 62/85] Alias: update l10n-fr --- plugins/Alias/locale/fr.po | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/Alias/locale/fr.po b/plugins/Alias/locale/fr.po index 87c3bacbe..068f09224 100644 --- a/plugins/Alias/locale/fr.po +++ b/plugins/Alias/locale/fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Supybot-fr\n" -"POT-Creation-Date: 2010-10-16 14:10+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Supybot-fr \n" @@ -39,6 +39,10 @@ msgstr "" "\n" "Alias pour %q." +#: plugin.py:166 +msgid "argument" +msgstr "argument" + #: plugin.py:220 msgid "" "\n" From c5de801317d5ac722fa36f53d74dd5ba9c36eafe Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 26 Feb 2011 10:43:20 +0100 Subject: [PATCH 63/85] Channel: update l10n-fr --- plugins/Channel/locale/fr.po | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/plugins/Channel/locale/fr.po b/plugins/Channel/locale/fr.po index b63a969fd..d9fc7bdfe 100644 --- a/plugins/Channel/locale/fr.po +++ b/plugins/Channel/locale/fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Supybot-fr\n" -"POT-Creation-Date: 2010-10-25 13:10+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Supybot-fr \n" @@ -644,35 +644,36 @@ msgstr "%s n'était pas désactivé." #: plugin.py:818 msgid "" -"[]\n" +"[] [--count]\n" "\n" " Returns the nicks in . is only necessary if the\n" -" message isn't sent in the channel itself.\n" +" message isn't sent in the channel itself. Returns only the number of\n" +" nicks if --count option is provided.\n" " " msgstr "" -"[]\n" +"[] [--count]\n" "\n" -"Retourne les nick sur le . n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." +"Retourne les nick sur le . n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même. Ne retourne que le nombre de nicks si --count est donné." -#: plugin.py:829 +#: plugin.py:830 msgid "You don't have access to that information." msgstr "Vous n'avez pas accès à cette information" -#: plugin.py:837 +#: plugin.py:843 msgid "" "Internal message for notifying all the #channel,ops in a channel of\n" " a given situation." msgstr "Message interne pour notifier tous les #canal,ops sur un canal d'une situation donnée." -#: plugin.py:840 +#: plugin.py:846 msgid "Alert to all %s ops: %s" msgstr "Alerte à tous les ops de %s : %s" -#: plugin.py:842 +#: plugin.py:848 msgid " (from %s)" msgstr "(de %s)" -#: plugin.py:850 +#: plugin.py:856 msgid "" "[] \n" "\n" From a3ff40d3597df9cdd036e6f87449a98c69bf04e5 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 26 Feb 2011 10:43:28 +0100 Subject: [PATCH 64/85] Ctcp: update l10n-fr --- plugins/Ctcp/locale/fr.po | 41 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/plugins/Ctcp/locale/fr.po b/plugins/Ctcp/locale/fr.po index 0b9193541..2dba46d88 100644 --- a/plugins/Ctcp/locale/fr.po +++ b/plugins/Ctcp/locale/fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Supybot-fr\n" -"POT-Creation-Date: 2010-10-26 18:57+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Supybot-fr \n" @@ -13,7 +13,46 @@ msgstr "" "X-Poedit-Country: France\n" "X-Poedit-SourceCharset: ASCII\n" +#: plugin.py:77 +msgid "PING ?(.*)" +msgstr "" + +#: plugin.py:86 +msgid "VERSION" +msgstr "" + +#: plugin.py:91 +msgid "USERINFO" +msgstr "" + +#: plugin.py:96 +msgid "TIME" +msgstr "" + +#: plugin.py:101 +msgid "FINGER" +msgstr "" + #: plugin.py:104 msgid "Supybot, the best Python IRC bot in existence!" msgstr "Supybot, le meilleur bot IRC en Python au monde !" +#: plugin.py:107 +msgid "SOURCE" +msgstr "" + +#: plugin.py:123 +msgid "" +"[] [--nicks]\n" +"\n" +" Sends a CTCP VERSION to , returning the various\n" +" version strings returned. It waits for 10 seconds before returning\n" +" the versions received at that point. If --nicks is given, nicks are\n" +" associated with the version strings; otherwise, only the version\n" +" strings are given.\n" +" " +msgstr "" +"[] [--nicks]\n" +"\n" +"Envoie un CTCP VERSION au canal, et renvoie les différentes réponses reçues. Il attend 10 secondes avant de renvoyer les réponses reçues jusqu'alors. Si --nicks est donné, les nicks sont associés à la chaîne de version ; sinon, seules les chaînes sont données." + From f5de0997989532f4b436da4447743debc1efe3d4 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 26 Feb 2011 10:43:38 +0100 Subject: [PATCH 65/85] Dict: update l10n-fr --- plugins/Dict/locale/fr.po | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/plugins/Dict/locale/fr.po b/plugins/Dict/locale/fr.po index 2b70d582c..32708ea0b 100644 --- a/plugins/Dict/locale/fr.po +++ b/plugins/Dict/locale/fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Supybot-fr\n" -"POT-Creation-Date: 2010-10-17 10:39+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Supybot-fr \n" @@ -34,7 +34,7 @@ msgid "" " will use all dictionaries to define words." msgstr "Détermine le dictionnaire par défaut dans lequel le bot cherchera les définitions. Si la valeur est '*' (sans les guillemets), le bot utilisera tous les dictionnaires pour définir le mot." -#: plugin.py:52 +#: plugin.py:54 msgid "" "takes no arguments\n" "\n" @@ -45,7 +45,7 @@ msgstr "" "\n" "Retourne les dictionnaires valides pour la commande dict." -#: plugin.py:68 +#: plugin.py:70 msgid "" "takes no arguments\n" "\n" @@ -56,7 +56,7 @@ msgstr "" "\n" "Retourne un dictionnaire valide aléatoire." -#: plugin.py:83 +#: plugin.py:85 msgid "" "[] \n" "\n" @@ -68,19 +68,33 @@ msgstr "" "\n" "Recherche la définition du mot sur le serveur dictd spécifié par la variable de configuration supybot.plugins.Dict.server." -#: plugin.py:106 +#: plugin.py:108 msgid "You must give a word to define." msgstr "Vous devez donner un mot à définir." -#: plugin.py:112 +#: plugin.py:114 msgid "No definition for %q could be found." msgstr "La définition de %q ne peut être trouvée." -#: plugin.py:115 +#: plugin.py:117 msgid "No definition for %q could be found in %s" msgstr "La définition de %q ne peut être trouvée dans %s." -#: plugin.py:127 +#: plugin.py:129 msgid "%L responded: %s" msgstr "%L a répondu : %s" +#: plugin.py:136 +msgid "" +" [ ...]\n" +" Gets a random synonym from the Moby Thesaurus (moby-thes) database.\n" +" \n" +" If given many words, gets a random synonym for each of them.\n" +" \n" +" Quote phrases to have them treated as one lookup word.\n" +" " +msgstr "" +" [ ...]\n" +"\n" +"Récupère un synonyme aléatoire à partir de la base de données de Moby Thesaurus (moby-thes).Si plusieurs mots sont donnés, récupère un synonyme aléatoire pour chaque d'eux.Mettez les phrases entre guillemets pour qu'elles soient considérées comme un seul mot." + From 3c42a1f513f5efeb98d48829abac40b4c198e97b Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 26 Feb 2011 10:44:35 +0100 Subject: [PATCH 66/85] Plugin: fix forgotten merge info --- plugins/Plugin/plugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/Plugin/plugin.py b/plugins/Plugin/plugin.py index 105a0125f..6bb7ac73d 100644 --- a/plugins/Plugin/plugin.py +++ b/plugins/Plugin/plugin.py @@ -130,7 +130,6 @@ class Plugin(callbacks.Plugin): irc.error(format('There is no command %q.', command)) plugins = wrap(plugins, [many('something')]) ->>>>>>> 3005752... fix docstring for Plugin.plugin command so it actually says what the command will do. def author(self, irc, msg, args, cb): """ From f7899c762780000b4adb8f69cebc7b8c4c59d000 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 26 Feb 2011 10:45:29 +0100 Subject: [PATCH 67/85] Factoids: fix merge --- plugins/Factoids/plugin.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index e250f19f0..23f4fe566 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -325,16 +325,13 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): self._replyFactoids(irc, msg, key, channel, factoids, number, raw=raw) else: self._replyApproximateFactoids(irc, msg, channel, key) -<<<<<<< HEAD whatis = wrap(whatis, ['channel', many('something')]) - @internationalizeDocstring -======= - whatis = wrap(whatis, ['channel', + whatis = wrap(whatis, ['channel', getopts({'raw': '',}), many('something')]) - ->>>>>>> e4c51ef... Add --raw option to factoids.whatis, which disables variable substitution on the factoid. + + @internationalizeDocstring def alias(self, irc, msg, args, channel, oldkey, newkey, number): """[] [] @@ -618,7 +615,8 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): time.localtime(int(added_at))) L.append(format(_('#%i was added by %s at %s, and has been ' 'recalled %n'), - counter, added_by, added_at, (usage_count, 'time'))) + counter, added_by, added_at, + (usage_count, _('time')))) factoids = '; '.join(L) s = format('Key %q is %s and has %n associated with it: %s', key, locked and 'locked' or 'not locked', From e687061caeed3ef0499435ec3a76dd2899e03edd Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 26 Feb 2011 10:45:42 +0100 Subject: [PATCH 68/85] Factoids: update l10n-fr --- plugins/Factoids/locale/fr.po | 151 ++++++++++++++++++++++++++-------- 1 file changed, 115 insertions(+), 36 deletions(-) diff --git a/plugins/Factoids/locale/fr.po b/plugins/Factoids/locale/fr.po index a9d4b6531..9cc269f84 100644 --- a/plugins/Factoids/locale/fr.po +++ b/plugins/Factoids/locale/fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Supybot-fr\n" -"POT-Creation-Date: 2010-11-11 12:37+CET\n" +"POT-Creation-Date: 2011-02-26 09:58+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Supybot-fr \n" @@ -42,10 +42,18 @@ msgid "" msgstr "Détermine si le bot répondra aux commandes invalides lors de la recherche d'une factoid ; permet simplement de rendre la commande 'whatis' inutile lorsque vous voulez toutes les factoids d'un clef donnée." #: config.py:64 +msgid "" +"If you try to look up a nonexistent factoid,\n" +" this setting make the bot try to find some possible matching keys through\n" +" several approximate matching algorithms and return a list of matching keys,\n" +" before giving up." +msgstr "Si vous essayez de chercher une factoid inexistante, cette option permet de faire en sorte que le bot recherche les clefs de factoids dont le nom est proche, et qu'il les affiche." + +#: config.py:69 msgid "$key could be $value." msgstr "$key semble être $value." -#: config.py:64 +#: config.py:69 msgid "" "Determines the format of\n" " the response given when a factoid's value is requested. All the standard\n" @@ -53,7 +61,7 @@ msgid "" " for the factoid's value." msgstr "Détermine le format de la réponse donnée lorsqu'une valeur de factoid est demandée. Tous les substitus standards s'appliquent, en plus de \"$key\" pour la clef de la factoid et \"$value\" pour la valeur de la factoid." -#: plugin.py:153 +#: plugin.py:179 msgid "" "[] %s \n" "\n" @@ -68,35 +76,95 @@ msgstr "" "\n" "Associer la avec la . n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même. Le mot '%s' est nécessaire pour séparer la clef de la valeur. Il peut être modifié avec la valeur de registre learnSeparator." -#: plugin.py:179 +#: plugin.py:199 +msgid "" +"Try to typo-match input to possible factoids.\n" +" \n" +" Assume first letter is correct, to reduce processing time. \n" +" First, try a simple wildcard search.\n" +" If that fails, use the Damerau-Levenshtein edit-distance metric.\n" +" " +msgstr "" + +#: plugin.py:257 +#: plugin.py:391 msgid "That's not a valid number for that key." msgstr "Ce n'est pas un nombre valide pour cette clef." -#: plugin.py:199 -#: plugin.py:345 +#: plugin.py:279 +#: plugin.py:377 +#: plugin.py:604 msgid "No factoid matches that key." msgstr "Aucune factoid ne correspond à cette clef." -#: plugin.py:211 +#: plugin.py:304 msgid "" -"[] []\n" +"[] [--raw] []\n" "\n" " Looks up the value of in the factoid database. If given a\n" -" number, will return only that exact factoid. is only\n" -" necessary if the message isn't sent in the channel itself.\n" +" number, will return only that exact factoid. If '--raw' option is\n" +" given, no variable substitution will take place on the factoid.\n" +" is only necessary if the message isn't sent in the channel\n" +" itself.\n" " " msgstr "" "[] []\n" "\n" -"Regarde la valeur de la dans la base de données de factoids. Si un est donné, retourne la factoid exacte. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." +"Regarde la valeur de la dans la base de données de factoids. Si un est donné, retourne la factoid exacte.Si l'option --raw est donnée, aucune substitution de variable ne sera effectuée avant d'afficher la factoid. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:222 -#: plugin.py:273 -#: plugin.py:384 +#: plugin.py:321 +#: plugin.py:529 msgid "key id" msgstr "id de clef" -#: plugin.py:230 +#: plugin.py:339 +msgid "" +"[] []\n" +"\n" +" Adds a new key for factoid associated with .\n" +" is only necessary if there's more than one factoid associated\n" +" with .\n" +"\n" +" The same action can be accomplished by using the 'learn' function with\n" +" a new key but an existing (verbatim) factoid content.\n" +" " +msgstr "" +"[] []\n" +"\n" +"Ajoute une à la factoid correspondant à l'.Le n'est nécessaire que si il y a plus d'une factoid associée à l'.La même action peut être accomplie en utilisant la fonction 'learn' avec la , sans le contenu actuel de la factoid." + +#: plugin.py:386 +#: plugin.py:403 +msgid "This key-factoid relationship already exists." +msgstr "Cette relation clef-factoid existe déjà." + +#: plugin.py:394 +msgid "This key has more than one factoid associated with it, but you have not provided a number." +msgstr "Cette clef a plus d'une factoid associée, mais vous n'avez pas fourni un nombre." + +#: plugin.py:409 +msgid "" +"[] [--plain] [--alpha] []\n" +"\n" +" Returns a list of top-ranked factoid keys, sorted by usage count\n" +" (rank). If is not provided, the default number of factoid keys\n" +" returned is set by the rankListLength registry value.\n" +"\n" +" If --plain option is given, rank numbers and usage counts are not\n" +" included in output.\n" +"\n" +" If --alpha option is given in addition to --plain, keys are sorted\n" +" alphabetically, instead of by rank.\n" +"\n" +" is only necessary if the message isn't sent in the channel\n" +" itself.\n" +" " +msgstr "" +"[] [--plain] [--alpha] []\n" +"\n" +"Retourne une liste des factoids les plus utilisées. Si le n'est pas fourni, il correspond par défaut au nombre de clefs de factoids défini dans la clef de registre rankListLength.Si --plain est donné, le numéro des rangs et le comptage des utilisations n'est pas inclu dans la sortie.Si --alpha est donné, en plus de --plain, les clefs seront triées alphabétiquement, au lieu de l'être par leur rang. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." + +#: plugin.py:454 msgid "" "[] \n" "\n" @@ -109,7 +177,7 @@ msgstr "" "\n" "Verrouille la/les factoid(s) associé(e) à la , pour qu'elles ne puissent plus être supprimées ou modifiées. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:245 +#: plugin.py:472 msgid "" "[] \n" "\n" @@ -122,34 +190,39 @@ msgstr "" "\n" "Verrouille la/les factoid(s) associé(e) à la , pour qu'elles puissent être supprimées ou modifiées. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:260 +#: plugin.py:511 msgid "" "[] [|*]\n" "\n" -" Removes the factoid from the factoids database. If there are\n" -" more than one factoid with such a key, a number is necessary to\n" -" determine which one should be removed. A * can be used to remove all\n" -" factoids associated with a key. is only necessary if\n" +" Removes a key-fact relationship for key from the factoids\n" +" database. If there is more than one such relationship for this key,\n" +" a number is necessary to determine which one should be removed.\n" +" A * can be used to remove all relationships for .\n" +"\n" +" If as a result, the key (factoid) remains without any relationships to\n" +" a factoid (key), it shall be removed from the database.\n" +"\n" +" is only necessary if\n" " the message isn't sent in the channel itself.\n" " " msgstr "" "[] [|*]\n" "\n" -"Enlève la factoid de la base de données. Si il y a plus d'une factoid avec cette clef, un est requis pour déterminer laquelle sera supprimée. Un joker * peut être utilisé pour enlever toutes les factoids avec cette clef. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." +"Enlève la factoid de la base de données. Si il y a plus d'une factoid avec cette clef, un est requis pour déterminer laquelle sera supprimée. Un joker * peut être utilisé pour enlever toutes les factoids avec cette clef.Si, en tant que résultat, la clef (factoid) n'a plus aucune relation avec une autre factoid (clef), elle devrait être supprimée de la base de données. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:285 +#: plugin.py:543 msgid "There is no such factoid." msgstr "Cette factoid n'existe pas." -#: plugin.py:298 +#: plugin.py:553 msgid "Invalid factoid number." msgstr "Numéro de factoid invalide." -#: plugin.py:304 +#: plugin.py:558 msgid "%s factoids have that key. Please specify which one to remove, or use * to designate all of them." msgstr "%s factoids ont cette clef. Veuillez spécifier laquelle vous voulez supprimer ou utiliser * pour toutes les désigner." -#: plugin.py:312 +#: plugin.py:566 msgid "" "[]\n" "\n" @@ -161,11 +234,11 @@ msgstr "" "\n" "Retourne une factoid aléatoire de la base de données pour le canal. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:330 +#: plugin.py:588 msgid "I couldn't find a factoid." msgstr "Je ne peux trouver une factoid" -#: plugin.py:335 +#: plugin.py:593 msgid "" "[] \n" "\n" @@ -178,11 +251,15 @@ msgstr "" "\n" "Donne des informations sur la/les factoid(s) associée(s) à la . n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:358 -msgid "#%i was added by %s at %s" -msgstr "#%i a été ajouté par %s le %s" +#: plugin.py:619 +msgid "#%i was added by %s at %s, and has been recalled %n" +msgstr "#%i a été ajouté par %s le %s, et il y a eu %n." -#: plugin.py:369 +#: plugin.py:622 +msgid "time" +msgstr "rappel" + +#: plugin.py:632 msgid "" "[] \n" "\n" @@ -194,11 +271,11 @@ msgstr "" "\n" "Change la factoid associée à la , en accord avec l'. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:381 +#: plugin.py:646 msgid "I couldn't find any key %q" msgstr "Je ne peux trouver de clef %q" -#: plugin.py:396 +#: plugin.py:661 msgid "" "[] [--values] [--{regexp} ] [ ...]\n" "\n" @@ -211,11 +288,13 @@ msgstr "" "\n" "Recherche les clefs correspondant au . Si --regexp est donné, recherche les clefs correspondantes à l'. Si --values est donné, recherche parmi les valeurs, plutôt que parmi les clefs. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:431 +#: plugin.py:698 +#: plugin.py:707 msgid "No keys matched that query." msgstr "Aucune clef ne correspond à cette requête." -#: plugin.py:436 +#: plugin.py:703 +#: plugin.py:712 msgid "More than 100 keys matched that query; please narrow your query." msgstr "Plus de 100 clefs correspondent à votre requête ; veuillez la préciser." From aef0b6865d940a7c27dff6c0559c2f6e5b349403 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 26 Feb 2011 11:19:32 +0100 Subject: [PATCH 69/85] Filter & Format & Later & Plugin & RSS & Reply & Status & Topic & Unix & Web: update French translation --- plugins/Filter/locale/fr.po | 206 +++++++++++++++++++----------------- plugins/Format/locale/fr.po | 32 ++++-- plugins/Later/locale/fr.po | 50 +++++++-- plugins/Plugin/locale/fr.po | 90 +++++++++------- plugins/RSS/locale/fr.po | 52 +++++---- plugins/Reply/locale/fr.po | 24 +++-- plugins/Status/locale/fr.po | 10 +- plugins/Topic/locale/fr.po | 89 ++++++++-------- plugins/Unix/locale/fr.po | 69 ++++++++---- plugins/Web/locale/fr.po | 14 +-- 10 files changed, 377 insertions(+), 259 deletions(-) diff --git a/plugins/Filter/locale/fr.po b/plugins/Filter/locale/fr.po index e32003aa9..19b7e28e6 100644 --- a/plugins/Filter/locale/fr.po +++ b/plugins/Filter/locale/fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Supybot-fr\n" -"POT-Creation-Date: 2010-10-17 11:48+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Supybot-fr \n" @@ -37,7 +37,7 @@ msgid "" " in a word before it will be shrunken by the shrink command/filter." msgstr "Détermine le nombre minimum de lettre dans un mot pour qu'il soit coupé par la commande/le filtre shrink." -#: plugin.py:51 +#: plugin.py:50 msgid "" "This plugin offers several commands which transform text in some way.\n" " It also provides the capability of using such commands to 'filter' the\n" @@ -46,7 +46,7 @@ msgid "" " Not very useful, but definitely quite fun :)" msgstr "Ce plugin offre quelques commandes qui peuvent être utilisées pour transformer du texte de différentes façons. Il fourni également la possiblité d'utiliser ces commandes pour 'filtrer' la sortie du bot ; par exemple, vous pouvez faire en sorte que tout ce que le bot dit le soit en l33tsp34k, en Morse, ou n'importe lequel des autres filtres. Pas très utile, mais plutôt fun :)" -#: plugin.py:85 +#: plugin.py:84 msgid "" "[] []\n" "\n" @@ -59,11 +59,11 @@ msgstr "" "\n" "Définit le filtre de sortie de ce plugin pour être . Si aucune commande n'est définie, supprime le filtre de sortie. n'est nécessaire que si la commande n'est pas envoyée sur le canal lui-même." -#: plugin.py:98 +#: plugin.py:97 msgid "That's not a valid filter command." msgstr "Ce n'est pas une commande de filtre valide" -#: plugin.py:108 +#: plugin.py:107 msgid "" "\n" "\n" @@ -76,7 +76,7 @@ msgstr "" "\n" "Retire toutes les voyelles du (si vous êtes curieux de pourquoi elle s'appelle 'hebrew', c'est parce que je (jemfinch) pense que que en Hébreux, il manque souvent les voyelles)." -#: plugin.py:120 +#: plugin.py:119 msgid "" "\n" "\n" @@ -87,7 +87,7 @@ msgstr "" "\n" "Supprime tous les espaces du ." -#: plugin.py:130 +#: plugin.py:129 msgid "" "\n" "\n" @@ -98,7 +98,7 @@ msgstr "" "\n" "Renvoie le texte, avec toutes les lettres consécutives dupliquées supprimées." -#: plugin.py:143 +#: plugin.py:142 msgid "" "\n" "\n" @@ -109,7 +109,19 @@ msgstr "" "\n" "Retourne la représentation binaire du ." -#: plugin.py:169 +#: plugin.py:168 +msgid "" +"\n" +"\n" +" Returns the character representation of binary .\n" +" Assumes ASCII, 8 digits per character.\n" +" " +msgstr "" +"\n" +"\n" +"Retourne la représentation binaire du . Considère qu'il s'agit d'ASCII, 8 bits par caractère." + +#: plugin.py:179 msgid "" "\n" "\n" @@ -121,7 +133,7 @@ msgstr "" "\n" "Retourne une chaîne héxadécimale à partir de la chaîne donnée ; une chaîne héxadécimale est une chaîne composée de la valeur héxadécimale de chaque caractère de la chaîne." -#: plugin.py:179 +#: plugin.py:189 msgid "" "\n" "\n" @@ -133,11 +145,11 @@ msgstr "" "\n" "Retourne la chaîne correspondant à la . Bien sûr, ne doit contenir que des caractères hexadécimaux." -#: plugin.py:187 +#: plugin.py:197 msgid "Invalid input." msgstr "Entrée invalide." -#: plugin.py:192 +#: plugin.py:202 msgid "" "\n" "\n" @@ -150,7 +162,7 @@ msgstr "" "\n" "Déplace chaque caractère du de 13 places vers la droite de l'alphabet. Rot13 est courremment utilisé pour les textes qui doivent être cachés des yeux indiscrets, mais être facilement reversible." -#: plugin.py:203 +#: plugin.py:213 msgid "" "\n" "\n" @@ -161,7 +173,7 @@ msgstr "" "\n" "Retourne la version zézéyée du texte." -#: plugin.py:234 +#: plugin.py:244 msgid "" "\n" "\n" @@ -172,7 +184,7 @@ msgstr "" "\n" "Retourne la version l33t du ." -#: plugin.py:254 +#: plugin.py:264 msgid "" "\n" "\n" @@ -183,7 +195,7 @@ msgstr "" "\n" "Répond avec une traduction k-rad du ." -#: plugin.py:270 +#: plugin.py:280 msgid "" "\n" "\n" @@ -195,7 +207,7 @@ msgstr "" "\n" "Répond avec une chaîne où chaque mot est mélangé ; c'est à dire que chaque lettre interne (=toute lettre qui n'est pas la première ni la dernière) est mélangée avec les autres." -#: plugin.py:335 +#: plugin.py:345 msgid "" "\n" "\n" @@ -206,7 +218,7 @@ msgstr "" "\n" "Fait l'inverse de la commande morse." -#: plugin.py:352 +#: plugin.py:362 msgid "" "\n" "\n" @@ -217,7 +229,7 @@ msgstr "" "\n" "Donne le code Morse équivalent à la chaîne donnée." -#: plugin.py:364 +#: plugin.py:374 msgid "" "\n" "\n" @@ -228,7 +240,7 @@ msgstr "" "\n" "Inverse le ." -#: plugin.py:381 +#: plugin.py:391 msgid "" "\n" "\n" @@ -239,7 +251,7 @@ msgstr "" "\n" "Retourne le avec chaque caractère coloré de façon aléatoire." -#: plugin.py:391 +#: plugin.py:401 msgid "" "\n" "\n" @@ -250,7 +262,7 @@ msgstr "" "\n" "Retourne le texte colorisé comme un arc-en-ciel." -#: plugin.py:402 +#: plugin.py:412 msgid "" "\n" "\n" @@ -261,7 +273,7 @@ msgstr "" "\n" "Retourne le texte en retirant tous les codes de couleur" -#: plugin.py:411 +#: plugin.py:421 msgid "" "\n" "\n" @@ -272,7 +284,7 @@ msgstr "" "\n" "Retourne le comme si un AOLuser l'avait dit." -#: plugin.py:438 +#: plugin.py:448 msgid "" "\n" "\n" @@ -283,279 +295,279 @@ msgstr "" "\n" "Retourne le comme si JeffK l'avait dit lui-même." -#: plugin.py:534 +#: plugin.py:544 msgid "ay" msgstr "ah" -#: plugin.py:534 +#: plugin.py:544 msgid "bee" msgstr "bé" -#: plugin.py:534 +#: plugin.py:544 msgid "dee" msgstr "dé" -#: plugin.py:534 +#: plugin.py:544 msgid "see" msgstr "cé" -#: plugin.py:535 +#: plugin.py:545 msgid "aych" msgstr "ache" -#: plugin.py:535 +#: plugin.py:545 msgid "ee" msgstr "euh" -#: plugin.py:535 +#: plugin.py:545 msgid "eff" msgstr "èf" -#: plugin.py:535 +#: plugin.py:545 msgid "gee" msgstr "gé" -#: plugin.py:536 +#: plugin.py:546 msgid "ell" msgstr "èl" -#: plugin.py:536 +#: plugin.py:546 msgid "eye" msgstr "ih" -#: plugin.py:536 +#: plugin.py:546 msgid "jay" msgstr "ji" -#: plugin.py:536 +#: plugin.py:546 msgid "kay" msgstr "ka" -#: plugin.py:537 +#: plugin.py:547 msgid "cue" msgstr "cu" -#: plugin.py:537 +#: plugin.py:547 msgid "em" msgstr "èm" -#: plugin.py:537 +#: plugin.py:547 msgid "en" msgstr "èn" -#: plugin.py:537 +#: plugin.py:547 msgid "oh" msgstr "oh" -#: plugin.py:537 +#: plugin.py:547 msgid "pee" msgstr "pé" -#: plugin.py:538 +#: plugin.py:548 msgid "arr" msgstr "ère" -#: plugin.py:538 +#: plugin.py:548 msgid "ess" msgstr "èce" -#: plugin.py:538 +#: plugin.py:548 msgid "tee" msgstr "té" -#: plugin.py:538 +#: plugin.py:548 msgid "you" msgstr "uh" -#: plugin.py:539 +#: plugin.py:549 msgid "double-you" msgstr "double-vé" -#: plugin.py:539 +#: plugin.py:549 msgid "ecks" msgstr "icks" -#: plugin.py:539 +#: plugin.py:549 msgid "vee" msgstr "vé" -#: plugin.py:539 +#: plugin.py:549 msgid "why" msgstr "i-grec" -#: plugin.py:540 +#: plugin.py:550 msgid "zee" msgstr "zèd" -#: plugin.py:545 +#: plugin.py:555 msgid "exclamation point" msgstr "point d'exclamation" -#: plugin.py:546 +#: plugin.py:556 msgid "quote" msgstr "guillemet double" -#: plugin.py:547 +#: plugin.py:557 msgid "pound" msgstr "livre" -#: plugin.py:548 +#: plugin.py:558 msgid "dollar sign" msgstr "signe du dollar" -#: plugin.py:549 +#: plugin.py:559 msgid "percent" msgstr "pourcent" -#: plugin.py:550 +#: plugin.py:560 msgid "ampersand" msgstr "espèrluette" -#: plugin.py:551 +#: plugin.py:561 msgid "single quote" msgstr "guillemet" -#: plugin.py:552 +#: plugin.py:562 msgid "left paren" msgstr "parenthèse ouvrante" -#: plugin.py:553 +#: plugin.py:563 msgid "right paren" msgstr "parenthèse fermante" -#: plugin.py:554 +#: plugin.py:564 msgid "asterisk" msgstr "asterisque" -#: plugin.py:555 +#: plugin.py:565 msgid "plus" msgstr "plus" -#: plugin.py:556 +#: plugin.py:566 msgid "comma" msgstr "virgule" -#: plugin.py:557 +#: plugin.py:567 msgid "minus" msgstr "moins" -#: plugin.py:558 +#: plugin.py:568 msgid "period" msgstr "point" -#: plugin.py:559 +#: plugin.py:569 msgid "slash" msgstr "slash" -#: plugin.py:560 +#: plugin.py:570 msgid "colon" msgstr "double-point" -#: plugin.py:561 +#: plugin.py:571 msgid "semicolon" msgstr "point-virgule" -#: plugin.py:562 +#: plugin.py:572 msgid "less than" msgstr "inférieur" -#: plugin.py:563 +#: plugin.py:573 msgid "equals" msgstr "moins que" -#: plugin.py:564 +#: plugin.py:574 msgid "greater than" msgstr "supérieur" -#: plugin.py:565 +#: plugin.py:575 msgid "question mark" msgstr "point d'exclamation" -#: plugin.py:566 +#: plugin.py:576 msgid "at" msgstr "arobase" -#: plugin.py:567 +#: plugin.py:577 msgid "left bracket" msgstr "crochet ouvrant" -#: plugin.py:568 +#: plugin.py:578 msgid "backslash" msgstr "anti-slash" -#: plugin.py:569 +#: plugin.py:579 msgid "right bracket" msgstr "crochet fermant" -#: plugin.py:570 +#: plugin.py:580 msgid "caret" msgstr "accent circonflexe" -#: plugin.py:571 +#: plugin.py:581 msgid "underscore" msgstr "underscore" -#: plugin.py:572 +#: plugin.py:582 msgid "backtick" msgstr "accent grave" -#: plugin.py:573 +#: plugin.py:583 msgid "left brace" msgstr "crochet ouvrant" -#: plugin.py:574 +#: plugin.py:584 msgid "pipe" msgstr "pipe" -#: plugin.py:575 +#: plugin.py:585 msgid "right brace" msgstr "crochet fermant" -#: plugin.py:576 +#: plugin.py:586 msgid "tilde" msgstr "tilde" -#: plugin.py:579 +#: plugin.py:589 msgid "one" msgstr "un" -#: plugin.py:579 +#: plugin.py:589 msgid "three" msgstr "trois" -#: plugin.py:579 +#: plugin.py:589 msgid "two" msgstr "deux" -#: plugin.py:579 +#: plugin.py:589 msgid "zero" msgstr "zéro" -#: plugin.py:580 +#: plugin.py:590 msgid "five" msgstr "cinq" -#: plugin.py:580 +#: plugin.py:590 msgid "four" msgstr "quatre" -#: plugin.py:580 +#: plugin.py:590 msgid "seven" msgstr "sept" -#: plugin.py:580 +#: plugin.py:590 msgid "six" msgstr "six" -#: plugin.py:581 +#: plugin.py:591 msgid "eight" msgstr "huit" -#: plugin.py:581 +#: plugin.py:591 msgid "nine" msgstr "neuf" -#: plugin.py:585 +#: plugin.py:595 msgid "" "\n" "\n" @@ -566,7 +578,7 @@ msgstr "" "\n" "Retourne le , épellé phonétiquement" -#: plugin.py:615 +#: plugin.py:625 msgid "" "\n" "\n" @@ -577,7 +589,7 @@ msgstr "" "\n" "Retourne le comme si GNU/RMS l'avait dite." -#: plugin.py:624 +#: plugin.py:634 msgid "" "\n" "\n" @@ -590,7 +602,7 @@ msgstr "" "\n" "Retourne le texte avec chaque mot plus long que supybot.plugins.Filter.shrink.minimum découpé (par exemple, \"internationalization\" devient i18n)" -#: plugin.py:643 +#: plugin.py:653 msgid "" "\n" "\n" @@ -601,7 +613,7 @@ msgstr "" "\n" "Retourne le avec les I transormés en r et les r transformés en I." -#: plugin.py:692 +#: plugin.py:702 msgid "" "\n" "\n" diff --git a/plugins/Format/locale/fr.po b/plugins/Format/locale/fr.po index 52c4bc11b..5a63b2359 100644 --- a/plugins/Format/locale/fr.po +++ b/plugins/Format/locale/fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Supybot-fr\n" -"POT-Creation-Date: 2010-10-17 12:46+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Supybot-fr \n" @@ -88,6 +88,18 @@ msgstr " doit être de la même taille que \n" +"\n" +" Replaces all non-overlapping occurrences of \n" +" with in .\n" +" " +msgstr "" +" \n" +"\n" +"Replace toutes les occurences de (à condition qu'elles n'entrent pas en conflit) par les dans le ." + +#: plugin.py:112 +msgid "" "\n" "\n" " Returns uppercased.\n" @@ -97,7 +109,7 @@ msgstr "" "\n" "Retourne le texte, en majuscules" -#: plugin.py:112 +#: plugin.py:121 msgid "" "\n" "\n" @@ -108,7 +120,7 @@ msgstr "" "\n" "Retourne le texte, en minuscules" -#: plugin.py:121 +#: plugin.py:130 msgid "" "\n" "\n" @@ -119,7 +131,7 @@ msgstr "" "\n" "Retourne le texte, capitalisé" -#: plugin.py:130 +#: plugin.py:139 msgid "" "\n" "\n" @@ -130,7 +142,7 @@ msgstr "" "\n" "Retourne le texte, mis en majuscules de titre." -#: plugin.py:139 +#: plugin.py:148 msgid "" "\n" "\n" @@ -141,7 +153,7 @@ msgstr "" "\n" "Retourne le texte, entouré de doubles guillemets." -#: plugin.py:148 +#: plugin.py:157 msgid "" " \n" "\n" @@ -154,7 +166,7 @@ msgstr "" "\n" "Concatène les deux chaînes. Notez que ce n'est pas la même chose que de les joindre avec \"\", car, si contient des espaces, ils ne seront pas supprimés par la concaténation." -#: plugin.py:159 +#: plugin.py:168 msgid "" " \n" "\n" @@ -167,7 +179,7 @@ msgstr "" "\n" "Coup le en morceaux de , en découpant les caractères dépassant la . Si la est un nombre négatif, il coupe en comptant à partir de la fin du texte." -#: plugin.py:170 +#: plugin.py:179 msgid "" " \n" "\n" @@ -179,7 +191,7 @@ msgstr "" "\n" "Retourne le -ième élément (séparé par des espaces) du . C'est à dire que si le texte est \"foo bar baz\" et que est 2, \"bar\" sera retourné." -#: plugin.py:183 +#: plugin.py:192 msgid "" " [ ...]\n" "\n" @@ -191,7 +203,7 @@ msgstr "" " [ ...]\n" "\n" -#: plugin.py:197 +#: plugin.py:206 msgid "Not enough arguments for the format string." msgstr "Pas assez d'arguments pour formatter la chaîne." diff --git a/plugins/Later/locale/fr.po b/plugins/Later/locale/fr.po index 49f8ded4d..a168128ad 100644 --- a/plugins/Later/locale/fr.po +++ b/plugins/Later/locale/fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Supybot-fr\n" -"POT-Creation-Date: 2010-10-28 16:15+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Supybot-fr \n" @@ -26,6 +26,19 @@ msgid "" " the first place in which they're seen, or in private." msgstr "Détermine si les utilisateurs seront notifiés au premier endroit où ils sont vus, ou en privé." +#: config.py:52 +msgid "" +"Determines whether users will be notified upon\n" +" joining any channel the bot is in, or only upon sending a message." +msgstr "Détermine si les utilisateurs seront notifiés au premier endroit où ils sont vus, ou seulement lorsqu'ils envoient un message." + +#: config.py:55 +msgid "" +"Determines the maximum number of\n" +" days that a message will remain queued for a user. After this time elapses,\n" +" the message will be deleted. If this value is 0, there is no maximum." +msgstr "Détermine le nombre maximum de messages en attente d'un utilisateur. Après que ce temps se soit écoulé, le message sera supprimé. Si la valeur est 0, il n'y a pas de maximum." + #: plugin.py:46 msgid "" "Used to do things later; currently, it only allows the sending of\n" @@ -42,7 +55,22 @@ msgstr "il y a %s" msgid "just now" msgstr "à l'instant" -#: plugin.py:107 +#: plugin.py:106 +msgid "" +"Validate nick according to the IRC RFC 2812 spec.\n" +"\n" +" Reference: http://tools.ietf.org/rfcmarkup?doc=2812#section-2.3.1\n" +"\n" +" Some irc clients' tab-completion feature appends 'address' characters\n" +" to nick, such as ':' or ','. We try correcting for that by trimming\n" +" a char off the end.\n" +"\n" +" If nick incorrigibly invalid, return False, otherwise,\n" +" return (possibly trimmed) nick.\n" +" " +msgstr "" + +#: plugin.py:151 msgid "" " \n" "\n" @@ -55,15 +83,15 @@ msgstr "" "\n" "Dit le à la prochaine fois qu'il est vu. peut contenir des jokers, et le premier nick correspondant recevra la note." -#: plugin.py:114 +#: plugin.py:159 msgid "I can't send notes to myself." msgstr "Je ne peux m'envoyer de notes à moi-même." -#: plugin.py:120 +#: plugin.py:169 msgid "That person's message queue is already full." msgstr "La file d'attente des messages de cette personne est déjà pleine." -#: plugin.py:125 +#: plugin.py:174 msgid "" "[]\n" "\n" @@ -75,19 +103,19 @@ msgstr "" "\n" "Si le est donné, répond avec les notes en attente pour ; sinon, répond avec les nicks ayant des notes en attente." -#: plugin.py:136 +#: plugin.py:185 msgid "I have no notes for that nick." msgstr "Je n'ai pas de note pour ce nick." -#: plugin.py:141 +#: plugin.py:190 msgid "I currently have notes waiting for %L." msgstr "J'ai actuellement des notes en attente pour %L." -#: plugin.py:144 +#: plugin.py:193 msgid "I have no notes waiting to be delivered." msgstr "Je n'ai pas de note à délivrer." -#: plugin.py:149 +#: plugin.py:198 msgid "" "\n" "\n" @@ -98,11 +126,11 @@ msgstr "" "\n" "Supprime les notes en attente pour ." -#: plugin.py:158 +#: plugin.py:207 msgid "There were no notes for %r" msgstr "Il n'y a pas de note pour %r" -#: plugin.py:182 +#: plugin.py:231 msgid "Sent %s: <%s> %s" msgstr "Envoyé %s : <%s> %s" diff --git a/plugins/Plugin/locale/fr.po b/plugins/Plugin/locale/fr.po index d62ab426e..95aac5a31 100644 --- a/plugins/Plugin/locale/fr.po +++ b/plugins/Plugin/locale/fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Supybot-fr\n" -"POT-Creation-Date: 2010-10-16 13:50+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Supybot-fr \n" @@ -13,7 +13,7 @@ msgstr "" "X-Poedit-Country: France\n" "X-Poedit-SourceCharset: ASCII\n" -#: plugin.py:43 +#: plugin.py:42 msgid "" "This plugin exists to help users manage their plugins. Use 'plugin\n" " list' to list the loaded plugins; use 'plugin help' to get the description\n" @@ -21,7 +21,7 @@ msgid "" " command exists in." msgstr "Ce plugin existe pour aider les utilisateurs à gérer leurs plugins. Utilisez 'list' pour liser les plugins chargés ; utilisez 'help' pour avoir de l'aide quant à d'autres plugins ; utilisez la commande 'plugin' elle-même pour déterminer dans quel plugin une commande existe." -#: plugin.py:49 +#: plugin.py:48 msgid "" "\n" "\n" @@ -33,11 +33,11 @@ msgstr "" "\n" "Retourne une description utile de comment utiliser le , si le plugin en a une." -#: plugin.py:58 +#: plugin.py:57 msgid "That plugin is loaded, but has no plugin help." msgstr "Ce plugin est chargé mais n'a pas d'aide." -#: plugin.py:63 +#: plugin.py:62 msgid "" "takes no arguments\n" "\n" @@ -48,34 +48,48 @@ msgstr "" "\n" "Retourne une liste des plugins actuellement chargés." -#: plugin.py:74 +#: plugin.py:73 msgid "" "\n" "\n" -" Returns the plugin(s) that is in.\n" +" Returns the name of the plugin that would be used to call .\n" +" \n" +" If it is not uniquely determined, returns list of all plugins that\n" +" contain .\n" " " msgstr "" "\n" "\n" -"Retourne le(s) plugin(s) ayant la ." +"Retourne le nom du plugin qui serait utilisé pour appeller la .Si il ne peut être déterminé (c'est à dire s'il y a un conflit, sans plugin par défaut), la liste de tous les plugins contenant cette commande sera renvoyée." -#: plugin.py:89 +#: plugin.py:91 msgid "plugins" msgstr "plugins" -#: plugin.py:91 +#: plugin.py:93 msgid "plugin" msgstr "plugin" -#: plugin.py:92 +#: plugin.py:94 msgid "The %q command is available in the %L %s." msgstr "La commande %q est disponibles dans le(s) plugin(s) %L%v." -#: plugin.py:95 +#: plugin.py:97 msgid "There is no command %q." msgstr "Il n'y a pas de commande %q." -#: plugin.py:100 +#: plugin.py:113 +msgid "" +"\n" +"\n" +" Returns the names of all plugins that contain .\n" +" " +msgstr "" +"\n" +"\n" +"Retourne les noms de tous les plugins contenant la ." + +#: plugin.py:135 msgid "" "\n" "\n" @@ -87,15 +101,15 @@ msgstr "" "\n" "Retourne l'auteur du . C'est la personne à qui vous devriez parler si vous avez des idées, suggestions, ou d'autres commentaires à propos d'un plugin donné." -#: plugin.py:106 +#: plugin.py:141 msgid "That plugin does not seem to be loaded." msgstr "Ce plugin ne semble pas être chargé." -#: plugin.py:112 +#: plugin.py:147 msgid "That plugin doesn't have an author that claims it." msgstr "Ce plugin n'a pas d'auteur." -#: plugin.py:117 +#: plugin.py:152 msgid "" " []\n" "\n" @@ -109,7 +123,7 @@ msgstr "" "\n" "Renvoie une liste des personnes ayant contribué à un plugin donné. Si le est spécifié, les contributions de cette personne seront lisées. Note : est la partie entre parenthèses lors du listing des personnes." -#: plugin.py:125 +#: plugin.py:160 msgid "" "\n" " Take an Authors object, and return only the name and nick values\n" @@ -117,7 +131,7 @@ msgid "" " " msgstr "" -#: plugin.py:131 +#: plugin.py:166 msgid "" "\n" " Take a list of long names and turn it into :\n" @@ -125,7 +139,7 @@ msgid "" " " msgstr "" -#: plugin.py:138 +#: plugin.py:173 msgid "" "\n" " Sort the list of 'long names' based on the number of contributions\n" @@ -133,7 +147,7 @@ msgid "" " " msgstr "" -#: plugin.py:148 +#: plugin.py:183 msgid "" "\n" " Build the list of author + contributors (if any) for the requested\n" @@ -141,39 +155,39 @@ msgid "" " " msgstr "" -#: plugin.py:152 +#: plugin.py:187 msgid "The %s plugin" msgstr "Le plugin s" -#: plugin.py:153 +#: plugin.py:188 msgid "has not been claimed by an author" msgstr "n'a aucun auteur" -#: plugin.py:154 +#: plugin.py:189 msgid "and" msgstr "et" -#: plugin.py:155 +#: plugin.py:190 msgid "has no contributors listed." msgstr "n'a pas de contributeur listé." -#: plugin.py:160 +#: plugin.py:195 msgid "was written by %s" msgstr "a été écrit par %s" -#: plugin.py:171 +#: plugin.py:206 msgid "%s %h contributed to it." msgstr "%s y %h contribué." -#: plugin.py:176 +#: plugin.py:211 msgid "has no additional contributors listed." msgstr "n'a pas d'autre contributeur listé." -#: plugin.py:178 +#: plugin.py:213 msgid "but" msgstr "mais" -#: plugin.py:181 +#: plugin.py:216 msgid "" "\n" " Build the list of contributions (if any) for the requested person\n" @@ -181,39 +195,39 @@ msgid "" " " msgstr "" -#: plugin.py:195 +#: plugin.py:230 msgid "The nick specified (%s) is not a registered contributor." msgstr "Le nick spécifié(%s) n'est pas un contributeur enregistré." -#: plugin.py:201 +#: plugin.py:236 msgid "The %s plugin does not have '%s' listed as a contributor." msgstr "Le plugin %s n'a pas '%s' listé comme contributeur." -#: plugin.py:209 +#: plugin.py:244 msgid "command" msgstr "commande" -#: plugin.py:212 +#: plugin.py:247 msgid "the %L %s" msgstr "La/les commande(s) %L%v" -#: plugin.py:214 +#: plugin.py:249 msgid "the %L" msgstr "La/les %L" -#: plugin.py:217 +#: plugin.py:252 msgid "%s wrote the %s plugin and also contributed %L." msgstr "%s a écrit le plugin %s et a aussi contribué à %L" -#: plugin.py:220 +#: plugin.py:255 msgid "%s contributed %L to the %s plugin." msgstr "%s a contribué à %L et au plugin %s" -#: plugin.py:223 +#: plugin.py:258 msgid "%s wrote the %s plugin" msgstr "%s a écrit le plugin %s" -#: plugin.py:226 +#: plugin.py:261 msgid "%s has no listed contributions for the %s plugin." msgstr "%s n'a pas de contribution listée pour le plugin %s." diff --git a/plugins/RSS/locale/fr.po b/plugins/RSS/locale/fr.po index 8c8cbecd3..5bd523e80 100644 --- a/plugins/RSS/locale/fr.po +++ b/plugins/RSS/locale/fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Supybot-fr\n" -"POT-Creation-Date: 2010-10-19 19:27+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Supybot-fr \n" @@ -15,20 +15,20 @@ msgstr "" #: config.py:50 msgid "" -"Determines whether the bot will bold the title of the feed when it\n" -" announces new news." +"Determines whether the bot will bold the title of the feed when\n" +" it announces new news." msgstr "Détermine si le bot mettera en gras le titre des flux lorsqu'il annoncera des news." #: config.py:53 msgid "" -"Determines what string is used\n" -" to separate headlines in new feeds." +"Determines what string is\n" +" used to separate headlines in new feeds." msgstr "Détermine quelle chaîne est utilisé pour séparer les titres dans les nouveaux flux." #: config.py:56 msgid "" -"Determines what prefix\n" -" is prepended (if any) to the new news item announcements made in the\n" +"Determines what\n" +" prefix is prepended (if any) to the new news item announcements made in the\n" " channel." msgstr "Détermine quel préfixe (s'il y en a un) est utilisé pour annoncer les news sur le canal." @@ -60,14 +60,14 @@ msgid "" " listed when a feed is automatically announced." msgstr "Détermine si le bot listera le lien de chaque flus avec son titre, lorsque la commande rss est appelée. supybot.plugins.RSS.announce.showLinks affecte si les liens sont affichés lorsqu'un flux est annoncé automatiquement." -#: config.py:78 +#: config.py:84 msgid "" "Determines whether the bot will list the link\n" " along with the title of the feed when a feed is automatically\n" " announced." msgstr "Détermine si le bot listera le lien de chaque flux avec le titre lorsqu'un flux est automatiquement annoncé." -#: plugin.py:63 +#: plugin.py:62 msgid "" "This plugin is useful both for announcing updates to RSS feeds in a\n" " channel, and for retrieving the headlines of RSS feeds via command. Use\n" @@ -75,7 +75,7 @@ msgid "" " command to determine what feeds should be announced in a given channel." msgstr "Ce plugin est utile pour annoncer des flux RSS sur un canal, et pour récupérer les en-tête des flux RSS via une commande. Utilisez la commande \"add\" pour ajouter des flux au plugin, et utilisez la commande \"annonce\" pour détermine quels flux pourront être annoncés sur un canal donné." -#: plugin.py:311 +#: plugin.py:316 msgid "" " \n" "\n" @@ -87,7 +87,7 @@ msgstr "" "\n" "Ajoute un commande à ce plugin qui permet de regarder le flux situé à l'." -#: plugin.py:322 +#: plugin.py:327 msgid "" "\n" "\n" @@ -99,15 +99,27 @@ msgstr "" "\n" "Supprime le flux des flux qui peuvent être lus grâce à une commande." -#: plugin.py:328 +#: plugin.py:333 msgid "That's not a valid RSS feed command name." msgstr "Ce n'est pas une commande de flux RSS valide" -#: plugin.py:346 +#: plugin.py:344 +msgid "" +"[]\n" +"\n" +" Returns the list of feeds announced in . is\n" +" only necessary if the message isn't sent in the channel itself.\n" +" " +msgstr "" +"[]\n" +"\n" +"Retourne la liste des flux annoncés sur le . n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." + +#: plugin.py:351 msgid "I am currently not announcing any feeds." msgstr "Je n'annonce actuellement aucun flux." -#: plugin.py:351 +#: plugin.py:356 msgid "" "[] [ ...]\n" "\n" @@ -121,7 +133,7 @@ msgstr "" "\n" "Ajoute la liste de flux à la liste actuelle des flux annoncés sur le . Vous devez indiquer le du flux si il est déjà enregistré, ou l' dans le cas contraire. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:369 +#: plugin.py:374 msgid "" "[] [ ...]\n" "\n" @@ -135,7 +147,7 @@ msgstr "" "\n" "Supprime la liste de flux de la liste actuelle des flux annoncés sur le . Vous devez indiquer le du flux si il est déjà enregistré, ou l' dans le cas contraire. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:387 +#: plugin.py:392 msgid "" " []\n" "\n" @@ -147,11 +159,11 @@ msgstr "" "\n" "Récupère le titre des éléments du flux RSS donné. si le est donné, ne retourne que ce nombre de lignes d'en-tête." -#: plugin.py:400 +#: plugin.py:405 msgid "Couldn't get RSS feed." msgstr "Ne peut récupérer le flux RSS." -#: plugin.py:413 +#: plugin.py:420 msgid "" "\n" "\n" @@ -163,11 +175,11 @@ msgstr "" "\n" "Retourne des informations sur le flux RSS donné : le titre, l'URL, la description, et la dernière mise à jour." -#: plugin.py:426 +#: plugin.py:433 msgid "I couldn't retrieve that RSS feed." msgstr "Je ne peux récupérer ce flux RSS." -#: plugin.py:439 +#: plugin.py:446 msgid "Title: %s; URL: %u; Description: %s; Last updated: %s." msgstr "Titre : %s , URL : %u ; description : %s ; dernière mise à jour : %s." diff --git a/plugins/Reply/locale/fr.po b/plugins/Reply/locale/fr.po index 8acefbde3..4bcc56a98 100644 --- a/plugins/Reply/locale/fr.po +++ b/plugins/Reply/locale/fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Supybot-fr\n" -"POT-Creation-Date: 2010-10-19 19:27+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Supybot-fr \n" @@ -13,7 +13,7 @@ msgstr "" "X-Poedit-Country: France\n" "X-Poedit-SourceCharset: ASCII\n" -#: plugin.py:38 +#: plugin.py:37 msgid "" "This plugins contains a few commands that construct various types of\n" " replies. Some bot owners would be wise to not load this plugin because it\n" @@ -21,7 +21,7 @@ msgid "" " " msgstr "Ce plugin contient quelques commandes pour construire différents types de réponses. Certains propriétaires de bots pourraient ne pas vouloir le charger, car on peut facilement en abuser." -#: plugin.py:44 +#: plugin.py:43 msgid "" "\n" "\n" @@ -33,7 +33,7 @@ msgstr "" "\n" "Répond avec le en privé. Utile pour les commandes imbriquées." -#: plugin.py:54 +#: plugin.py:53 msgid "" "\n" "\n" @@ -45,7 +45,7 @@ msgstr "" "\n" "Répond avec le comme une action. Utile pour les commandes imbriquées." -#: plugin.py:67 +#: plugin.py:66 msgid "" "\n" "\n" @@ -57,7 +57,7 @@ msgstr "" "\n" "Répond avec le en notice. Utile pour les commandes imbriquées." -#: plugin.py:77 +#: plugin.py:76 msgid "" "\n" "\n" @@ -68,3 +68,15 @@ msgstr "" "\n" "Répond avec le . Équivalent à l'alias 'echo $nick: $i'." +#: plugin.py:85 +msgid "" +" [ ...]\n" +"\n" +" Replies with each of its arguments in separate replies, depending\n" +" the configuration of supybot.reply.oneToOne.\n" +" " +msgstr "" +" [ ...]\n" +"\n" +"Répond avec chacun des s dans des réponses séparées, en fonction de la configutation de supybot.reply.oneToOne." + diff --git a/plugins/Status/locale/fr.po b/plugins/Status/locale/fr.po index 6d79d98b0..bb02d64b1 100644 --- a/plugins/Status/locale/fr.po +++ b/plugins/Status/locale/fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Supybot-fr\n" -"POT-Creation-Date: 2010-10-20 09:05+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Supybot-fr \n" @@ -85,8 +85,8 @@ msgid "an indeterminate amount of time" msgstr "une durée indéterminée" #: plugin.py:112 -msgid "I have received %s messages for a total of %s bytes. I have sent %s messages for a total of %s bytes. I have been connected to %s for %s." -msgstr "J'ai reçu %s message pour un total de %s octets. J'ai envoyé %s messages pour un total de %s octets. J'ai été connecté à %s pendant %s." +msgid "I have received %s messages for a total of %S. I have sent %s messages for a total of %S. I have been connected to %s for %s." +msgstr "J'ai reçu %s message pour un total de %S. J'ai envoyé %s messages pour un total de %S. J'ai été connecté à %s pendant %s." #: plugin.py:121 msgid "" @@ -112,8 +112,8 @@ msgid "Unable to run ps command." msgstr "Impossible de lancer la commande ps." #: plugin.py:166 -msgid " I'm taking up %s kB of memory." -msgstr " Je prend plus de %s kO de mémoire." +msgid " I'm taking up %S of memory." +msgstr " Je prend plus de %S de mémoire." #: plugin.py:174 msgid "" diff --git a/plugins/Topic/locale/fr.po b/plugins/Topic/locale/fr.po index 50872e50d..a5d70583b 100644 --- a/plugins/Topic/locale/fr.po +++ b/plugins/Topic/locale/fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Supybot-fr\n" -"POT-Creation-Date: 2010-10-30 12:23+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Supybot-fr \n" @@ -50,41 +50,42 @@ msgid "" " topics to keep around in case the undo command is called." msgstr "Détermine le nombre de topics précédents à garder pour le cas où la commande 'undo' est appelée." -#: config.py:70 +#: config.py:69 +#, fuzzy msgid "" -"Determines the \n" +"Determines the\n" " capabilities required (if any) to make any topic changes,\n" -" (everything except for read-only operations). Use 'channel,capab' for \n" +" (everything except for read-only operations). Use 'channel,capab' for\n" " channel-level capabilities.\n" -" Note that absence of an explicit anticapability means user has \n" +" Note that absence of an explicit anticapability means user has\n" " capability." msgstr "Détermine les permissions requises (s'il y en a) pour changer le topic (c'est à dire tout ce qui n'est pas une opération en lecture seule). Utilisez #canal,capacité pour les capacités de canaux. Notez qu'en l'absence d'une anticapacité explicite, l'utilisateur a cette capacité." -#: plugin.py:55 +#: plugin.py:57 msgid "I'm not currently in %s." msgstr "Je ne suis pas actuellement sur %s." -#: plugin.py:59 +#: plugin.py:61 msgid "I can't change the topic, I'm not opped and %s is +t." msgstr "Je ne peux changer le topic, je ne suis pas opé, et %s a le mode +t." -#: plugin.py:66 +#: plugin.py:68 msgid "The topic must not include %q." msgstr "Le topic ne doit pas inclure %q." -#: plugin.py:77 +#: plugin.py:79 msgid "topic number" msgstr "numéro de topic" -#: plugin.py:90 +#: plugin.py:92 msgid "There are no topics in %s." msgstr "Il n'y a pas de topic sur %s." -#: plugin.py:198 +#: plugin.py:200 msgid "That topic is too long for this server (maximum length: %i; this topic: %i)." msgstr "Ce topic est trop long pour ce serveur (longueur maximum : %i ; ce topic : %i)." -#: plugin.py:212 +#: plugin.py:213 msgid "" "Check if the user has any of the required capabilities to manage\n" " the channel topic.\n" @@ -97,7 +98,7 @@ msgid "" " " msgstr "" -#: plugin.py:264 +#: plugin.py:265 msgid "" "[]\n" "\n" @@ -109,7 +110,7 @@ msgstr "" "\n" "Retourne le topic du . n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:275 +#: plugin.py:276 msgid "" "[] \n" "\n" @@ -121,7 +122,7 @@ msgstr "" "\n" "Ajoute le aux topics du . n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:290 +#: plugin.py:291 msgid "" "[] \n" "\n" @@ -135,7 +136,7 @@ msgstr "" "\n" "Ajoute le topic aux topics du . Si le topic est trop long pour le serveur, les topics les plus vieux seront supprimés jusqu'à ce qu'il y ai assez de place. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:307 +#: plugin.py:308 msgid "" "[] \n" "\n" @@ -146,7 +147,7 @@ msgstr "" "\n" "Remplace le topic par le ." -#: plugin.py:321 +#: plugin.py:322 msgid "" "[] \n" "\n" @@ -159,7 +160,7 @@ msgstr "" "\n" "Ajoute le aux topics du , au début des topics actuellement sur le . n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:337 +#: plugin.py:338 msgid "" "[]\n" "\n" @@ -171,11 +172,11 @@ msgstr "" "\n" "Mélance les topics sur le . n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:347 +#: plugin.py:348 msgid "I can't shuffle 1 or fewer topics." msgstr "Je ne peux mélanger les topics que si il y en a au moins deux." -#: plugin.py:359 +#: plugin.py:360 msgid "" "[] [ ...]\n" "\n" @@ -189,19 +190,19 @@ msgstr "" "\n" "Remet les topics du dans l'ordre spécifié par les arguments . est un index dans les topics. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:372 +#: plugin.py:373 msgid "I cannot reorder 1 or fewer topics." msgstr "Je ne peux réordonner les topics s'il y en a moins de deux." -#: plugin.py:374 +#: plugin.py:375 msgid "All topic numbers must be specified." msgstr "Tous les nombres de topics doivent être spécifiés." -#: plugin.py:376 +#: plugin.py:377 msgid "Duplicate topic numbers cannot be specified." msgstr "Les numéros de topics ne peuvent être en double." -#: plugin.py:384 +#: plugin.py:385 msgid "" "[]\n" "\n" @@ -214,11 +215,11 @@ msgstr "" "\n" "Retourne la liste des topics sur le , préfixés par leur index. Généralement utile pour réordonner les topics. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:393 +#: plugin.py:394 msgid "%i: %s" msgstr "%i : %s" -#: plugin.py:400 +#: plugin.py:401 msgid "" "[] \n" "\n" @@ -231,7 +232,7 @@ msgstr "" "\n" "Retourne le topic numéro du canal. est un index dans les topics. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:415 +#: plugin.py:416 msgid "" "[] \n" "\n" @@ -246,7 +247,7 @@ msgstr "" "\n" "Change le topic sur le , en accord avec l'expression régulière . est un index parmis les topics ; est une expression régulière de la forme s/regexp/replacement/flags. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:433 +#: plugin.py:434 msgid "" "[] [] \n" "\n" @@ -259,7 +260,7 @@ msgstr "" "\n" "Définit le topic . Si le n'est pas donné, il s'agit du topic entier. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:454 +#: plugin.py:455 msgid "" "[] \n" "\n" @@ -273,7 +274,7 @@ msgstr "" "\n" "Supprime le topic des topics du . Les topics sont numérotés à partir de 1 ; vous pouvez également utiliser des indexs négatifs pour compter à partir de la fin. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:471 +#: plugin.py:472 msgid "" "[]\n" "\n" @@ -285,11 +286,11 @@ msgstr "" "\n" "Verrouille le topic (défini le mode +t) sur le . n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:481 +#: plugin.py:482 msgid "lock the topic" msgstr "verrouiller le topic" -#: plugin.py:485 +#: plugin.py:486 msgid "" "[]\n" "\n" @@ -301,11 +302,11 @@ msgstr "" "\n" "Déverrouille le topic (défini le mode +t) sur le . n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:495 +#: plugin.py:496 msgid "unlock the topic" msgstr "déverrouiller le topic" -#: plugin.py:499 +#: plugin.py:500 msgid "" "[]\n" "\n" @@ -317,11 +318,11 @@ msgstr "" "\n" "Restaure le topic tel qu'il était la dernière fois que le bot l'a défini. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:510 +#: plugin.py:511 msgid "I haven't yet set the topic in %s." msgstr "Je n'ai encore jamais définit le topic sur %s." -#: plugin.py:518 +#: plugin.py:519 msgid "" "[]\n" "\n" @@ -334,11 +335,11 @@ msgstr "" "\n" "Restaure le topic à l'état dans lequel il était avant la dernière utilisation de la commande 'topic. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:532 +#: plugin.py:533 msgid "There are no more undos for %s." msgstr "Il n'y a plus rien à défaire sur %s" -#: plugin.py:537 +#: plugin.py:538 msgid "" "[]\n" "\n" @@ -350,11 +351,11 @@ msgstr "" "\n" "Défait le dernier 'undo'. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:549 +#: plugin.py:550 msgid "There are no redos for %s." msgstr "Il n'y a plus rien à refaire sur %s" -#: plugin.py:554 +#: plugin.py:555 msgid "" "[] \n" "\n" @@ -367,11 +368,11 @@ msgstr "" "\n" " Inverse les deux topics donnés. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:565 +#: plugin.py:566 msgid "I refuse to swap the same topic with itself." msgstr "Je refuse d'échanger un topic avec lui-même." -#: plugin.py:575 +#: plugin.py:576 msgid "" "[]\n" "\n" @@ -384,11 +385,11 @@ msgstr "" "\n" "Définit le topic du pour correspondre à celui par défaut défini pour le . Le topic par défaut pour un canal peut être configuré via la variable supybot.plugins.Topic.default. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." -#: plugin.py:588 +#: plugin.py:589 msgid "There is no default topic configured for %s." msgstr "Il n'y a pas de topic par défaut configuré pour %s." -#: plugin.py:594 +#: plugin.py:595 msgid "" "[] \n" "\n" diff --git a/plugins/Unix/locale/fr.po b/plugins/Unix/locale/fr.po index 058589d46..9e6c8c717 100644 --- a/plugins/Unix/locale/fr.po +++ b/plugins/Unix/locale/fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Supybot-fr\n" -"POT-Creation-Date: 2010-10-20 09:38+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Supybot-fr \n" @@ -72,7 +72,7 @@ msgid "" " command will be called for the wtf command." msgstr "Détermine quelle commande sera appelée par la commande 'wtf'." -#: plugin.py:73 +#: plugin.py:75 msgid "" "\n" "\n" @@ -83,19 +83,19 @@ msgstr "" "\n" "Retourne le code du numéro d'erreur, ou le numéro d'erreur du code." -#: plugin.py:85 +#: plugin.py:87 msgid "I can't find the errno number for that code." msgstr "Je ne peux trouver le numéro d'erreur pour ce code." -#: plugin.py:88 +#: plugin.py:90 msgid "(unknown)" msgstr "(inconnu)" -#: plugin.py:89 +#: plugin.py:91 msgid "%s (#%i): %s" msgstr "%s (#%i) : %s" -#: plugin.py:94 +#: plugin.py:96 msgid "" "takes no arguments\n" "\n" @@ -106,7 +106,7 @@ msgstr "" "\n" "Retourn différentes informations unix-y à propos du processus de supybot." -#: plugin.py:102 +#: plugin.py:104 msgid "" "takes no arguments\n" "\n" @@ -117,7 +117,7 @@ msgstr "" "\n" "Retourne le pid du processus de Supybot." -#: plugin.py:112 +#: plugin.py:114 msgid "" " []\n" "\n" @@ -131,7 +131,7 @@ msgstr "" "\n" "Retourne le résultat d'un crypt() sur le . Si le n'est pas donné, utilise un sel aléatoire. Si on est sur un système glibc2, ajouter '$1$' au début de votre sel fera que crypt retournera un crypt basé sur MD5sum plutôt que sur le crypt basé sur DES standard." -#: plugin.py:131 +#: plugin.py:133 msgid "" "\n" "\n" @@ -144,31 +144,31 @@ msgstr "" "\n" "Retourne le résultat du parsage du avec aspell/ispell. Les résultats affichés sont triés du meilleur au pire en fonction de comment ils correspondent au ." -#: plugin.py:140 +#: plugin.py:142 msgid "The spell checking command is not configured. If one is installed, reconfigure supybot.plugins.Unix.spell.command appropriately." msgstr "La commande de vérification orthographique ne semble pas être installée. Si il y en a une, configurez supybot.plugins.Unix.spell.command de façon appropriée." -#: plugin.py:145 +#: plugin.py:147 msgid " must begin with an alphabet character." msgstr " doit commencer par une lettre de l'alphabet." -#: plugin.py:167 +#: plugin.py:169 msgid "No results found." msgstr "Aucun résultat trouvé." -#: plugin.py:178 +#: plugin.py:180 msgid "%q may be spelled correctly." msgstr "%q semble être orthographié correctement." -#: plugin.py:180 +#: plugin.py:182 msgid "I could not find an alternate spelling for %q" msgstr "Je ne peux pas trouver d'orthographe alternative pour %q" -#: plugin.py:184 +#: plugin.py:186 msgid "Possible spellings for %q: %L." msgstr "Orthographes possibles pour %q : %L" -#: plugin.py:193 +#: plugin.py:195 msgid "" "takes no arguments\n" "\n" @@ -179,15 +179,15 @@ msgstr "" "\n" "Retourne une fortune depuis le programme fortune de *nix." -#: plugin.py:213 +#: plugin.py:215 msgid "It seems the configured fortune command was not available." msgstr "Il semble que la commande fortune configurée ne soit pas disponible." -#: plugin.py:222 +#: plugin.py:224 msgid "The fortune command is not configured. If fortune is installed on this system, reconfigure the supybot.plugins.Unix.fortune.command configuration variable appropriately." msgstr "La commande fortune n'est pas configurée. Si fortune est installé sur ce système, configurez supybot.plugins.Unix.fortune.command de façon appropriée." -#: plugin.py:229 +#: plugin.py:231 msgid "" "[is] \n" "\n" @@ -200,11 +200,38 @@ msgstr "" "\n" "Retourne ce que l' pourrait signifier. 'wtf' est une commande *nix qui est apparue dans NetBSD 1.5. Dans la plupart des machines unices, elle fait partie du paquet 'bsdgames'." -#: plugin.py:244 +#: plugin.py:246 msgid "It seems the configured wtf command was not available." msgstr "Il semble que la commande wtf ne soit pas disponible." -#: plugin.py:253 +#: plugin.py:255 msgid "The wtf command is not configured. If it is installed on this system, reconfigure the supybot.plugins.Unix.wtf.command configuration variable appropriately." msgstr "La commande wtf n'est pas configurée. Si elle est installée sur ce système, veuillez configurer supybot.plugins.Unix.wtf.command de façon appropriée." +#: plugin.py:263 +msgid "" +"[--c ] [--i ] [--t ] [--W ] \n" +" Sends an ICMP echo request to the specified host.\n" +" The arguments correspond with those listed in ping(8). --c is\n" +" limited to 10 packets or less (default is 5). --i is limited to 5\n" +" or less. --W is limited to 10 or less.\n" +" " +msgstr "" +"[--c ] [--i ] [--t ] [--W ] \n" +"Envoie une requête ICMP echo à l' spécifié. Les arguments correspondent à ceux listés dans ping(8).--c est limité à 10 paquets (5 par défaut).--i est limité à 5 (5 par défaut).--W est limité à 10." + +#: plugin.py:315 +msgid "" +" \n" +" Calls any command available on the system, and returns its output.\n" +" Requires owner capability.\n" +" Note that being restricted to owner, this command does not do any\n" +" sanity checking on input/output. So it is up to you to make sure\n" +" you don't run anything that will spamify your channel or that \n" +" will bring your machine to its knees. \n" +" " +msgstr "" +"\n" +"\n" +"Appelle n'importe quelle commande disponible sur le système, et retourne sa sortie. Requiert la capacité owner.Notez que comme elle est restreinte au propriétaire, cette commande n'effectue aucune vérification sur les entrées/sorties. Donc, c'est à vous de vous assurez que ça ne fera rien qui spammera le canal ou que ça ne va pas faire tomber votre machine à genoux." + diff --git a/plugins/Web/locale/fr.po b/plugins/Web/locale/fr.po index 6c6d8cd6d..6b4ebea79 100644 --- a/plugins/Web/locale/fr.po +++ b/plugins/Web/locale/fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Supybot-fr\n" -"POT-Creation-Date: 2010-10-20 09:39+CEST\n" +"POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Supybot-fr \n" @@ -83,12 +83,12 @@ msgstr "" #: plugin.py:157 #: plugin.py:162 -msgid "%u is %i bytes long." -msgstr "%u est long de %i octets." +msgid "%u is %S long." +msgstr "%u est long de %S." #: plugin.py:164 -msgid "The server didn't tell me how long %u is but it's longer than %i bytes." -msgstr "Le serveur ne m'a pas dit quelle est la longueur de %u, mais c'est sûr que c'est plus que %i octets." +msgid "The server didn't tell me how long %u is but it's longer than %S." +msgstr "Le serveur ne m'a pas dit quelle est la longueur de %u, mais c'est sûr que c'est plus que %S." #: plugin.py:173 msgid "" @@ -106,8 +106,8 @@ msgid "That URL appears to have no HTML title." msgstr "Cette URL semble ne pas avoir de titre HTML." #: plugin.py:190 -msgid "That URL appears to have no HTML title within the first %i bytes." -msgstr "Ce URL semble ne pas avoir de titre HTML dans les %i premiers octets." +msgid "That URL appears to have no HTML title within the first %S." +msgstr "Ce URL semble ne pas avoir de titre HTML dans les %S au début du fichier." #: plugin.py:198 msgid "" From 2779b676c260b3bae2c28f2cb19cb1127b1b16e9 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 26 Feb 2011 11:26:02 +0100 Subject: [PATCH 70/85] Conditional: internationalize and localize in French --- plugins/Conditional/config.py | 10 ++ plugins/Conditional/locale/fr.po | 229 +++++++++++++++++++++++++++++++ plugins/Conditional/messages.pot | 40 +++--- plugins/Conditional/plugin.py | 28 ++++ 4 files changed, 287 insertions(+), 20 deletions(-) create mode 100644 plugins/Conditional/locale/fr.po diff --git a/plugins/Conditional/config.py b/plugins/Conditional/config.py index ab69968bd..f64814015 100644 --- a/plugins/Conditional/config.py +++ b/plugins/Conditional/config.py @@ -31,6 +31,16 @@ import supybot.conf as conf import supybot.registry as registry +try: + from supybot.i18n import PluginInternationalization + from supybot.i18n import internationalizeDocstring + _ = PluginInternationalization('Conditional') +except: + # This are useless functions that's allow to run the plugin on a bot + # without the i18n plugin + _ = lambda x:x + internationalizeDocstring = lambda x:x + 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 diff --git a/plugins/Conditional/locale/fr.po b/plugins/Conditional/locale/fr.po new file mode 100644 index 000000000..91f8d1cbf --- /dev/null +++ b/plugins/Conditional/locale/fr.po @@ -0,0 +1,229 @@ +msgid "" +msgstr "" +"Project-Id-Version: Gribble\n" +"POT-Creation-Date: 2010-11-02 11:39+CET\n" +"PO-Revision-Date: \n" +"Last-Translator: Valentin Lorentz \n" +"Language-Team: Supybot-fr \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Français\n" +"X-Poedit-Country: France\n" +"X-Poedit-SourceCharset: ASCII\n" + +#: plugin.py:64 +msgid "" +"Add the help for \"@plugin help Conditional\" here\n" +" This should describe *how* to use this plugin." +msgstr "" + +#: plugin.py:71 +msgid "Run a command from message, as if command was sent over IRC." +msgstr "" + +#: plugin.py:80 +msgid "" +" \n" +" \n" +" Runs if evaluates to true, runs \n" +" if it evaluates to false.\n" +" \n" +" Use other logical operators defined in this plugin and command nesting\n" +" to your advantage here.\n" +" " +msgstr "" +" \n" +"\n" +"Exécute la si la est évaluée à true, lance la si elle est évaluée à false. Utilisez d'autres opérateurs logiques définis dans ce plugin et l'imbrication de commandes à votre avantage." + +#: plugin.py:97 +msgid "" +" [ ... ]\n" +" \n" +" Returns true if all conditions supplied evaluate to true.\n" +" " +msgstr "" +" [ ... ]\n" +"\n" +"Retourne True si toutes les conditions sont évaluées à true." + +#: plugin.py:109 +msgid "" +" [ ... ]\n" +" \n" +" Returns true if any one of conditions supplied evaluates to true.\n" +" " +msgstr "" +" [ ... ]\n" +"\n" +"Retourne True si une au moins des conditions est évaluée à true." + +#: plugin.py:121 +msgid "" +" [ ... ]\n" +" \n" +" Returns true if only one of conditions supplied evaluates to true.\n" +" " +msgstr "" +" [ ... ]\n" +"\n" +"Retourne True si une seule des conditions est évaluée à true." + +#: plugin.py:133 +msgid "" +" \n" +" \n" +" Does a string comparison on and . \n" +" Returns true if they are equal.\n" +" " +msgstr "" +"<élément1> <élément2>\n" +"\n" +"Fait une comparaison de chaîne entre <élément1> et <élément2>. Retourne true si ils sont égaux." + +#: plugin.py:146 +msgid "" +" \n" +" \n" +" Does a string comparison on and . \n" +" Returns true if they are not equal.\n" +" " +msgstr "" +"<élément1> <élément2>\n" +"\n" +"Fait une comparaison de chaîne entre <élément1> et <élément2>. Retourne true si ils sont inégaux." + +#: plugin.py:159 +msgid "" +" \n" +" \n" +" Does a string comparison on and . \n" +" Returns true if is greater than .\n" +" " +msgstr "" +"<élément1> <élément2>\n" +"\n" +"Fait une comparaison de chaîne entre <élément1> et <élément2>. Retourne true si <élément1> est plus grand que <élément2>" + +#: plugin.py:172 +msgid "" +" \n" +" \n" +" Does a string comparison on and . \n" +" Returns true if is greater than or equal to .\n" +" " +msgstr "" +"<élément1> <élément2>\n" +"\n" +"Fait une comparaison de chaîne entre <élément1> et <élément2>. Retourne true si <élément1> est plus grand ou égal à <élément2>" + +#: plugin.py:185 +msgid "" +" \n" +" \n" +" Does a string comparison on and . \n" +" Returns true if is less than .\n" +" " +msgstr "" +"<élément1> <élément2>\n" +"\n" +"Fait une comparaison de chaîne entre <élément1> et <élément2>. Retourne true si <élément1> est plus petit que <élément2>" + +#: plugin.py:198 +msgid "" +" \n" +" \n" +" Does a string comparison on and . \n" +" Returns true if is less than or equal to .\n" +" " +msgstr "" +"<élément1> <élément2>\n" +"\n" +"Fait une comparaison de chaîne entre <élément1> et <élément2>. Retourne true si <élément1> est plus petit ou égal à <élément2>" + +#: plugin.py:211 +msgid "" +" \n" +" \n" +" Determines if is a substring of . \n" +" Returns true if is contained in .\n" +" " +msgstr "" +"<élément1> <élément2>\n" +"\n" +"Détermine si <élément1> est une sous-chaîne de <élément2>. Retourne true si <élément1> est contenu dans <élément2>" + +#: plugin.py:224 +msgid "" +" \n" +" \n" +" Does a numeric comparison on and . \n" +" Returns true if they are equal.\n" +" " +msgstr "" +"<élément1> <élément2>\n" +"\n" +"Fait une comparaison numérique entre <élément1> et <élément2>. Retourne true si ils sont égaux." + +#: plugin.py:237 +msgid "" +" \n" +" \n" +" Does a numeric comparison on and . \n" +" Returns true if they are not equal.\n" +" " +msgstr "" +"<élément1> <élément2>\n" +"\n" +"Fait une comparaison numérique entre <élément1> et <élément2>. Retourne true si ils sont inégaux." + +#: plugin.py:250 +msgid "" +" \n" +" \n" +" Does a numeric comparison on and . \n" +" Returns true if they is greater than .\n" +" " +msgstr "" +"<élément1> <élément2>\n" +"\n" +"Fait une comparaison numérique entre <élément1> et <élément2>. Retourne true si <élément1> est plus grand que <élément2>" + +#: plugin.py:263 +msgid "" +" \n" +" \n" +" Does a numeric comparison on and . \n" +" Returns true if is greater than or equal to .\n" +" " +msgstr "" +"<élément1> <élément2>\n" +"\n" +"Fait une comparaison numérique entre <élément1> et <élément2>. Retourne true si <élément1> est plus grand ou égal à <élément2>" + +#: plugin.py:276 +msgid "" +" \n" +" \n" +" Does a numeric comparison on and . \n" +" Returns true if is less than .\n" +" " +msgstr "" +"<élément1> <élément2>\n" +"\n" +"Fait une comparaison numérique entre <élément1> et <élément2>. Retourne true si <élément1> est plus petit que <élément2>" + +#: plugin.py:289 +msgid "" +" \n" +" \n" +" Does a numeric comparison on and . \n" +" Returns true if is less than or equal to .\n" +" " +msgstr "" +"<élément1> <élément2>\n" +"\n" +"Fait une comparaison numérique entre <élément1> et <élément2>. Retourne true si <élément1> est plus petit ou égal à <élément2>" + diff --git a/plugins/Conditional/messages.pot b/plugins/Conditional/messages.pot index 2eb00e392..0fd941dbd 100644 --- a/plugins/Conditional/messages.pot +++ b/plugins/Conditional/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2011-02-26 09:49+CET\n" +"POT-Creation-Date: 2010-11-02 11:39+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -15,19 +15,19 @@ msgstr "" "Generated-By: pygettext.py 1.5\n" -#: plugin.py:54 +#: plugin.py:64 #, docstring msgid "" "Add the help for \"@plugin help Conditional\" here\n" " This should describe *how* to use this plugin." msgstr "" -#: plugin.py:61 +#: plugin.py:71 #, docstring msgid "Run a command from message, as if command was sent over IRC." msgstr "" -#: plugin.py:69 +#: plugin.py:80 #, docstring msgid "" " \n" @@ -40,7 +40,7 @@ msgid "" " " msgstr "" -#: plugin.py:85 +#: plugin.py:97 #, docstring msgid "" " [ ... ]\n" @@ -49,7 +49,7 @@ msgid "" " " msgstr "" -#: plugin.py:96 +#: plugin.py:109 #, docstring msgid "" " [ ... ]\n" @@ -58,7 +58,7 @@ msgid "" " " msgstr "" -#: plugin.py:107 +#: plugin.py:121 #, docstring msgid "" " [ ... ]\n" @@ -67,7 +67,7 @@ msgid "" " " msgstr "" -#: plugin.py:118 +#: plugin.py:133 #, docstring msgid "" " \n" @@ -77,7 +77,7 @@ msgid "" " " msgstr "" -#: plugin.py:130 +#: plugin.py:146 #, docstring msgid "" " \n" @@ -87,7 +87,7 @@ msgid "" " " msgstr "" -#: plugin.py:142 +#: plugin.py:159 #, docstring msgid "" " \n" @@ -97,7 +97,7 @@ msgid "" " " msgstr "" -#: plugin.py:154 +#: plugin.py:172 #, docstring msgid "" " \n" @@ -107,7 +107,7 @@ msgid "" " " msgstr "" -#: plugin.py:166 +#: plugin.py:185 #, docstring msgid "" " \n" @@ -117,7 +117,7 @@ msgid "" " " msgstr "" -#: plugin.py:178 +#: plugin.py:198 #, docstring msgid "" " \n" @@ -127,7 +127,7 @@ msgid "" " " msgstr "" -#: plugin.py:190 +#: plugin.py:211 #, docstring msgid "" " \n" @@ -137,7 +137,7 @@ msgid "" " " msgstr "" -#: plugin.py:202 +#: plugin.py:224 #, docstring msgid "" " \n" @@ -147,7 +147,7 @@ msgid "" " " msgstr "" -#: plugin.py:214 +#: plugin.py:237 #, docstring msgid "" " \n" @@ -157,7 +157,7 @@ msgid "" " " msgstr "" -#: plugin.py:226 +#: plugin.py:250 #, docstring msgid "" " \n" @@ -167,7 +167,7 @@ msgid "" " " msgstr "" -#: plugin.py:238 +#: plugin.py:263 #, docstring msgid "" " \n" @@ -177,7 +177,7 @@ msgid "" " " msgstr "" -#: plugin.py:250 +#: plugin.py:276 #, docstring msgid "" " \n" @@ -187,7 +187,7 @@ msgid "" " " msgstr "" -#: plugin.py:262 +#: plugin.py:289 #, docstring msgid "" " \n" diff --git a/plugins/Conditional/plugin.py b/plugins/Conditional/plugin.py index 6f9422152..d349f2103 100644 --- a/plugins/Conditional/plugin.py +++ b/plugins/Conditional/plugin.py @@ -36,6 +36,16 @@ import supybot.callbacks as callbacks import re +try: + from supybot.i18n import PluginInternationalization + from supybot.i18n import internationalizeDocstring + _ = PluginInternationalization('Conditional') +except: + # This are useless functions that's allow to run the plugin on a bot + # without the i18n plugin + _ = lambda x:x + internationalizeDocstring = lambda x:x + # builtin any is overwritten by callbacks... and python2.4 doesn't have it def _any(iterable): for element in iterable: @@ -65,6 +75,7 @@ class Conditional(callbacks.Plugin): except Exception, e: log.exception('Uncaught exception in requested function:') + @internationalizeDocstring def cif(self, irc, msg, args, condition, ifcommand, elsecommand): """ @@ -81,6 +92,7 @@ class Conditional(callbacks.Plugin): irc.noReply() cif = wrap(cif, ['boolean', 'something', 'something']) + @internationalizeDocstring def cand(self, irc, msg, args, conds): """ [ ... ] @@ -92,6 +104,7 @@ class Conditional(callbacks.Plugin): irc.reply("false") cand = wrap(cand, [many('boolean'),]) + @internationalizeDocstring def cor(self, irc, msg, args, conds): """ [ ... ] @@ -103,6 +116,7 @@ class Conditional(callbacks.Plugin): irc.reply("false") cor = wrap(cor, [many('boolean'),]) + @internationalizeDocstring def cxor(self, irc, msg, args, conds): """ [ ... ] @@ -114,6 +128,7 @@ class Conditional(callbacks.Plugin): irc.reply("false") cxor = wrap(cxor, [many('boolean'),]) + @internationalizeDocstring def ceq(self, irc, msg, args, item1, item2): """ @@ -126,6 +141,7 @@ class Conditional(callbacks.Plugin): irc.reply('false') ceq = wrap(ceq, ['anything', 'anything']) + @internationalizeDocstring def ne(self, irc, msg, args, item1, item2): """ @@ -138,6 +154,7 @@ class Conditional(callbacks.Plugin): irc.reply('false') ne = wrap(ne, ['anything', 'anything']) + @internationalizeDocstring def gt(self, irc, msg, args, item1, item2): """ @@ -150,6 +167,7 @@ class Conditional(callbacks.Plugin): irc.reply('false') gt = wrap(gt, ['anything', 'anything']) + @internationalizeDocstring def ge(self, irc, msg, args, item1, item2): """ @@ -162,6 +180,7 @@ class Conditional(callbacks.Plugin): irc.reply('false') ge = wrap(ge, ['anything', 'anything']) + @internationalizeDocstring def lt(self, irc, msg, args, item1, item2): """ @@ -174,6 +193,7 @@ class Conditional(callbacks.Plugin): irc.reply('false') lt = wrap(lt, ['anything', 'anything']) + @internationalizeDocstring def le(self, irc, msg, args, item1, item2): """ @@ -186,6 +206,7 @@ class Conditional(callbacks.Plugin): irc.reply('false') le = wrap(le, ['anything', 'anything']) + @internationalizeDocstring def match(self, irc, msg, args, item1, item2): """ @@ -198,6 +219,7 @@ class Conditional(callbacks.Plugin): irc.reply('false') match = wrap(match, ['something', 'something']) + @internationalizeDocstring def nceq(self, irc, msg, args, item1, item2): """ @@ -210,6 +232,7 @@ class Conditional(callbacks.Plugin): irc.reply('false') nceq = wrap(nceq, ['float', 'float']) + @internationalizeDocstring def nne(self, irc, msg, args, item1, item2): """ @@ -222,6 +245,7 @@ class Conditional(callbacks.Plugin): irc.reply('false') nne = wrap(nne, ['float', 'float']) + @internationalizeDocstring def ngt(self, irc, msg, args, item1, item2): """ @@ -234,6 +258,7 @@ class Conditional(callbacks.Plugin): irc.reply('false') ngt = wrap(ngt, ['float', 'float']) + @internationalizeDocstring def nge(self, irc, msg, args, item1, item2): """ @@ -246,6 +271,7 @@ class Conditional(callbacks.Plugin): irc.reply('false') nge = wrap(nge, ['float', 'float']) + @internationalizeDocstring def nlt(self, irc, msg, args, item1, item2): """ @@ -258,6 +284,7 @@ class Conditional(callbacks.Plugin): irc.reply('false') nlt = wrap(nlt, ['float', 'float']) + @internationalizeDocstring def nle(self, irc, msg, args, item1, item2): """ @@ -269,6 +296,7 @@ class Conditional(callbacks.Plugin): else: irc.reply('false') nle = wrap(nle, ['float', 'float']) +Condition = internationalizeDocstring(Conditional) Class = Conditional From 401725f5118385a4047a7ac7353c34baebc99af7 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 26 Feb 2011 11:48:21 +0100 Subject: [PATCH 71/85] MessageParser: internationalization and French localization --- plugins/MessageParser/config.py | 47 +++--- plugins/MessageParser/locale/fr.po | 241 +++++++++++++++++++++++++++++ plugins/MessageParser/messages.pot | 103 ++++++++++-- plugins/MessageParser/plugin.py | 46 ++++-- 4 files changed, 386 insertions(+), 51 deletions(-) create mode 100644 plugins/MessageParser/locale/fr.po diff --git a/plugins/MessageParser/config.py b/plugins/MessageParser/config.py index f70dbdb24..a3f020d83 100644 --- a/plugins/MessageParser/config.py +++ b/plugins/MessageParser/config.py @@ -31,41 +31,42 @@ 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('MessageParser', True) +try: + from supybot.i18n import PluginInternationalization + from supybot.i18n import internationalizeDocstring + _ = PluginInternationalization('MessageParser') +except: + # This are useless functions that's allow to run the plugin on a bot + # without the i18n plugin + _ = lambda x:x + internationalizeDocstring = lambda x:x MessageParser = conf.registerPlugin('MessageParser') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(MessageParser, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) conf.registerChannelValue(MessageParser, 'enable', - registry.Boolean(True, """Determines whether the + registry.Boolean(True, _("""Determines whether the message parser is enabled. If enabled, will trigger on regexps - added to the regexp db.""")) + added to the regexp db."""))) conf.registerChannelValue(MessageParser, 'keepRankInfo', - registry.Boolean(True, """Determines whether we keep updating the usage - count for each regexp, for popularity ranking.""")) + registry.Boolean(True, _("""Determines whether we keep updating the usage + count for each regexp, for popularity ranking."""))) conf.registerChannelValue(MessageParser, 'rankListLength', - registry.Integer(20, """Determines the number of regexps returned - by the triggerrank command.""")) + registry.Integer(20, _("""Determines the number of regexps returned + by the triggerrank command."""))) conf.registerChannelValue(MessageParser, 'requireVacuumCapability', - registry.String('admin', """Determines the capability required (if any) to - vacuum the database.""")) + registry.String('admin', _("""Determines the capability required (if any) to + vacuum the database."""))) conf.registerChannelValue(MessageParser, 'requireManageCapability', - registry.String('admin; channel,op', - """Determines the + registry.String('admin; channel,op', _("""Determines the capabilities required (if any) to manage the regexp database, - including add, remove, lock, unlock. Use 'channel,capab' for + including add, remove, lock, unlock. Use 'channel,capab' for channel-level capabilities. - Note that absence of an explicit anticapability means user has - capability.""")) + Note that absence of an explicit anticapability means user has + capability."""))) conf.registerChannelValue(MessageParser, 'listSeparator', - registry.String(', ', """Determines the separator used between rexeps when - shown by the list command.""")) - + registry.String(', ', _("""Determines the separator used between rexeps when + shown by the list command."""))) + # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/plugins/MessageParser/locale/fr.po b/plugins/MessageParser/locale/fr.po new file mode 100644 index 000000000..64968eefa --- /dev/null +++ b/plugins/MessageParser/locale/fr.po @@ -0,0 +1,241 @@ +msgid "" +msgstr "" +"Project-Id-Version: Gribble\n" +"POT-Creation-Date: 2011-02-26 11:47+CET\n" +"PO-Revision-Date: \n" +"Last-Translator: Valentin Lorentz \n" +"Language-Team: Supybot-fr \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Français\n" +"X-Poedit-Country: France\n" +"X-Poedit-SourceCharset: Gribble\n" + +#: config.py:49 +msgid "" +"Determines whether the\n" +" message parser is enabled. If enabled, will trigger on regexps\n" +" added to the regexp db." +msgstr "Détermine si le parseur de messages est activé. S'il l'est, il réagira aux expressions régulières qui sont dans la base de données d'expressions régulières." + +#: config.py:53 +msgid "" +"Determines whether we keep updating the usage\n" +" count for each regexp, for popularity ranking." +msgstr "Détermine si on met à jour le compteur d'utilisation de chaque expression régulière, pour un classement de popularité" + +#: config.py:56 +msgid "" +"Determines the number of regexps returned\n" +" by the triggerrank command." +msgstr "Détermine le nombre d'expressions régulières retournées par la commande triggerrank" + +#: config.py:59 +msgid "" +"Determines the capability required (if any) to\n" +" vacuum the database." +msgstr "Détermine la capacité requise (s'il y en a une) pour faire un vacuum de la base de données." + +#: config.py:62 +msgid "" +"Determines the\n" +" capabilities required (if any) to manage the regexp database,\n" +" including add, remove, lock, unlock. Use 'channel,capab' for\n" +" channel-level capabilities.\n" +" Note that absence of an explicit anticapability means user has\n" +" capability." +msgstr "Détermine les capacités requises (s'il y en a) pour gérer la base de données d'expressions régulières, ce qui inclue l'ajout, la suppression, le verrouillage, et le déverrouillage. Utilisez 'canal,capa' pour des permissions par canal. Notez que l'absence de toute anti-capacité explicite signifit que l'utilisateur peut le faire." + +#: config.py:69 +msgid "" +"Determines the separator used between rexeps when\n" +" shown by the list command." +msgstr "Détermine le séparateur utilisé entre les expressions régulières affichées par la commande list." + +#: plugin.py:75 +msgid "" +"This plugin can set regexp triggers to activate the bot.\n" +" Use 'add' command to add regexp trigger, 'remove' to remove." +msgstr "Ce plugin peut définir les triggers pour activer le bot. Utilisez la commande 'add' pour ajouter un trigger et 'remove' pour en retirer un." + +#: plugin.py:83 +msgid "Create the database and connect to it." +msgstr "" + +#: plugin.py:106 +msgid "Use this to get a database for a specific channel." +msgstr "" + +#: plugin.py:129 +msgid "Run a command from message, as if command was sent over IRC." +msgstr "" + +#: plugin.py:137 +msgid "" +"Check if the user has any of the required capabilities to manage\n" +" the regexp database." +msgstr "" + +#: plugin.py:179 +msgid "" +"[] \n" +"\n" +" Associates with . is only\n" +" necessary if the message isn't sent on the channel\n" +" itself. Action is echoed upon regexp match, with variables $1, $2, \n" +" etc. being interpolated from the regexp match groups." +msgstr "" +"[] \n" +"\n" +"Associe l' à l'. est affiché après la correspondance avec l', avec les variables $1, $2, etc, récupérés à partir des groupes de correspondance de l'. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." + +#: plugin.py:201 +msgid "Invalid python regexp: %s" +msgstr "Expression régulière Python invalide : %s" + +#: plugin.py:213 +msgid "That trigger is locked." +msgstr "Ce trigger est bloqué." + +#: plugin.py:219 +msgid "" +"[] [--id] ]\n" +"\n" +" Removes the trigger for from the triggers database. \n" +" is only necessary if\n" +" the message isn't sent in the channel itself.\n" +" If option --id specified, will retrieve by regexp id, not content.\n" +" " +msgstr "" +"[] [--id] \n" +"\n" +"Supprime le déclencheur pour l' de la base de données des déclencheurs. Si l'option --id est spécifiée, l'id de l' sera récupéré, et non le contenu." + +#: plugin.py:241 +#: plugin.py:271 +#: plugin.py:294 +#: plugin.py:322 +#: plugin.py:352 +msgid "There is no such regexp trigger." +msgstr "Cette expression régulière n'existe pas." + +#: plugin.py:245 +msgid "This regexp trigger is locked." +msgstr "Cette expression régulière est verrouillée" + +#: plugin.py:257 +msgid "" +"[] \n" +"\n" +" Locks the so that it cannot be\n" +" removed or overwritten to. is only necessary if the message isn't\n" +" sent in the channel itself.\n" +" " +msgstr "" +"[] \n" +"\n" +"Verrouille l', ce qui fait que l'on ne puisse plus la supprimer ou la modifier. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." + +#: plugin.py:280 +msgid "" +"[] \n" +"\n" +" Unlocks the entry associated with so that it can be\n" +" removed or overwritten. is only necessary if the message isn't\n" +" sent in the channel itself.\n" +" " +msgstr "" +"[] \n" +"\n" +"Déverrouille l', ce qui fait que l'on peut à nouveau la supprimer ou la modifier. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." + +#: plugin.py:303 +msgid "" +"[] [--id] \n" +"\n" +" Looks up the value of in the triggers database.\n" +" is only necessary if the message isn't sent in the channel \n" +" itself.\n" +" If option --id specified, will retrieve by regexp id, not content.\n" +" " +msgstr "" +"[] [--id] \n" +"\n" +"Recherche la valeur de l' de la base de données des déclencheurs. Si l'option --id est spécifiée, l'id de l' sera récupéré, et non le contenu." + +#: plugin.py:332 +msgid "" +"[] [--id] \n" +"\n" +" Display information about in the triggers database.\n" +" is only necessary if the message isn't sent in the channel \n" +" itself.\n" +" If option --id specified, will retrieve by regexp id, not content.\n" +" " +msgstr "" +"[] [--id] \n" +"\n" +"Affiche des informations à propos de l' de la base de données des déclencheurs. Si l'option --id est spécifiée, l'id de l' sera récupéré, et non le contenu." + +#: plugin.py:355 +msgid "The regexp id is %d, regexp is \"%s\", and action is \"%s\". It was added by user %s on %s, has been triggered %d times, and is %s." +msgstr "L'id de l'expression régulière est %d, l'expression régulière est \"%s\", et l'action est \"%s\". Elle a été ajoutée par l'utilisateur %s le %s, et a été utilisée %d fois, et est %s" + +#: plugin.py:364 +msgid "locked" +msgstr "verouillée" + +#: plugin.py:364 +msgid "not locked" +msgstr "non verrouillée" + +#: plugin.py:371 +msgid "" +"[]\n" +"\n" +" Lists regexps present in the triggers database.\n" +" is only necessary if the message isn't sent in the channel \n" +" itself. Regexp ID listed in paretheses.\n" +" " +msgstr "" +"[]\n" +"\n" +"Liste les expressions régulières présentes dans la base de données. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." + +#: plugin.py:384 +#: plugin.py:410 +msgid "There are no regexp triggers in the database." +msgstr "Il n'y a pas d'expression régulière dans ma base de données." + +#: plugin.py:394 +msgid "" +"[]\n" +" \n" +" Returns a list of top-ranked regexps, sorted by usage count \n" +" (rank). The number of regexps returned is set by the \n" +" rankListLength registry value. is only necessary if the \n" +" message isn't sent in the channel itself.\n" +" " +msgstr "" +"[]\n" +"\n" +"Retourne une liste des expressions régulières les plus utilisées. Le nombre d'expressions régulières est définie par la variable de registre supybot.plugins.MessageParser.rankListLength. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." + +#: plugin.py:418 +msgid "" +"[]\n" +" \n" +" Vacuums the database for .\n" +" See SQLite vacuum doc here: http://www.sqlite.org/lang_vacuum.html\n" +" is only necessary if the message isn't sent in \n" +" the channel itself.\n" +" First check if user has the required capability specified in plugin \n" +" config requireVacuumCapability.\n" +" " +msgstr "" +"[]\n" +"\n" +"Fait un vacuum de la base de données pour le .Lisez la documentation de SQLite sur cette fonctionnalité : http://www.sqlite.org/lang_vacuum.htmlVérifie d'abord si l'utilisateur a bien la permission spécifiée dans la variable de configuration supybot.plugins.requireVacuumCapability n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." + diff --git a/plugins/MessageParser/messages.pot b/plugins/MessageParser/messages.pot index b220ea4eb..6a9766807 100644 --- a/plugins/MessageParser/messages.pot +++ b/plugins/MessageParser/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2011-02-26 09:49+CET\n" +"POT-Creation-Date: 2011-02-26 11:47+CET\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -15,36 +15,77 @@ msgstr "" "Generated-By: pygettext.py 1.5\n" -#: plugin.py:65 +#: config.py:49 +msgid "" +"Determines whether the\n" +" message parser is enabled. If enabled, will trigger on regexps\n" +" added to the regexp db." +msgstr "" + +#: config.py:53 +msgid "" +"Determines whether we keep updating the usage\n" +" count for each regexp, for popularity ranking." +msgstr "" + +#: config.py:56 +msgid "" +"Determines the number of regexps returned\n" +" by the triggerrank command." +msgstr "" + +#: config.py:59 +msgid "" +"Determines the capability required (if any) to\n" +" vacuum the database." +msgstr "" + +#: config.py:62 +msgid "" +"Determines the\n" +" capabilities required (if any) to manage the regexp database,\n" +" including add, remove, lock, unlock. Use 'channel,capab' for\n" +" channel-level capabilities.\n" +" Note that absence of an explicit anticapability means user has\n" +" capability." +msgstr "" + +#: config.py:69 +msgid "" +"Determines the separator used between rexeps when\n" +" shown by the list command." +msgstr "" + +#: plugin.py:75 #, docstring msgid "" "This plugin can set regexp triggers to activate the bot.\n" " Use 'add' command to add regexp trigger, 'remove' to remove." msgstr "" -#: plugin.py:73 +#: plugin.py:83 #, docstring msgid "Create the database and connect to it." msgstr "" -#: plugin.py:96 +#: plugin.py:106 #, docstring msgid "Use this to get a database for a specific channel." msgstr "" -#: plugin.py:119 +#: plugin.py:129 #, docstring msgid "Run a command from message, as if command was sent over IRC." msgstr "" -#: plugin.py:127 +#: plugin.py:137 #, docstring msgid "" "Check if the user has any of the required capabilities to manage\n" " the regexp database." msgstr "" -#: plugin.py:168 +#: plugin.py:179 #, docstring msgid "" "[] \n" @@ -55,7 +96,15 @@ msgid "" " etc. being interpolated from the regexp match groups." msgstr "" -#: plugin.py:207 +#: plugin.py:201 +msgid "Invalid python regexp: %s" +msgstr "" + +#: plugin.py:213 +msgid "That trigger is locked." +msgstr "" + +#: plugin.py:219 #, docstring msgid "" "[] [--id] ]\n" @@ -67,7 +116,15 @@ msgid "" " " msgstr "" -#: plugin.py:244 +#: plugin.py:241 plugin.py:271 plugin.py:294 plugin.py:322 plugin.py:352 +msgid "There is no such regexp trigger." +msgstr "" + +#: plugin.py:245 +msgid "This regexp trigger is locked." +msgstr "" + +#: plugin.py:257 #, docstring msgid "" "[] \n" @@ -78,7 +135,7 @@ msgid "" " " msgstr "" -#: plugin.py:266 +#: plugin.py:280 #, docstring msgid "" "[] \n" @@ -89,7 +146,7 @@ msgid "" " " msgstr "" -#: plugin.py:288 +#: plugin.py:303 #, docstring msgid "" "[] [--id] \n" @@ -101,7 +158,7 @@ msgid "" " " msgstr "" -#: plugin.py:316 +#: plugin.py:332 #, docstring msgid "" "[] [--id] \n" @@ -113,7 +170,19 @@ msgid "" " " msgstr "" -#: plugin.py:354 +#: plugin.py:355 +msgid "The regexp id is %d, regexp is \"%s\", and action is \"%s\". It was added by user %s on %s, has been triggered %d times, and is %s." +msgstr "" + +#: plugin.py:364 +msgid "locked" +msgstr "" + +#: plugin.py:364 +msgid "not locked" +msgstr "" + +#: plugin.py:371 #, docstring msgid "" "[]\n" @@ -124,7 +193,11 @@ msgid "" " " msgstr "" -#: plugin.py:376 +#: plugin.py:384 plugin.py:410 +msgid "There are no regexp triggers in the database." +msgstr "" + +#: plugin.py:394 #, docstring msgid "" "[]\n" @@ -136,7 +209,7 @@ msgid "" " " msgstr "" -#: plugin.py:399 +#: plugin.py:418 #, docstring msgid "" "[]\n" diff --git a/plugins/MessageParser/plugin.py b/plugins/MessageParser/plugin.py index 62d8e9dad..d72516696 100644 --- a/plugins/MessageParser/plugin.py +++ b/plugins/MessageParser/plugin.py @@ -41,6 +41,16 @@ import re import os import time +try: + from supybot.i18n import PluginInternationalization + from supybot.i18n import internationalizeDocstring + _ = PluginInternationalization('MessageParser') +except: + # This are useless functions that's allow to run the plugin on a bot + # without the i18n plugin + _ = lambda x:x + internationalizeDocstring = lambda x:x + #try: #import sqlite #except ImportError: @@ -164,6 +174,7 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler): for action in actions: self._runCommandFunction(irc, msg, action) + @internationalizeDocstring def add(self, irc, msg, args, channel, regexp, action): """[] @@ -187,7 +198,7 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler): try: re.compile(regexp) except Exception, e: - irc.error('Invalid python regexp: %s' % (e,)) + irc.error(_('Invalid python regexp: %s') % (e,)) return if ircdb.users.hasUser(msg.prefix): name = ircdb.users.getUser(msg.prefix).name @@ -199,10 +210,11 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler): db.commit() irc.replySuccess() else: - irc.error('That trigger is locked.') + irc.error(_('That trigger is locked.')) return add = wrap(add, ['channel', 'something', 'something']) + @internationalizeDocstring def remove(self, irc, msg, args, channel, optlist, regexp): """[] [--id] ] @@ -226,11 +238,11 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler): if len(results) != 0: (id, locked) = map(int, results[0]) else: - irc.error('There is no such regexp trigger.') + irc.error(_('There is no such regexp trigger.')) return if locked: - irc.error('This regexp trigger is locked.') + irc.error(_('This regexp trigger is locked.')) return cursor.execute("""DELETE FROM triggers WHERE id=?""", (id,)) @@ -240,6 +252,7 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler): getopts({'id': '',}), 'something']) + @internationalizeDocstring def lock(self, irc, msg, args, channel, regexp): """[] @@ -255,13 +268,14 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler): cursor.execute("SELECT id FROM triggers WHERE regexp=?", (regexp,)) results = cursor.fetchall() if len(results) == 0: - irc.error('There is no such regexp trigger.') + irc.error(_('There is no such regexp trigger.')) return cursor.execute("UPDATE triggers SET locked=1 WHERE regexp=?", (regexp,)) db.commit() irc.replySuccess() lock = wrap(lock, ['channel', 'text']) + @internationalizeDocstring def unlock(self, irc, msg, args, channel, regexp): """[] @@ -277,13 +291,14 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler): cursor.execute("SELECT id FROM triggers WHERE regexp=?", (regexp,)) results = cursor.fetchall() if len(results) == 0: - irc.error('There is no such regexp trigger.') + irc.error(_('There is no such regexp trigger.')) return cursor.execute("UPDATE triggers SET locked=0 WHERE regexp=?", (regexp,)) db.commit() irc.replySuccess() unlock = wrap(unlock, ['channel', 'text']) + @internationalizeDocstring def show(self, irc, msg, args, channel, optlist, regexp): """[] [--id] @@ -304,7 +319,7 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler): if len(results) != 0: (regexp, action) = results[0] else: - irc.error('There is no such regexp trigger.') + irc.error(_('There is no such regexp trigger.')) return irc.reply("The action for regexp trigger \"%s\" is \"%s\"" % (regexp, action)) @@ -312,6 +327,7 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler): getopts({'id': '',}), 'something']) + @internationalizeDocstring def info(self, irc, msg, args, channel, optlist, regexp): """[] [--id] @@ -333,23 +349,24 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler): (id, regexp, added_by, added_at, usage_count, action, locked) = results[0] else: - irc.error('There is no such regexp trigger.') + irc.error(_('There is no such regexp trigger.')) return - irc.reply("The regexp id is %d, regexp is \"%s\", and action is" + irc.reply(_("The regexp id is %d, regexp is \"%s\", and action is" " \"%s\". It was added by user %s on %s, has been " - "triggered %d times, and is %s." % (id, + "triggered %d times, and is %s.") % (id, regexp, action, added_by, time.strftime(conf.supybot.reply.format.time(), time.localtime(int(added_at))), usage_count, - locked and "locked" or "not locked",)) + locked and _("locked") or _("not locked"),)) info = wrap(info, ['channel', getopts({'id': '',}), 'something']) + @internationalizeDocstring def list(self, irc, msg, args, channel): """[] @@ -364,7 +381,7 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler): if len(results) != 0: regexps = results else: - irc.reply('There are no regexp triggers in the database.') + irc.reply(_('There are no regexp triggers in the database.')) return s = [ "\"%s\" (%d)" % (regexp[0], regexp[1]) for regexp in regexps ] @@ -372,6 +389,7 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler): irc.reply(separator.join(s)) list = wrap(list, ['channel']) + @internationalizeDocstring def rank(self, irc, msg, args, channel): """[] @@ -389,12 +407,13 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler): LIMIT ?""", (numregexps,)) regexps = cursor.fetchall() if len(regexps) == 0: - irc.reply('There are no regexp triggers in the database.') + irc.reply(_('There are no regexp triggers in the database.')) return s = [ "#%d \"%s\" (%d)" % (i+1, regexp[0], regexp[1]) for i, regexp in enumerate(regexps) ] irc.reply(", ".join(s)) rank = wrap(rank, ['channel']) + @internationalizeDocstring def vacuum(self, irc, msg, args, channel): """[] @@ -415,6 +434,7 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler): db.commit() irc.replySuccess() vacuum = wrap(vacuum, ['channel']) +MessageParser = internationalizeDocstring(MessageParser) Class = MessageParser From cb0139299aad273ca89cfdc365ce5e5b12626a9d Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Mon, 28 Feb 2011 09:47:00 +0100 Subject: [PATCH 72/85] BadWord & Channel: add Finish translation --- plugins/BadWords/locale/fi.po | 185 ++++++++++++++++++++++++++++++++++ plugins/Channel/locale/fi.po | 185 ++++++++++++++++++++++++++++++++++ 2 files changed, 370 insertions(+) create mode 100644 plugins/BadWords/locale/fi.po create mode 100644 plugins/Channel/locale/fi.po diff --git a/plugins/BadWords/locale/fi.po b/plugins/BadWords/locale/fi.po new file mode 100644 index 000000000..5bc983103 --- /dev/null +++ b/plugins/BadWords/locale/fi.po @@ -0,0 +1,185 @@ +msgid "" +msgstr "" +"Project-Id-Version: Supybot-fr\n" +"POT-Creation-Date: 2011-01-29 11:48+CET\n" +"PO-Revision-Date: \n" +"Last-Translator: Mika Suomalainen \n" +"Language-Team: Supybot-fr \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Français\n" +"X-Poedit-Country: France\n" +"X-Poedit-SourceCharset: ASCII\n" + +#: __init__.py:30 +msgid "" +"\n" +"Filters bad words on outgoing messages from the bot, so the bot can't be made\n" +"to say bad words.\n" +msgstr "" +"\n" +"Suodattaa botin ulostulevista viesteistä pahat sanat, jotta bottia ei saada\n" +"sanomaan pahoja sanoja.\n" + +#: config.py:40 +msgid "Would you like to add some bad words?" +msgstr "Haluaisitko lisätä joitakin pahoja sanoja?" + +#: config.py:41 +msgid "What words? (separate individual words by spaces)" +msgstr "Mitkä sanoja? (Eristä erilliset sanat käyttämällä välilyöntiä." + +#: config.py:53 +msgid "" +"Determines what words are\n" +" considered to be 'bad' so the bot won't say them." +msgstr "" +"Määrittää mitkä sanat ovat\n" +"'pahoja', jottei botti sano niitä." + +#: config.py:56 +msgid "" +"Determines whether the bot will require bad\n" +" words to be independent words, or whether it will censor them within other\n" +" words. For instance, if 'darn' is a bad word, then if this is true, 'darn'\n" +" will be censored, but 'darnit' will not. You probably want this to be\n" +" false. After changing this setting, the BadWords regexp needs to be\n" +" regenerated by adding/removing a word to the list, or reloading the\n" +" plugin." +msgstr "" +"Määrittää vaatiiko botti pahojen sanojen\n" +"olevan toisistaan riippumattomia sanoja, vai sensuroiko se ne toisten sanojen\n" +"sisältä. Esimerkiksi,jos 'pah' on paha sana, ja jos tämä on asetettu true asentoon, 'pah'\n" +"sensuroidaan, mutta 'pahus' ei sensuroida. Sinä luultavasti tahdot pitää tämän\n" +"false asennossa. Tämän asetuksen muuttamisen jälkeen, BadWords regexp täytyy\n" +"luoda uudelleen lisäämällä/poistamalla sana listalta, tai uudelleenlataamalla \n" +"lisäosa." + +#: config.py:73 +msgid "" +"Determines what characters will replace bad words; a\n" +" chunk of these characters matching the size of the replaced bad word will\n" +" be used to replace the bad words you've configured." +msgstr "" +"Määrittä mitkä merkit korvaavat pahat sanat; \n" +"osa näistä merkeistä, jotka sopivat pahasanan kokoon\n" +"käytetään määrittämiesi pahojen sanojen korvaamisessa." + +#: config.py:81 +msgid "" +"Determines the manner in which\n" +" bad words will be replaced. 'nastyCharacters' (the default) will replace a\n" +" bad word with the same number of 'nasty characters' (like those used in\n" +" comic books; configurable by supybot.plugins.BadWords.nastyChars).\n" +" 'simple' will replace a bad word with a simple strings (regardless of the\n" +" length of the bad word); this string is configurable via\n" +" supybot.plugins.BadWords.simpleReplacement." +msgstr "" +"Määrittää millä tavalla\n" +"pahat sanat korvataan. 'nastyCharacters' (oletus) korvaa\n" +"pahan sanan samalla määrällä 'häijyjä merkkejä' (kuten ne jotka ovat\n" +"sarjakuvissa; muokattavissa supybot.plugins.BadWords.nastyChars asetuksella).\n" +"'simple' korvaa pahan sanan yksinkertaisella merkkijonolla (riippumatta\n" +"pahan sanan koosta); tämä merkkijono on muokattavissa\n" +"asetuksella supybot.plugins.BadWords.simpleReplacement." + +#: config.py:89 +msgid "" +"Determines what word will replace bad\n" +" words if the replacement method is 'simple'." +msgstr "" +"Määrittää mikä sana korvaa pahat\n" +"sanat jos korvausmenetelmä on 'simple'." + +#: config.py:92 +msgid "" +"Determines whether the bot will strip\n" +" formatting characters from messages before it checks them for bad words.\n" +" If this is False, it will be relatively trivial to circumvent this plugin's\n" +" filtering. If it's True, however, it will interact poorly with other\n" +" plugins that do coloring or bolding of text." +msgstr "" +"Määrittää riisuuko botti\n" +" muotoilun merkeistä ennen kuin se tarkistaa ne pahojen sanojen varalta.\n" +" Jos tämä on 'False', on hyvin pinnallista kiertää tämän lisäosan\n" +" suodatusta. Jos se kuitenkin on 'True', se on huonosti vuorovaikutuksessa\n" +"tekstin värittämistä tai korostamista tekevien lisäosien kanssa." + +#: config.py:99 +msgid "" +"Determines whether the bot will kick people with\n" +" a warning when they use bad words." +msgstr "" +"Määrittää potkiiko botti ihmiset\n" +"varoituksella jos he käyttävät pahoja sanoja." + +#: config.py:102 +msgid "" +"You have been kicked for using a word\n" +" prohibited in the presence of this bot. Please use more appropriate\n" +" language in the future." +msgstr "" +"Sinut on potkittu kielletyn sanan\n" +"käytöstä tämän botin läsnäollessa. Ole hyvä ja käytä asianmukaisempaa\n" +" kieltä tulevaisuudessa." + +#: config.py:104 +msgid "" +"Determines the kick message used by the\n" +" bot when kicking users for saying bad words." +msgstr "" +"Määrittää potkimisviestin, jota\n" +"botti käyttää, kun potkii käyttäjiä pahojen sanojen käyttämistä." + +#: plugin.py:46 +msgid "" +"Maintains a list of words that the bot is not allowed to say.\n" +" Can also be used to kick people that say these words, if the bot\n" +" has op." +msgstr "" +"Säilyttää listaa sanoista, joita botin ei ole sallittua sanoa.\n" +"Voidaan myös käyttää potkimaan ihmisiä, jotka sanovat näitä sanoja, jos botilla\n" +"on kanavaoperaattori." + +#: plugin.py:113 +msgid "" +"takes no arguments\n" +"\n" +" Returns the list of words being censored.\n" +" " +msgstr "" +"ei ota parametrejä\n" +"\n" +"Palauttaa listan sanoista, joita sensuroidaan.\n" +" " + +#: plugin.py:123 +msgid "I'm not currently censoring any bad words." +msgstr "Tällä hetkellä ei sensuroida yhtään pahoja sanoja." + +#: plugin.py:128 +msgid "" +" [ ...]\n" +"\n" +" Adds all s to the list of words being censored.\n" +" " +msgstr "" +" [ ...]\n" +"\n" +"Lisää kaikki (t) sensuroidaan.\n" +" " + +#: plugin.py:140 +msgid "" +" [ ...]\n" +"\n" +" Removes s from the list of words being censored.\n" +" " +msgstr "" +" [ ...]\n" +"\n" +"Poistaa (t) sensuroitujen sanojen listalta.\n" +" " + diff --git a/plugins/Channel/locale/fi.po b/plugins/Channel/locale/fi.po new file mode 100644 index 000000000..5bc983103 --- /dev/null +++ b/plugins/Channel/locale/fi.po @@ -0,0 +1,185 @@ +msgid "" +msgstr "" +"Project-Id-Version: Supybot-fr\n" +"POT-Creation-Date: 2011-01-29 11:48+CET\n" +"PO-Revision-Date: \n" +"Last-Translator: Mika Suomalainen \n" +"Language-Team: Supybot-fr \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Français\n" +"X-Poedit-Country: France\n" +"X-Poedit-SourceCharset: ASCII\n" + +#: __init__.py:30 +msgid "" +"\n" +"Filters bad words on outgoing messages from the bot, so the bot can't be made\n" +"to say bad words.\n" +msgstr "" +"\n" +"Suodattaa botin ulostulevista viesteistä pahat sanat, jotta bottia ei saada\n" +"sanomaan pahoja sanoja.\n" + +#: config.py:40 +msgid "Would you like to add some bad words?" +msgstr "Haluaisitko lisätä joitakin pahoja sanoja?" + +#: config.py:41 +msgid "What words? (separate individual words by spaces)" +msgstr "Mitkä sanoja? (Eristä erilliset sanat käyttämällä välilyöntiä." + +#: config.py:53 +msgid "" +"Determines what words are\n" +" considered to be 'bad' so the bot won't say them." +msgstr "" +"Määrittää mitkä sanat ovat\n" +"'pahoja', jottei botti sano niitä." + +#: config.py:56 +msgid "" +"Determines whether the bot will require bad\n" +" words to be independent words, or whether it will censor them within other\n" +" words. For instance, if 'darn' is a bad word, then if this is true, 'darn'\n" +" will be censored, but 'darnit' will not. You probably want this to be\n" +" false. After changing this setting, the BadWords regexp needs to be\n" +" regenerated by adding/removing a word to the list, or reloading the\n" +" plugin." +msgstr "" +"Määrittää vaatiiko botti pahojen sanojen\n" +"olevan toisistaan riippumattomia sanoja, vai sensuroiko se ne toisten sanojen\n" +"sisältä. Esimerkiksi,jos 'pah' on paha sana, ja jos tämä on asetettu true asentoon, 'pah'\n" +"sensuroidaan, mutta 'pahus' ei sensuroida. Sinä luultavasti tahdot pitää tämän\n" +"false asennossa. Tämän asetuksen muuttamisen jälkeen, BadWords regexp täytyy\n" +"luoda uudelleen lisäämällä/poistamalla sana listalta, tai uudelleenlataamalla \n" +"lisäosa." + +#: config.py:73 +msgid "" +"Determines what characters will replace bad words; a\n" +" chunk of these characters matching the size of the replaced bad word will\n" +" be used to replace the bad words you've configured." +msgstr "" +"Määrittä mitkä merkit korvaavat pahat sanat; \n" +"osa näistä merkeistä, jotka sopivat pahasanan kokoon\n" +"käytetään määrittämiesi pahojen sanojen korvaamisessa." + +#: config.py:81 +msgid "" +"Determines the manner in which\n" +" bad words will be replaced. 'nastyCharacters' (the default) will replace a\n" +" bad word with the same number of 'nasty characters' (like those used in\n" +" comic books; configurable by supybot.plugins.BadWords.nastyChars).\n" +" 'simple' will replace a bad word with a simple strings (regardless of the\n" +" length of the bad word); this string is configurable via\n" +" supybot.plugins.BadWords.simpleReplacement." +msgstr "" +"Määrittää millä tavalla\n" +"pahat sanat korvataan. 'nastyCharacters' (oletus) korvaa\n" +"pahan sanan samalla määrällä 'häijyjä merkkejä' (kuten ne jotka ovat\n" +"sarjakuvissa; muokattavissa supybot.plugins.BadWords.nastyChars asetuksella).\n" +"'simple' korvaa pahan sanan yksinkertaisella merkkijonolla (riippumatta\n" +"pahan sanan koosta); tämä merkkijono on muokattavissa\n" +"asetuksella supybot.plugins.BadWords.simpleReplacement." + +#: config.py:89 +msgid "" +"Determines what word will replace bad\n" +" words if the replacement method is 'simple'." +msgstr "" +"Määrittää mikä sana korvaa pahat\n" +"sanat jos korvausmenetelmä on 'simple'." + +#: config.py:92 +msgid "" +"Determines whether the bot will strip\n" +" formatting characters from messages before it checks them for bad words.\n" +" If this is False, it will be relatively trivial to circumvent this plugin's\n" +" filtering. If it's True, however, it will interact poorly with other\n" +" plugins that do coloring or bolding of text." +msgstr "" +"Määrittää riisuuko botti\n" +" muotoilun merkeistä ennen kuin se tarkistaa ne pahojen sanojen varalta.\n" +" Jos tämä on 'False', on hyvin pinnallista kiertää tämän lisäosan\n" +" suodatusta. Jos se kuitenkin on 'True', se on huonosti vuorovaikutuksessa\n" +"tekstin värittämistä tai korostamista tekevien lisäosien kanssa." + +#: config.py:99 +msgid "" +"Determines whether the bot will kick people with\n" +" a warning when they use bad words." +msgstr "" +"Määrittää potkiiko botti ihmiset\n" +"varoituksella jos he käyttävät pahoja sanoja." + +#: config.py:102 +msgid "" +"You have been kicked for using a word\n" +" prohibited in the presence of this bot. Please use more appropriate\n" +" language in the future." +msgstr "" +"Sinut on potkittu kielletyn sanan\n" +"käytöstä tämän botin läsnäollessa. Ole hyvä ja käytä asianmukaisempaa\n" +" kieltä tulevaisuudessa." + +#: config.py:104 +msgid "" +"Determines the kick message used by the\n" +" bot when kicking users for saying bad words." +msgstr "" +"Määrittää potkimisviestin, jota\n" +"botti käyttää, kun potkii käyttäjiä pahojen sanojen käyttämistä." + +#: plugin.py:46 +msgid "" +"Maintains a list of words that the bot is not allowed to say.\n" +" Can also be used to kick people that say these words, if the bot\n" +" has op." +msgstr "" +"Säilyttää listaa sanoista, joita botin ei ole sallittua sanoa.\n" +"Voidaan myös käyttää potkimaan ihmisiä, jotka sanovat näitä sanoja, jos botilla\n" +"on kanavaoperaattori." + +#: plugin.py:113 +msgid "" +"takes no arguments\n" +"\n" +" Returns the list of words being censored.\n" +" " +msgstr "" +"ei ota parametrejä\n" +"\n" +"Palauttaa listan sanoista, joita sensuroidaan.\n" +" " + +#: plugin.py:123 +msgid "I'm not currently censoring any bad words." +msgstr "Tällä hetkellä ei sensuroida yhtään pahoja sanoja." + +#: plugin.py:128 +msgid "" +" [ ...]\n" +"\n" +" Adds all s to the list of words being censored.\n" +" " +msgstr "" +" [ ...]\n" +"\n" +"Lisää kaikki (t) sensuroidaan.\n" +" " + +#: plugin.py:140 +msgid "" +" [ ...]\n" +"\n" +" Removes s from the list of words being censored.\n" +" " +msgstr "" +" [ ...]\n" +"\n" +"Poistaa (t) sensuroitujen sanojen listalta.\n" +" " + From db849e21a06e673fdd2f3d90dce445de85ceff77 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Mon, 28 Feb 2011 16:02:17 +0100 Subject: [PATCH 73/85] Add a try/catch arround plugin directory determination (might be useful for some utilities...) --- src/i18n.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i18n.py b/src/i18n.py index fb610ea2f..d54ccfb5a 100644 --- a/src/i18n.py +++ b/src/i18n.py @@ -74,7 +74,13 @@ def getPluginDir(plugin_name): except KeyError: # It sometimes happens with Owner pass if filename == None: - filename = sys.modules['supybot.plugins.' + plugin_name].__file__ + try: + filename = sys.modules['supybot.plugins.' + plugin_name].__file__ + except: # In the case where the plugin is not loaded by Supybot + try: + filename = sys.modules['plugin'].__file__ + except: + filename = sys.modules['__main__'].__file__ if filename.endswith(".pyc"): filename = filename[0:-1] From 26c2e56905d24423dd26d1d7c09feb3b7501473c Mon Sep 17 00:00:00 2001 From: Mika Suomalainen Date: Mon, 28 Feb 2011 16:45:53 +0100 Subject: [PATCH 74/85] Admin & Alias & Anonymous & AutoMode & BadWord & Channel & ChannelLogger: update/create Finnish translation --- plugins/Admin/locale/fi.po | 226 ++++++++ plugins/Alias/locale/fi.po | 109 ++++ plugins/Anonymous/locale/fi.po | 101 ++++ plugins/AutoMode/locale/fi.po | 86 +++ plugins/BadWords/locale/fi.po | 9 +- plugins/Channel/locale/fi.po | 884 +++++++++++++++++++++++++---- plugins/ChannelLogger/locale/fi.po | 126 ++++ 7 files changed, 1418 insertions(+), 123 deletions(-) create mode 100644 plugins/Admin/locale/fi.po create mode 100644 plugins/Alias/locale/fi.po create mode 100644 plugins/Anonymous/locale/fi.po create mode 100644 plugins/AutoMode/locale/fi.po create mode 100644 plugins/ChannelLogger/locale/fi.po diff --git a/plugins/Admin/locale/fi.po b/plugins/Admin/locale/fi.po new file mode 100644 index 000000000..4dd96ecac --- /dev/null +++ b/plugins/Admin/locale/fi.po @@ -0,0 +1,226 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: Finnish translation of Admin plugin in Supybot\n" +"POT-Creation-Date: 2010-10-16 10:43+CEST\n" +"PO-Revision-Date: 2011-02-28 14:51+0200\n" +"Last-Translator: Mika Suomalainen \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" +"X-Poedit-Language: Finnish\n" +"X-Poedit-Country: FINLAND\n" + +#: plugin.py:54 +msgid "Nick/channel temporarily unavailable." +msgstr "Nimimerkki/kanava on väliaikaisesti saavutettavissa." + +#: plugin.py:72 +msgid "Cannot join %s, it's full." +msgstr "Ei voida liittyä %s, se on täynnä." + +#: plugin.py:80 +msgid "Cannot join %s, I was not invited." +msgstr "Ei voi liittyä %s, minua ei ole kutsuttu." + +#: plugin.py:88 +msgid "Cannot join %s, it's banned me." +msgstr "Ei voi liittyä %s, se on bannannut minut." + +#: plugin.py:96 +msgid "Cannot join %s, my keyword was wrong." +msgstr "Ei voi littyä %s, minun avainsanani oli väärä." + +#: plugin.py:104 +msgid "Cannot join %s, I'm not identified with the NickServ." +msgstr "Ei voi liittyä %s, minä en ole tunnistautunut NickServillä." + +#: plugin.py:134 +msgid "" +" []\n" +"\n" +" Tell the bot to join the given channel. If is given, it is used\n" +" when attempting to join the channel.\n" +" " +msgstr "" +" []\n" +"\n" +"Käskee botin liittyä annetulle kanavalle. Jos on annettu, sitä käytetään\n" +"yrittäessä liittyä kanavalle.\n" +" " + +#: plugin.py:147 +msgid "I'm already too close to maximum number of channels for this network." +msgstr "Minä olen liian lähellä kanavien maksimi määrää tällä verkolla." + +#: plugin.py:156 +msgid "" +"takes no arguments\n" +"\n" +" Returns the channels the bot is on. Must be given in private, in order\n" +" to protect the secrecy of secret channels.\n" +" " +msgstr "" +"Ei ota parametrejä\n" +"\n" +"Palauttaa listan kanavista, joilla botti on. Täytyy antaa yksityisviestillä salaisten kanavien\n" +"salaisuuden suojelemiseksi.\n" +" " + +#: plugin.py:166 +msgid "I'm not currently in any channels." +msgstr "En juuri nyt ole millään kanavalla." + +#: plugin.py:172 +msgid "My connection is restricted, I can't change nicks." +msgstr "Minun yhteyteni on rajoitettu. En voi vaihtaa nimimerkkiä." + +#: plugin.py:179 +msgid "Someone else is already using that nick." +msgstr "Joku muu käyttää jo tuota nimimerkkiä." + +#: plugin.py:186 +msgid "That nick is currently banned." +msgstr "Se nimimerkki on juuri nyt bannattu." + +#: plugin.py:193 +msgid "I can't change nicks, the server said %q." +msgstr "Minä en voi vaihtaa nimimerkkiä, palvelin sanoi %q" + +#: plugin.py:207 +msgid "" +"[]\n" +"\n" +" Changes the bot's nick to . If no nick is given, returns the\n" +" bot's current nick.\n" +" " +msgstr "" +"[]\n" +"\n" +"Vaihtaa botin nimimerkin . Jos nimimerkkiä ei ole annettu, palauttaa\n" +"botin nykyisen nimimerkin.\n" +" " + +#: plugin.py:222 +msgid "" +"[] []\n" +"\n" +" Tells the bot to part the list of channels you give it. is\n" +" only necessary if you want the bot to part a channel other than the\n" +" current channel. If is specified, use it as the part\n" +" message.\n" +" " +msgstr "" +"[] []\n" +"\n" +"Käskee botin poistua kanavilta, jotka annat sille. on\n" +"vaadittu jost tahdot botin poistuvat muulta, kuin \n" +"nykyiseltä kanavalta. Jos on määritetty, sitä käytetään poistumis\n" +"viestissä.\n" +" " + +#: plugin.py:240 +msgid "I'm not in %s." +msgstr "Minä en ole %s:ssa." + +#: plugin.py:252 +msgid "" +" \n" +"\n" +" Gives the user specified by (or the user to whom \n" +" currently maps) the specified capability \n" +" " +msgstr "" +" \n" +"\n" +"Antaa määrittämälle käyttäjälle (tai käyttäjälle jonka \n" +"ilmoittaa) määritetyn valtuuden \n" +" " + +#: plugin.py:272 +msgid "The \"owner\" capability can't be added in the bot. Use the supybot-adduser program (or edit the users.conf file yourself) to add an owner capability." +msgstr "\"Owner\" valtuutta ei voida lisätä bottiin. Käytä supybot-adduser ohjelmaa (tai muokkaa users.conf tiedostoa itse) lisätäksesi owner valtuuden." + +#: plugin.py:283 +msgid "You can't add capabilities you don't have." +msgstr "Et voi lisätä valtuuksia, joita sinulla ei ole." + +#: plugin.py:288 +msgid "" +" \n" +"\n" +" Takes from the user specified by (or the user to whom\n" +" currently maps) the specified capability \n" +" " +msgstr "" +" \n" +"\n" +"Ottaa määrittämältä käyttäjältä (tai käyttäjältä jonka\n" +" sopii) määritetyn valtuuden \n" +" " + +#: plugin.py:300 +msgid "That user doesn't have that capability." +msgstr "Tuolla käyttäjällä ei ole sitä valtuutta." + +#: plugin.py:302 +msgid "You can't remove capabilities you don't have." +msgstr "Sinä et voi poistaa valtuuksia, joita sinulla ei ole." + +#: plugin.py:310 +msgid "" +" []\n" +"\n" +" This will set a persistent ignore on or the hostmask\n" +" currently associated with . is an optional argument\n" +" specifying when (in \"seconds from now\") the ignore will expire; if\n" +" it isn't given, the ignore will never automatically expire.\n" +" " +msgstr "" +" []\n" +"\n" +"Tämä asettaa pysyvän ignoren tai hostmaskiin,\n" +" joka on tällä hetkellä yhdistetty . on vaihtoehtoinen paremetri,\n" +"joka määrittää (in \"sekuntit\") jolloin ignore vanhentuu; jos\n" +"sitä ei ole annettu, ignore ei vanhene automaattisesti ikinä.\n" +" " + +#: plugin.py:323 +msgid "" +"\n" +"\n" +" This will remove the persistent ignore on or the\n" +" hostmask currently associated with .\n" +" " +msgstr "" +"\n" +"\n" +"Tämä poistaa pysyvän ignoren tai\n" +"hostmaskista joka on tällä hetkellä yhdistetty .\n" +" " + +#: plugin.py:332 +msgid "%s wasn't in the ignores database." +msgstr "%s ei ollut ignore tietokannassa." + +#: plugin.py:337 +msgid "" +"takes no arguments\n" +"\n" +" Lists the hostmasks that the bot is ignoring.\n" +" " +msgstr "" +"Ei ota parametrejä\n" +"\n" +"Luetteloi hostmaskit jotka ovat botin ignoressa.\n" +" " + +#: plugin.py:345 +msgid "I'm not currently globally ignoring anyone." +msgstr "Kukaan ei juuri nyt ole globaalisti estolistalla." + diff --git a/plugins/Alias/locale/fi.po b/plugins/Alias/locale/fi.po new file mode 100644 index 000000000..f31f38dfb --- /dev/null +++ b/plugins/Alias/locale/fi.po @@ -0,0 +1,109 @@ +msgid "" +msgstr "" +"Project-Id-Version: Supybot Alias plugin\n" +"POT-Creation-Date: 2010-10-16 14:10+CEST\n" +"PO-Revision-Date: \n" +"Last-Translator: Mika Suomalainen \n" +"Language-Team: \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Finnish\n" +"X-Poedit-Country: FINLAND\n" + +#: plugin.py:45 +msgid "" +"Returns the channel the msg came over or the channel given in args.\n" +"\n" +" If the channel was given in args, args is modified (the channel is\n" +" removed).\n" +" " +msgstr "" +"Palauttaa kanavan, jolta viesti tuli tai kanavan, joka on annettu parametreissä.\n" +"\n" +"Jos kanava annetaan parametreissä, parametriä muokataan (kanava\n" +"poistetaan).\n" +" " + +#: plugin.py:164 +msgid " at least" +msgstr "vähintään" + +#: plugin.py:165 +msgid "" +"\n" +"\n" +"Alias for %q." +msgstr "" +"\n" +"\n" +"Alias %q:lle." + +#: plugin.py:220 +msgid "" +"\n" +"\n" +" Locks an alias so that no one else can change it.\n" +" " +msgstr "" +"\n" +"\n" +"Lukitsee aliaksen, niin ettei kukaan muu voi muuttaa sitä.\n" +" " + +#: plugin.py:229 +#: plugin.py:243 +msgid "There is no such alias." +msgstr "Tuollaista aliasta ei ole." + +#: plugin.py:234 +msgid "" +"\n" +"\n" +" Unlocks an alias so that people can define new aliases over it.\n" +" " +msgstr "" +"\n" +"\n" +"Poistaa lukituksen aliaksesta, jotta ihmiset vouvat määrittää uusia aliaksia sen päälle.\n" +" " + +#: plugin.py:254 +msgid "That name isn't valid. Try %q instead." +msgstr "Nimi ei ole kelvollinen. Yritä sen sijaa %q:ta." + +#: plugin.py:292 +msgid "" +" \n" +"\n" +" Defines an alias that executes . The \n" +" should be in the standard \"command argument [nestedcommand argument]\"\n" +" arguments to the alias; they'll be filled with the first, second, etc.\n" +" arguments. $1, $2, etc. can be used for required arguments. @1, @2,\n" +" etc. can be used for optional arguments. $* simply means \"all\n" +" remaining arguments,\" and cannot be combined with optional arguments.\n" +" " +msgstr "" +" \n" +"\n" +"Määrittää aliaksen , joka suorittaa . \n" +"Aliaksen pitäisi olla tavallisia \"komento parametri [sisäkkäiset parametrit]\"\n" +"parametrejä aliakselle; ne täytetään ensinmäinen, toinen, jne.\n" +"Parametrit. $1, $2, jne. voidaan käyttää vaadittuina parametreinä. @1, @2,\n" +"jne. voidaan käyttää vaihtoehtoisina parametreinä. $* tarkoittaa yksinkertaisesti \"kaikki\n" +"jäljellä olevat parametrit,\" ja johon ei voida yhdistää vaihtoehtoisia parametrejä.\n" +" " + +#: plugin.py:315 +msgid "" +"\n" +"\n" +" Removes the given alias, if unlocked.\n" +" " +msgstr "" +"\n" +"\n" +"Poistaa annetun aliaksen jos se ei ole lukittu.\n" +" " + diff --git a/plugins/Anonymous/locale/fi.po b/plugins/Anonymous/locale/fi.po new file mode 100644 index 000000000..6d2c01bae --- /dev/null +++ b/plugins/Anonymous/locale/fi.po @@ -0,0 +1,101 @@ +msgid "" +msgstr "" +"Project-Id-Version: Supybot Anonymous\n" +"POT-Creation-Date: 2010-10-16 15:14+CEST\n" +"PO-Revision-Date: \n" +"Last-Translator: Mika Suomalainen \n" +"Language-Team: \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Finnish\n" +"X-Poedit-Country: FINLAND\n" + +#: config.py:49 +msgid "" +"Determines whether\n" +" the bot should require people trying to use this plugin to be in the\n" +" channel they wish to anonymously send to." +msgstr "" +"Määrittelee täytyisikö \n" +"botin vaatia ihmisiä, jotka yrittävät käyttää tätä lisäosaa olla\n" +"kanavalla, jonne he tahtovat lähettää viestin tuntemattomasti." + +#: config.py:53 +msgid "" +"Determines whether the bot should require\n" +" people trying to use this plugin to be registered." +msgstr "" +"Määrittelee täytyisikö botin vaatia\n" +"ihmisiä, jotka yrittävät käyttää tätä lisäosaa olla rekisteröityneitä." + +#: config.py:56 +msgid "" +"Determines what capability (if any) the bot should\n" +" require people trying to use this plugin to have." +msgstr "" +"Määrittää mitä valtuutta (jos mitään) botin täytyisi\n" +"vaatia ihmisiltä, jotka yrittävät käyttää tätä lisäosaa." + +#: config.py:59 +msgid "" +"Determines whether the bot will require \n" +" targets of the \"say\" command to be public (i.e., channels). If this is\n" +" True, the bot will allow people to use the \"say\" command to send private\n" +" messages to other users." +msgstr "" +"Määrittelee täytyykö botin vaatia \n" +"\"say\" komennon olevan julkisia (esim., kanavia). Jos tämä on\n" +" True, botti sallii ihmisten käyttää \"say\" komentoa lähettääkseen yksityisviestejä \n" +" toisille käyttäjille." + +#: plugin.py:41 +msgid "" +"This plugin allows users to act through the bot anonymously. The 'do'\n" +" command has the bot perform an anonymous action in a given channel, and\n" +" the 'say' command allows other people to speak through the bot. Since\n" +" this can be fairly well abused, you might want to set\n" +" supybot.plugins.Anonymous.requireCapability so only users with that\n" +" capability can use this plugin. For extra security, you can require that\n" +" the user be *in* the channel they are trying to address anonymously with\n" +" supybot.plugins.Anonymous.requirePresenceInChannel, or you can require\n" +" that the user be registered by setting\n" +" supybot.plugins.Anonymous.requireRegistration.\n" +" " +msgstr "" +"Tämä lisäosa sallii käyttäjien toimia botin kautta tuntemattomasti.\n" +"Komento 'do' sallii botin tehdä Anonymous toiminnon annetulla kanavalla ja\n" +"'say' komento sallii toisten ihmisten puhua botin läpi. Koska\n" +"tätä voidaan väärinkäyttää helposti voit tahtoa asettaa \n" +"supybot.plugins.Anonymous.requireCapability niin, että vain käyttäjät tuolla\n" +"valtuudella. Lisäturvallisuuden vuoksi voit vaatia, että käyttäjän täytyy *olla* kanavalla joita he yrittävät puhutella tuntemattomasti asetuksella supybot.plugins.Anonymous.requirePresenceInChannel, tai sinä voit vaatia,\n" +"että tuo käyttäjä on rekisteröitynyt asetuksella\n" +"supybot.plugins.Anonymous.requireRegistration" + +#: plugin.py:81 +msgid "" +" \n" +"\n" +" Sends to . Can only send to if\n" +" supybot.plugins.Anonymous.allowPrivateTarget is True.\n" +" " +msgstr "" +" \n" +"\n" +"Lähettää . voi lähettää vain jos\n" +"supybot.plugins.Anonymous.allowPrivateTarget on True.\n" +" " + +#: plugin.py:95 +msgid "" +" \n" +"\n" +" Performs in .\n" +" " +msgstr "" +" \n" +"\n" +"Suorittaa .\n" +" " + diff --git a/plugins/AutoMode/locale/fi.po b/plugins/AutoMode/locale/fi.po new file mode 100644 index 000000000..c90680ff1 --- /dev/null +++ b/plugins/AutoMode/locale/fi.po @@ -0,0 +1,86 @@ +msgid "" +msgstr "" +"Project-Id-Version: Supybot AutoMode\n" +"POT-Creation-Date: 2010-10-16 18:48+CEST\n" +"PO-Revision-Date: \n" +"Last-Translator: Mika Suomalainen \n" +"Language-Team: \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Finnish\n" +"X-Poedit-Country: FINLAND\n" + +#: config.py:46 +msgid "" +"Determines whether this plugin is enabled.\n" +" " +msgstr "" +"Määrittää onko tämä lisäosa käytössä.\n" +" " + +#: config.py:49 +msgid "" +"Determines whether this plugin will automode\n" +" owners." +msgstr "" +"Määrittää käytetäänkö tätä lisäosaa \n" +"omistajiin." + +#: config.py:52 +msgid "" +"Determines whether the bot will \"fall through\n" +" to halfop/voicing when auto-opping is turned off but\n" +" auto-halfopping/voicing are turned on." +msgstr "" +"Määrittää \"siirtyykö botti\n" +"halfoppaamiseen/voicen antamiseen kun automaatti-oppaaminen on on/off asennossa mutta\n" +"automaattinen-halfoppaaminen/voicen antaminen ovat käytössä." + +#: config.py:56 +msgid "" +"Determines whether the bot will automatically\n" +" op people with the ,op capability when they join the channel.\n" +" " +msgstr "" +"Määrittää oppaako botti\n" +"ihmiset ,op valtuudella automaattisesti, kun he liittyvät kanavalle.\n" +" " + +#: config.py:60 +msgid "" +"Determines whether the bot will automatically\n" +" halfop people with the ,halfop capability when they join the\n" +" channel." +msgstr "" +"Miirittää halfoppaako botti automaattisesti, kun\n" +"ihmiset ,halfop valtuudella, kun he liittyvät \n" +"kanavalle." + +#: config.py:64 +msgid "" +"Determines whether the bot will automatically\n" +" voice people with the ,voice capability when they join the\n" +" channel." +msgstr "" +"Määrittää antaako botti automaattisesti voicen\n" +" ihmisille, joilla on ,voice valtuus kun he liittyvät\n" +"kanavalle." + +#: config.py:68 +msgid "" +"Determines whether the bot will automatically\n" +" ban people who join the channel and are on the banlist." +msgstr "" +"Määrittää bannaako botti ihmiset,\n" +"jotka liittyvät kanavalle ja ovat banni listalla." + +#: config.py:71 +msgid "" +"Determines how many seconds the bot\n" +" will automatically ban a person when banning." +msgstr "" +"Määrittää kuinka moneksi sekuntiksi botti\n" +"bannaa henkilön, kun ollaan bannaamassa." + diff --git a/plugins/BadWords/locale/fi.po b/plugins/BadWords/locale/fi.po index 5bc983103..d76b8d2be 100644 --- a/plugins/BadWords/locale/fi.po +++ b/plugins/BadWords/locale/fi.po @@ -1,17 +1,16 @@ msgid "" msgstr "" -"Project-Id-Version: Supybot-fr\n" +"Project-Id-Version: Supybot BadWords\n" "POT-Creation-Date: 2011-01-29 11:48+CET\n" "PO-Revision-Date: \n" "Last-Translator: Mika Suomalainen \n" -"Language-Team: Supybot-fr \n" +"Language-Team: \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Poedit-Language: Français\n" -"X-Poedit-Country: France\n" -"X-Poedit-SourceCharset: ASCII\n" +"X-Poedit-Language: Finnish\n" +"X-Poedit-Country: FINLAND\n" #: __init__.py:30 msgid "" diff --git a/plugins/Channel/locale/fi.po b/plugins/Channel/locale/fi.po index 5bc983103..55fc37a28 100644 --- a/plugins/Channel/locale/fi.po +++ b/plugins/Channel/locale/fi.po @@ -1,185 +1,833 @@ msgid "" msgstr "" -"Project-Id-Version: Supybot-fr\n" -"POT-Creation-Date: 2011-01-29 11:48+CET\n" +"Project-Id-Version: Supybot Channel\n" +"POT-Creation-Date: 2010-10-25 13:10+CEST\n" "PO-Revision-Date: \n" "Last-Translator: Mika Suomalainen \n" -"Language-Team: Supybot-fr \n" +"Language-Team: \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Poedit-Language: Français\n" -"X-Poedit-Country: France\n" -"X-Poedit-SourceCharset: ASCII\n" +"X-Poedit-Language: Finnish\n" +"X-Poedit-Country: FINLAND\n" -#: __init__.py:30 +#: config.py:48 msgid "" +"Determines whether the bot will always try to\n" +" rejoin a channel whenever it's kicked from the channel." +msgstr "" +"Määrittää yrittääkö botti aina\n" +"liittyä kanavalle uudelleen, kun se on potkittu kanavalta." + +#: plugin.py:69 +msgid "" +"[] [ ...]\n" "\n" -"Filters bad words on outgoing messages from the bot, so the bot can't be made\n" -"to say bad words.\n" +" Sets the mode in to , sending the arguments given.\n" +" is only necessary if the message isn't sent in the channel\n" +" itself.\n" +" " msgstr "" +"[] [ ...]\n" "\n" -"Suodattaa botin ulostulevista viesteistä pahat sanat, jotta bottia ei saada\n" -"sanomaan pahoja sanoja.\n" +"Asettaa , tilaksi lähettäen annetut parametrin.\n" +" on vaadittu vain, jos viestiä ei lähetetä kanavalta\n" +"jonka tilaa vaihdetaan.\n" +" " -#: config.py:40 -msgid "Would you like to add some bad words?" -msgstr "Haluaisitko lisätä joitakin pahoja sanoja?" +#: plugin.py:76 +msgid "change the mode" +msgstr "vaihda tila" -#: config.py:41 -msgid "What words? (separate individual words by spaces)" -msgstr "Mitkä sanoja? (Eristä erilliset sanat käyttämällä välilyöntiä." - -#: config.py:53 +#: plugin.py:80 msgid "" -"Determines what words are\n" -" considered to be 'bad' so the bot won't say them." +"[] []\n" +"\n" +" Sets the channel limit to . If is 0, or isn't given,\n" +" removes the channel limit. is only necessary if the message\n" +" isn't sent in the channel itself.\n" +" " msgstr "" -"Määrittää mitkä sanat ovat\n" -"'pahoja', jottei botti sano niitä." +"[] []\n" +"\n" +"Asettaa kanavan . Jos on 0, tai ei annettu,\n" +"kanava rajoitus poistetaan. on vaadittu cain jos\n" +"viestiä ei lähetetä kanavalta itseltään.\n" +" " -#: config.py:56 +#: plugin.py:90 +msgid "change the limit" +msgstr "Vaihda rajoitusta" + +#: plugin.py:95 msgid "" -"Determines whether the bot will require bad\n" -" words to be independent words, or whether it will censor them within other\n" -" words. For instance, if 'darn' is a bad word, then if this is true, 'darn'\n" -" will be censored, but 'darnit' will not. You probably want this to be\n" -" false. After changing this setting, the BadWords regexp needs to be\n" -" regenerated by adding/removing a word to the list, or reloading the\n" -" plugin." +"[]\n" +"\n" +" Sets +m on , making it so only ops and voiced users can\n" +" send messages to the channel. is only necessary if the\n" +" message isn't sent in the channel itself.\n" +" " msgstr "" -"Määrittää vaatiiko botti pahojen sanojen\n" -"olevan toisistaan riippumattomia sanoja, vai sensuroiko se ne toisten sanojen\n" -"sisältä. Esimerkiksi,jos 'pah' on paha sana, ja jos tämä on asetettu true asentoon, 'pah'\n" -"sensuroidaan, mutta 'pahus' ei sensuroida. Sinä luultavasti tahdot pitää tämän\n" -"false asennossa. Tämän asetuksen muuttamisen jälkeen, BadWords regexp täytyy\n" -"luoda uudelleen lisäämällä/poistamalla sana listalta, tai uudelleenlataamalla \n" -"lisäosa." +"[]\n" +"\n" +"Asettaa tilan +m , sallien vain operaattoireiden ja käyttäjien jolla on voice\n" +"lähettää viestejä kanavalle. on vaadittu vain jos\n" +"viestiä ei lähetetä itse kanavalta.\n" +" " -#: config.py:73 +#: plugin.py:102 +msgid "moderate the channel" +msgstr "valvo kanavaa" + +#: plugin.py:106 msgid "" -"Determines what characters will replace bad words; a\n" -" chunk of these characters matching the size of the replaced bad word will\n" -" be used to replace the bad words you've configured." +"[]\n" +"\n" +" Sets -m on , making it so everyone can\n" +" send messages to the channel. is only necessary if the\n" +" message isn't sent in the channel itself.\n" +" " msgstr "" -"Määrittä mitkä merkit korvaavat pahat sanat; \n" -"osa näistä merkeistä, jotka sopivat pahasanan kokoon\n" -"käytetään määrittämiesi pahojen sanojen korvaamisessa." +"[]\n" +"\n" +"Asettaa -m tilan , sallien jokaisen\n" +"lähettää viestejä kanavalla. on vaadittu vain jos\n" +" viestiä ei lähetetä kanavalta itseltään.\n" +" " -#: config.py:81 +#: plugin.py:114 +msgid "unmoderate the channel" +msgstr "lopeta kanavan valvominen" + +#: plugin.py:118 msgid "" -"Determines the manner in which\n" -" bad words will be replaced. 'nastyCharacters' (the default) will replace a\n" -" bad word with the same number of 'nasty characters' (like those used in\n" -" comic books; configurable by supybot.plugins.BadWords.nastyChars).\n" -" 'simple' will replace a bad word with a simple strings (regardless of the\n" -" length of the bad word); this string is configurable via\n" -" supybot.plugins.BadWords.simpleReplacement." +"[] []\n" +"\n" +" Sets the keyword in to . If is not given, removes\n" +" the keyword requirement to join . is only necessary\n" +" if the message isn't sent in the channel itself.\n" +" " msgstr "" -"Määrittää millä tavalla\n" -"pahat sanat korvataan. 'nastyCharacters' (oletus) korvaa\n" -"pahan sanan samalla määrällä 'häijyjä merkkejä' (kuten ne jotka ovat\n" -"sarjakuvissa; muokattavissa supybot.plugins.BadWords.nastyChars asetuksella).\n" -"'simple' korvaa pahan sanan yksinkertaisella merkkijonolla (riippumatta\n" -"pahan sanan koosta); tämä merkkijono on muokattavissa\n" -"asetuksella supybot.plugins.BadWords.simpleReplacement." +"[] []\n" +"\n" +"Asettaa avainsanan . Jos ei ole annettu, poistaa\n" +"poistaa avainsana vaatimuksen liittymisestä. on vaadittu vain\n" +"jos viestiä ei lähetetä kanavalta itsestään.\n" +" " -#: config.py:89 +#: plugin.py:130 +msgid "change the keyword" +msgstr "vaihtaa avainsanan" + +#: plugin.py:135 msgid "" -"Determines what word will replace bad\n" -" words if the replacement method is 'simple'." +"[] [ ...]\n" +"\n" +" If you have the #channel,op capability, this will give all the s\n" +" you provide ops. If you don't provide any s, this will op you.\n" +" is only necessary if the message isn't sent in the channel\n" +" itself.\n" +" " msgstr "" -"Määrittää mikä sana korvaa pahat\n" -"sanat jos korvausmenetelmä on 'simple'." +"[] [ ...]\n" +"\n" +"Jos sinulla on #kanava,op valtuus, tämä antaa kaikille \n" +"sinun tarjoamat opit. Jos et anna yhtään , tämä oppaa sinut.\n" +" on vaadittu vain jos viestiä ei lähetetä kanavalta\n" +"itsestään.\n" +" " -#: config.py:92 +#: plugin.py:147 +msgid "op someone" +msgstr "oppaa joku" + +#: plugin.py:151 msgid "" -"Determines whether the bot will strip\n" -" formatting characters from messages before it checks them for bad words.\n" -" If this is False, it will be relatively trivial to circumvent this plugin's\n" -" filtering. If it's True, however, it will interact poorly with other\n" -" plugins that do coloring or bolding of text." +"[] [ ...]\n" +"\n" +" If you have the #channel,halfop capability, this will give all the\n" +" s you provide halfops. If you don't provide any s, this\n" +" will give you halfops. is only necessary if the message isn't\n" +" sent in the channel itself.\n" +" " msgstr "" -"Määrittää riisuuko botti\n" -" muotoilun merkeistä ennen kuin se tarkistaa ne pahojen sanojen varalta.\n" -" Jos tämä on 'False', on hyvin pinnallista kiertää tämän lisäosan\n" -" suodatusta. Jos se kuitenkin on 'True', se on huonosti vuorovaikutuksessa\n" -"tekstin värittämistä tai korostamista tekevien lisäosien kanssa." +"[] [ ...]\n" +"\n" +"Jos sinulla on #kanava,halfop valtuus, tämä antaa kaikille\n" +" halfopit. Jos et anna yhtään , tämä\n" +"antaa sinulle halfopit. on vaadittu vain jos viestiä ei\n" +"lähetetä kanavalta itseltään.\n" +" " -#: config.py:99 +#: plugin.py:163 +msgid "halfop someone" +msgstr "halfoppaa joku" + +#: plugin.py:168 msgid "" -"Determines whether the bot will kick people with\n" -" a warning when they use bad words." +"[] [ ...]\n" +"\n" +" If you have the #channel,voice capability, this will voice all the\n" +" s you provide. If you don't provide any s, this will\n" +" voice you. is only necessary if the message isn't sent in the\n" +" channel itself.\n" +" " msgstr "" -"Määrittää potkiiko botti ihmiset\n" -"varoituksella jos he käyttävät pahoja sanoja." +"[] [ ...]\n" +"\n" +"Jos sinulla on #kanava,halfop valtuus, tämä antaa kaikille\n" +" halfopit. Jos et anna yhtään , tämä\n" +"antaa sinulle halfopit. on vaadittu vain jos viestiä ei\n" +"lähetetä kanavalta itseltään.\n" +" " -#: config.py:102 +#: plugin.py:190 +msgid "voice someone" +msgstr "anna jollekin voice" + +#: plugin.py:195 msgid "" -"You have been kicked for using a word\n" -" prohibited in the presence of this bot. Please use more appropriate\n" -" language in the future." +"[] [ ...]\n" +"\n" +" If you have the #channel,op capability, this will remove operator\n" +" privileges from all the nicks given. If no nicks are given, removes\n" +" operator privileges from the person sending the message.\n" +" " msgstr "" -"Sinut on potkittu kielletyn sanan\n" -"käytöstä tämän botin läsnäollessa. Ole hyvä ja käytä asianmukaisempaa\n" -" kieltä tulevaisuudessa." +"[] [ ...]\n" +"\n" +"Jos sinulla on #kanava,op valtuus, tämä poistaa operaattori\n" +"kaikilta annetuilta nimimerkeiltä. Jos nimimerkkejä ei ole annettu poistaa\n" +"poistaa operaattorioikeudet henkilöltä, joka lähettää viestin.\n" +" " -#: config.py:104 +#: plugin.py:202 +msgid "I cowardly refuse to deop myself. If you really want me deopped, tell me to op you and then deop me yourself." +msgstr "Minä pelkurimaisesti kieltäydyn deoppaamasta itseäni. Jos todella tahdot minut deopatuksi, käske minun opata sinut ja sitten deoppaa minut itse." + +#: plugin.py:210 +msgid "deop someone" +msgstr "deoppaa joku" + +#: plugin.py:215 msgid "" -"Determines the kick message used by the\n" -" bot when kicking users for saying bad words." +"[] [ ...]\n" +"\n" +" If you have the #channel,op capability, this will remove half-operator\n" +" privileges from all the nicks given. If no nicks are given, removes\n" +" half-operator privileges from the person sending the message.\n" +" " msgstr "" -"Määrittää potkimisviestin, jota\n" -"botti käyttää, kun potkii käyttäjiä pahojen sanojen käyttämistä." +"[] [ ...]\n" +"\n" +"Jos sinulla on #kanava,op valtuus, tämä poistaa puoli-kanavaoperaattorin\n" +"kaikilta annetuilta nimimerkeiltä. Jos nimimerkkejä ei anneta\n" +"viestin lähettäneeltä henkilöltä poistetaan puoli-kanavaoperaattorin oikeudet.\n" +" " -#: plugin.py:46 +#: plugin.py:222 +msgid "I cowardly refuse to dehalfop myself. If you really want me dehalfopped, tell me to op you and then dehalfop me yourself." +msgstr "Minä pelkurimaisesti kieltäydyn dehalfoppaamasta itseäni. Jos haluat minut depuoliopatuksi, käske minun opata sinut ja sitten dehalfoppaa minut itse." + +#: plugin.py:230 +msgid "dehalfop someone" +msgstr "dehalfoppaa joku" + +#: plugin.py:235 msgid "" -"Maintains a list of words that the bot is not allowed to say.\n" -" Can also be used to kick people that say these words, if the bot\n" -" has op." +"[] [ ...]\n" +"\n" +" If you have the #channel,op capability, this will remove voice from all\n" +" the nicks given. If no nicks are given, removes voice from the person\n" +" sending the message.\n" +" " msgstr "" -"Säilyttää listaa sanoista, joita botin ei ole sallittua sanoa.\n" -"Voidaan myös käyttää potkimaan ihmisiä, jotka sanovat näitä sanoja, jos botilla\n" -"on kanavaoperaattori." +"[] [ ...]\n" +"\n" +"Jos sinulla on #kanava,op valtuus, tämä poistaa voicen kaikilta\n" +"annetuilta nimimerkeiltä. Jos nimimerkkejä ei ole annettu, poistaa voicen henkilöltä\n" +"joka lähettää viestin.\n" +" " -#: plugin.py:113 +#: plugin.py:242 +msgid "I cowardly refuse to devoice myself. If you really want me devoiced, tell me to op you and then devoice me yourself." +msgstr "Minä pelkurimaisesti kieltäydyn poistamasta voicea itseltäni. Jos todella tahdot poistaa minulta voicen, käske minut oppaamaan sinut ja sitten poista ääni minulta itse." + +#: plugin.py:255 +msgid "" +"[]\n" +"\n" +" If you have the #channel,op capability, this will cause the bot to\n" +" \"cycle\", or PART and then JOIN the channel. is only necessary\n" +" if the message isn't sent in the channel itself.\n" +" " +msgstr "" +"[]\n" +"\n" +" Jos sinulla on #kanava,op valtuus, tämä aiheuttaa botin tekemään\n" +"\"cyclen\", tai POISTUMAAN ja LIITTYMÄÄN kanavalle. on vaadittu vain\n" +"jos viestiä ei lähetetä kanavalta itsestään.\n" +" " + +#: plugin.py:268 +msgid "" +"[] [, , ...] []\n" +"\n" +" Kicks (s) from for . If isn't given,\n" +" uses the nick of the person making the command as the reason.\n" +" is only necessary if the message isn't sent in the channel\n" +" itself.\n" +" " +msgstr "" +"[] [, , ...] []\n" +"\n" +"Potkii . Jos ei ole annettu,\n" +"käyttää komennon antajan nimimerkkiä syynä.\n" +" on vaadittu vain jos viestiä ei lähetetä kanavalta\n" +"itsestään.\n" +" " + +#: plugin.py:276 +msgid "I cowardly refuse to kick myself." +msgstr "Minä pelkurimaisesti kieltäydyn potkimasta itseäni." + +#: plugin.py:281 +msgid "The reason you gave is longer than the allowed length for a KICK reason on this server." +msgstr "Syy, jonka annoit on pidempi kuin sallittu pituus POTKIMIS syyksi tällä palvelimella." + +#: plugin.py:286 +msgid "kick someone" +msgstr "potki joku" + +#: plugin.py:292 +msgid "" +"[] [--{exact,nick,user,host}] [] []\n" +"\n" +" If you have the #channel,op capability, this will kickban for\n" +" as many seconds as you specify, or else (if you specify 0 seconds or\n" +" don't specify a number of seconds) it will ban the person indefinitely.\n" +" --exact bans only the exact hostmask; --nick bans just the nick;\n" +" --user bans just the user, and --host bans just the host. You can\n" +" combine these options as you choose. is a reason to give for\n" +" the kick.\n" +" is only necessary if the message isn't sent in the channel\n" +" itself.\n" +" " +msgstr "" +"[] [--{exact,nick,user,host}] [] []\n" +"\n" +"Jos sinulla on #kanava,op valtuus, tämä kickbannaa \n" +"niin moneksi sekuntiksi kuin määrität, tai muuten (jos määrität 0 sekuntia tai\n" +"tai et määritä sekuntien lukumäärää) se bannaa henkilön ikuisesti.\n" +"--exact bannaa vain hostmaskin; --nick bannaa vain nimimerkin;\n" +"--user bannaa vain käyttäjän, ja --host bannaa vain isännän. Voit\n" +"yhdistää näitä vaihtoehtoja mielesi mukaan. on syy, jonka vuoksi annat\n" +"potkun.\n" +" on vaadittu vain jos viestiä ei lähetetä kanavassa\n" +"itsestään.\n" +" " + +#: plugin.py:311 +msgid "I cowardly refuse to kickban myself." +msgstr "Minä pelkurimaisesti kieltäydyn kickbannaamasta itseäni." + +#: plugin.py:318 +msgid "I haven't seen %s." +msgstr "Minä en ole nähnyt %s:ää." + +#: plugin.py:326 +msgid "I cowardly refuse to ban myself." +msgstr "Minä pelkurimaisesti kieltäydyn bannaamasta itseäni." + +#: plugin.py:352 +msgid "%s has %s too, you can't ban him/her/it." +msgstr "%s:llä on %s myös, et voi bannata häntä/sitä." + +#: plugin.py:364 +msgid "kick or ban someone" +msgstr "potki tai bannaa joku" + +#: plugin.py:371 +msgid "" +"[] []\n" +"\n" +" Unbans on . If is not given, unbans\n" +" any hostmask currently banned on that matches your current\n" +" hostmask. Especially useful for unbanning yourself when you get\n" +" unexpectedly (or accidentally) banned from the channel. is\n" +" only necessary if the message isn't sent in the channel itself.\n" +" " +msgstr "" +"[] []\n" +"\n" +"Poistaa bannin . Jos ei ole annettu, poistaa bannin\n" +"kaikilta hostmaskeilta, jotka ovat bannattuja ja täsmäävät sinun\n" +"hostmaskiisi. Etenkin hyödyllinen jos joudut \n" +"odottomattomasti (tai vahingossa) bannatuksi kanavalta. on\n" +"vaadittu vain jos viestiä ei lähetetä kanavalta itseltään.\n" +" " + +#: plugin.py:388 +msgid "All bans on %s matching %s have been removed." +msgstr "Kaikki bannit, jotka sopivat %s :ään %s:tä on poistettu." + +#: plugin.py:392 +msgid "No bans matching %s were found on %s." +msgstr "Banneja, jotka täsmäävät %s:ään ei löydetty %s:tä." + +#: plugin.py:395 +msgid "unban someone" +msgstr "poista jonkun banni" + +#: plugin.py:400 +msgid "" +"[] \n" +"\n" +" If you have the #channel,op capability, this will invite \n" +" to join . is only necessary if the message isn't\n" +" sent in the channel itself.\n" +" " +msgstr "" +"[] \n" +"\n" +"Jos sinulla on #kanava,op valtuus, tämä kutsuu \n" +"liittymään . on vaadittu vain jos viestiö\n" +"ei lähetetä kanavalla itsessään.\n" +" " + +#: plugin.py:409 +msgid "invite someone" +msgstr "kutsu joku" + +#: plugin.py:428 +msgid "%s is already in %s." +msgstr "%s on jo %s:ssä." + +#: plugin.py:435 +msgid "There is no %s on this network." +msgstr "%s ei ole tässä verkossa." + +#: plugin.py:447 +msgid "" +"[]\n" +"\n" +" If you have the #channel,op capability, this will \"lobotomize\" the\n" +" bot, making it silent and unanswering to all requests made in the\n" +" channel. is only necessary if the message isn't sent in\n" +" the channel itself.\n" +" " +msgstr "" +"[]\n" +"\n" +"Jos sinulla on #kanava,op valtuus, tämä \"lobotomoi\" \n" +"botin tehden sen hiljaisesksi ja vastaamattomaksi kaikkiin pyyntöihin,\n" +" jotka on tehty kanavalla. on vaadittu vain jos viestiä ei lähetetä\n" +" kanavalla itsessään.\n" +" " + +#: plugin.py:462 +msgid "" +"[]\n" +"\n" +" If you have the #channel,op capability, this will unlobotomize the\n" +" bot, making it respond to requests made in the channel again.\n" +" is only necessary if the message isn't sent in the channel\n" +" itself.\n" +" " +msgstr "" +"[]\n" +"\n" +"Jos sinulla on #kanava,op valtuus, tämä poistaa lobotimian\n" +"botista, saaden sen jälleen vastaamaan kaikkiin pyyntöihin kanavalla.\n" +" on vaadittu vain jos viestiä ei lähetetä kanavalla itsessään.\n" +" " + +#: plugin.py:477 msgid "" "takes no arguments\n" "\n" -" Returns the list of words being censored.\n" -" " +" Returns the channels in which this bot is lobotomized.\n" +" " msgstr "" "ei ota parametrejä\n" "\n" -"Palauttaa listan sanoista, joita sensuroidaan.\n" +"Palauttaa kanavat, joilla botti on lobotomoitu.\n" +" " + +#: plugin.py:492 +msgid "I'm currently lobotomized in %L." +msgstr "Minut on tällähetkellä lobotomoity %L:ssa." + +#: plugin.py:495 +msgid "I'm not currently lobotomized in any channels that you're in." +msgstr "En tällä hetkellä ole lobotomoitu millään kanavalla, jolla sinä olet." + +#: plugin.py:502 +msgid "" +"[] []\n" +"\n" +" If you have the #channel,op capability, this will effect a\n" +" persistent ban from interacting with the bot on the given\n" +" (or the current hostmask associated with . Other\n" +" plugins may enforce this ban by actually banning users with\n" +" matching hostmasks when they join. is an optional\n" +" argument specifying when (in \"seconds from now\") the ban should\n" +" expire; if none is given, the ban will never automatically expire.\n" +" is only necessary if the message isn't sent in the\n" +" channel itself.\n" +" " +msgstr "" +"[] []\n" +"\n" +" Jos sinulla on #kanava,op valtuus, tämä aiheuttaa\n" +"pysyvän bannin estääkseen bottia olemasta vuorovaikutuksessa annetun\n" +" kanssa (tai nykyisen hostmaskin. Toiset\n" +"toiset lisäosat saattavat pakottaa tämän bannin bannaamalla käyttäjät\n" +" täsmäävällä hostmaskilla kun he liittyvät kanavalle. on vaihtoehtoinen\n" +"parametri , kun määritetään ( \"sekunteja alkaen nyt\") milloin banni\n" +" vanhenee; jos mitään ei ole annettu, banni ei ikinä vanhene automaattisesti.\n" +" on vaadittu vain jos viestiä ei lähetetä\n" +"kanavalla itsessään.\n" +" " + +#: plugin.py:522 +msgid "" +"[] \n" +"\n" +" If you have the #channel,op capability, this will remove the\n" +" persistent ban on . is only necessary if the\n" +" message isn't sent in the channel itself.\n" +" " +msgstr "" +"[] \n" +"\n" +"Jos sinulla on #kanava,op valtuus, tämä poistaa\n" +"pysyvän bannin . on vaadittu vain jos\n" +"viestiä ei lähetetä kanavalla itsellään.\n" +" " + +#: plugin.py:534 +msgid "There are no persistent bans for that hostmask." +msgstr "" +"Tuolla hostmaskilla ei ole pysyviä banneja.[]\n" +"\n" +"Jos sinulla on #kanava,op valtuus, tämä näyttää\n" +"pysyvät bannit #kanavalla.\n" +" " + +#: plugin.py:539 +msgid "" +"[]\n" +"\n" +" If you have the #channel,op capability, this will show you the\n" +" current persistent bans on #channel.\n" +" " +msgstr "" +"[]\n" +"\n" +"Jos sinulla on #kanava,op valtuus, tämä näyttää\n" +"pysyvät bannit #kanavalla.\n" +" " + +#: plugin.py:549 +msgid "%q (expires %t)" +msgstr "%q (venhentuu %t)" + +#: plugin.py:552 +msgid "%q (never expires)" +msgstr "%q (ei ikinä vanhennu)" + +#: plugin.py:556 +msgid "There are no persistent bans on %s." +msgstr "%s ei ole pysyviä banneja." + +#: plugin.py:563 +msgid "" +"[] []\n" +"\n" +" If you have the #channel,op capability, this will set a persistent\n" +" ignore on or the hostmask currently\n" +" associated with . is an optional argument\n" +" specifying when (in \"seconds from now\") the ignore will expire; if\n" +" it isn't given, the ignore will never automatically expire.\n" +" is only necessary if the message isn't sent in the\n" +" channel itself.\n" +" " +msgstr "" +"[] []\n" +"\n" +"Jos sinulla on #kanava,op valtuus, tämä asettaa pysyvän\n" +"ignoren tai hostmaskiin\n" +"joka on käytössä. on vaihtoehtoinen parametri\n" +"kun määritetään ( \"sekunteja alkaen nyt\")milloin ignore vanhentuu; jos\n" +"sitä ei ole annettu, banni ei ikinä vanhene automaattisesti.\n" +" on vaadittu vain jos viestiä ei lähetetä\n" +"kanavalla itsessään.\n" +" " + +#: plugin.py:581 +msgid "" +"[] \n" +"\n" +" If you have the #channel,op capability, this will remove the\n" +" persistent ignore on in the channel. is only\n" +" necessary if the message isn't sent in the channel itself.\n" +" " +msgstr "" +"[] \n" +"\n" +"Jos sinulla on #kanava,op valtuus, tämä poistaa\n" +"pysyvän ignoren kanavalla. on vaadittu\n" +"viestiä ei lähetetä kanavalla itsellään.\n" +" " + +#: plugin.py:593 +msgid "There are no ignores for that hostmask." +msgstr "Tuolla hostmaskilla ei ole pysyviä ignoreja." + +#: plugin.py:598 +msgid "" +"[]\n" +"\n" +" Lists the hostmasks that the bot is ignoring on the given channel.\n" +" is only necessary if the message isn't sent in the\n" +" channel itself.\n" +" " +msgstr "" +"[]\n" +"\n" +" Luettelee hostmaskit, jotka ovat botin ignoressa annetulla kanavalla.\n" +" on vaadittu vain, jos viestiä ei lähetetä\n" +"kanavalla itsellään.\n" +" " + +#: plugin.py:607 +msgid "I'm not currently ignoring any hostmasks in %q" +msgstr "Minun ignoressani ei ole yhtään hostmaskia %q:ssa." + +#: plugin.py:618 +msgid "" +"[] [ ...]\n" +"\n" +" If you have the #channel,op capability, this will give the user\n" +" (or the user to whom maps)\n" +" the capability in the channel. is only\n" +" necessary if the message isn't sent in the channel itself.\n" +" " +msgstr "" +"[] [ ...]\n" +"\n" +"Jos sinulla on #kanava,op valtuus, tämä antaa käyttäjä\n" +" (tai käyttäjälle, jonka määrittää)\n" +" valtuuden kanavalla. on\n" +"vaadittu vain jos viestiä ei lähetetä kanavalla itsessään.\n" +" " + +#: plugin.py:634 +msgid "" +"[] [ ...]\n" +"\n" +" If you have the #channel,op capability, this will take from the\n" +" user currently identified as (or the user to whom \n" +" maps) the capability in the channel. is only\n" +" necessary if the message isn't sent in the channel itself.\n" +" " +msgstr "" +"[] [ ...]\n" +"\n" +"Jos sinulla on #kanava,op valtuus, tämä ottaa\n" +"tällä hetkellä tunnistautuneelta käyttäjältä (tai käyttäjältä, jonka \n" +"täsmää) valtuuden kanavalla. on\n" +" on vaadittu vain jos viestiä ei lähetetä kanavalla itsessään.\n" +" " + +#: plugin.py:653 +msgid "That user didn't have the %L %s." +msgstr "Tuolla käyttäjällä ei ole %L:ää %s:ssä." + +#: plugin.py:662 +#, fuzzy +msgid "" +"[] {True|False}\n" +"\n" +" If you have the #channel,op capability, this will set the default\n" +" response to non-power-related (that is, not {op, halfop, voice}\n" +" capabilities to be the value you give. is only necessary\n" +" if the message isn't sent in the channel itself.\n" +" " +msgstr "" +"[] {True|False}\n" +"\n" +" Jos sinulla on #kanava,op valtuus, tämä asettaa oletus\n" +"ei voimaan-liittyviin (se on, ei {op, halfop, voice}\n" +"valtuus arvo, jonka annat. on vaadittu vain\n" +"jos viestiä ei lähetetä kanavalla itsellään.\n" +" " + +#: plugin.py:680 +msgid "" +"[] [ ...]\n" +"\n" +" If you have the #channel,op capability, this will add the channel\n" +" capability for all users in the channel. is\n" +" only necessary if the message isn't sent in the channel itself.\n" +" " +msgstr "" +"[] [ ...]\n" +"\n" +"Jos sinulla on #kanava,op valtuus, tämä lisää\n" +"valtuuden kaikille käyttäjille kanavalla. on\n" +"vaadittu vain jos viestiä ei lähetetä kanavalla itsellään.\n" +" " + +#: plugin.py:695 +msgid "" +"[] [ ...]\n" +"\n" +" If you have the #channel,op capability, this will unset the channel\n" +" capability so each user's specific capability or the\n" +" channel default capability will take precedence. is only\n" +" necessary if the message isn't sent in the channel itself.\n" +" " +msgstr "" +"[] [ ...]\n" +"\n" +"Jos sinulla on #kanava,op valtuus, tämä poistaa kanava valtuus\n" +"valtuuden joten jokaisen käyttäjäkohtainen valtuus tai\n" +"kanavan oletus valtuus otetaan käyttöön. on vaadittu\n" +"vain jos viestiä ei lähetetä kanavalla itsessään.\n" +" " + +#: plugin.py:711 +msgid "capability" +msgstr "valtuusMinä en tiedä %L:stä %s:llä" + +#: plugin.py:714 +msgid "I do not know about the %L %s." +msgstr "" +"[]\n" +"\n" +" Palauttaa olevat valtuudet. on\n" +"vaadittu vain jos viestiä ei lähetetä kanavalla itsellään.\n" +" " + +#: plugin.py:721 +msgid "" +"[]\n" +"\n" +" Returns the capabilities present on the . is\n" +" only necessary if the message isn't sent in the channel itself.\n" +" " +msgstr "" +"[] [] []\n" +"\n" +"Jos sinulla on #kanava,op valtuus, tämä poistaa käytöstä\n" +". Jos on annrttu, poistetaan käytöstä\n" +"vain siitä lisäosasta. Jos vain on annettu, kaikki komennot\n" +"annetusta lisäosasta poistetaan käytöstä. on vaadittu vain jos\n" +"viestiä ei lähetetä kanavalla itsellään.\n" " " -#: plugin.py:123 -msgid "I'm not currently censoring any bad words." -msgstr "Tällä hetkellä ei sensuroida yhtään pahoja sanoja." - -#: plugin.py:128 +#: plugin.py:733 msgid "" -" [ ...]\n" +"[] [] []\n" "\n" -" Adds all s to the list of words being censored.\n" +" If you have the #channel,op capability, this will disable the \n" +" in . If is provided, will be disabled only\n" +" for that plugin. If only is provided, all commands in the\n" +" given plugin will be disabled. is only necessary if the\n" +" message isn't sent in the channel itself.\n" " " msgstr "" -" [ ...]\n" +"[] [] []\n" "\n" -"Lisää kaikki (t) sensuroidaan.\n" +"Jos sinulla on #kanava,op valtuus, tämä poistaa käytöstä\n" +". Jos on annrttu, poistetaan käytöstä\n" +"vain siitä lisäosasta. Jos vain on annettu, kaikki komennot\n" +"annetusta lisäosasta poistetaan käytöstä. on vaadittu vain jos\n" +"viestiä ei lähetetä kanavalla itsellään.\n" " " -#: plugin.py:140 +#: plugin.py:749 +#: plugin.py:788 +msgid "The %s plugin does not have a command called %s." +msgstr "%s lisäosalla ei ole komentoa %s." + +#: plugin.py:756 +#: plugin.py:795 +msgid "No plugin or command named %s could be found." +msgstr "Lisäosaa tai komentoa nimeltä %s ei löytynyt." + +#: plugin.py:772 msgid "" -" [ ...]\n" +"[] [] []\n" "\n" -" Removes s from the list of words being censored.\n" +" If you have the #channel,op capability, this will enable the \n" +" in if it has been disabled. If is provided,\n" +" will be enabled only for that plugin. If only is\n" +" provided, all commands in the given plugin will be enabled. \n" +" is only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" -" [ ...]\n" +"[] [] []\n" "\n" -"Poistaa (t) sensuroitujen sanojen listalta.\n" +"Jos sinulla on #kanava,op valtuus, tämä ottaa käyttöön\n" +" jos se on ollut pois käytöstä. Jos on annettu,\n" +" otetaan käyttöön vain siinä lisäosassa. Jos vain on\n" +"annettu, kaikki komennot annetussa lisäosassa otetaan käyttööön. \n" +"on vaadittu vain jos viestiä ei lähetetä kanavalla itsellään.\n" +" " + +#: plugin.py:809 +msgid "%s was not disabled." +msgstr "%s ei ollut pois käytöstä." + +#: plugin.py:818 +msgid "" +"[]\n" +"\n" +" Returns the nicks in . is only necessary if the\n" +" message isn't sent in the channel itself.\n" +" " +msgstr "" +"[]\n" +"\n" +"Palauttaa nimimerkit, jotka ovat . on vaadittu vain jos\n" +"ei lähetetä kanavalla itsellään.\n" +" " + +#: plugin.py:829 +msgid "You don't have access to that information." +msgstr "Sinulla ei ole pääsyoikeutta tuohon tietoon." + +#: plugin.py:837 +msgid "" +"Internal message for notifying all the #channel,ops in a channel of\n" +" a given situation." +msgstr "" +"Sisäinen viesti huomattamaan kaikkia #kanava,oppeja kanavalla\n" +" annetusta tilanteesta." + +#: plugin.py:840 +msgid "Alert to all %s ops: %s" +msgstr "Hälytys kaikki %s operaattoreille: %s" + +#: plugin.py:842 +msgid " (from %s)" +msgstr "(%s:tä)" + +#: plugin.py:850 +msgid "" +"[] \n" +"\n" +" Sends to all the users in who have the ,op\n" +" capability.\n" +" " +msgstr "" +"[] \n" +"\n" +"Lähettää kaikille käyttäjille joilla on ,op\n" +"valtuus.\n" " " diff --git a/plugins/ChannelLogger/locale/fi.po b/plugins/ChannelLogger/locale/fi.po new file mode 100644 index 000000000..43e2d7734 --- /dev/null +++ b/plugins/ChannelLogger/locale/fi.po @@ -0,0 +1,126 @@ +msgid "" +msgstr "" +"Project-Id-Version: Supybot Channellogger\n" +"POT-Creation-Date: 2010-10-17 10:02+CEST\n" +"PO-Revision-Date: \n" +"Last-Translator: Mika Suomalainen \n" +"Language-Team: \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Finnish\n" +"X-Poedit-Country: FINLAND\n" + +#: config.py:46 +msgid "Determines whether logging is enabled." +msgstr "Määrittää onko lokin pitäminen käytössä." + +#: config.py:48 +msgid "" +"Determines whether channel logfiles will be\n" +" flushed anytime they're written to, rather than being buffered by the\n" +" operating system." +msgstr "" +"Määrittää pitäisikö kanava lokitiedostot\n" +" tallentaa silloin kun ne kirjoitetaan, mielummin kuin käyttöjärjestelmän\n" +"puskuroimana." + +#: config.py:52 +msgid "" +"Determines whether formatting characters (such\n" +" as bolding, color, etc.) are removed when writing the logs to disk." +msgstr "" +"Määrittää pitääkö muotoilu merkit (kuten\n" +"korostukset, väri, jne.) poistaa kun lokeja kirjoitetaan levylle." + +#: config.py:55 +msgid "" +"Determines whether the logs for this channel are\n" +" timestamped with the timestamp in supybot.log.timestampFormat." +msgstr "" +"Määrittää laitetaanko tämän kanavan lokitiedostoihin\n" +"aikaleimat aikaleimalla, joka on määritetty asetuksella supybot.log.timestampFormat." + +#: config.py:58 +msgid "" +"Determines what string a message should be\n" +" prefixed with in order not to be logged. If you don't want any such\n" +" prefix, just set it to the empty string." +msgstr "" +"Määrittää millä merkkiketjulla aloitettuja viestejä\n" +"viestejä ei tallenneta lokiin. Jos et halua\n" +"merkkiketjua, aseta se tyhjäksi viestiketjuksi." + +#: config.py:62 +msgid "" +"Determines whether the bot will automatically\n" +" rotate the logs for this channel. The bot will rotate logs when the\n" +" timestamp for the log changes. The timestamp is set according to\n" +" the 'filenameTimestamp' configuration variable." +msgstr "" +"Määrittää kääntääkö botti automaattisesti\n" +"lokit tällä kanavalla. Botti kääntää lokit kun\n" +"kun aikaleima lokeille vaihtuu. Aikaleima asetetaan\n" +"'filenameTimestamp' asetuksen mukaan." + +#: config.py:67 +msgid "" +"Determines how to represent the timestamp\n" +" used for the filename in rotated logs. When this timestamp changes, the\n" +" old logfiles will be closed and a new one started. The format characters\n" +" for the timestamp are in the time.strftime docs at python.org. In order\n" +" for your logs to be rotated, you'll also have to enable\n" +" supybot.plugins.ChannelLogger.rotateLogs." +msgstr "" +"Määrittää kuinka aikaleima, jota käytetään\n" +"tiedostonimenä käännetyille lokeille. Kun tämä aikaleima muuttuu\n" +"vanhat lokitiedostot suljetaan ja uudet aloitetaan. Muotomerkit\n" +"aikaleimoille ovat time.strftime documenteissa python.org :issa. Saadaksesi\n" +"lokisi käännetyksi, sinun täytyy myös ottaa käyttöön\n" +"supybot.plugins.ChannelLogger.rotateLogs." + +#: config.py:75 +msgid "" +"Determines whether the bot will partition its\n" +" channel logs into separate directories based on different criteria." +msgstr "" +"Määrittää osioiko botti kanavalokinsa\n" +"eri hakemistoihin perustuen eri kriteereihin." + +#: config.py:78 +msgid "" +"Determines whether the bot will use a network\n" +" directory if using directories." +msgstr "" +"Määrittää käyttääkö botti verkkohakemistoa\n" +"jos käytetään hakemistoja." + +#: config.py:81 +msgid "" +"Determines whether the bot will use a channel\n" +" directory if using directories." +msgstr "" +"Määrittää käyttääkö botti kanavahakemistoa\n" +"jos käytetään hakemistoja." + +#: config.py:84 +msgid "" +"Determines whether the bot will use a timestamp\n" +" (determined by supybot.plugins.ChannelLogger.directories.timestamp.format)\n" +" if using directories." +msgstr "" +"Määrittää käyttääkö botti aikaleimaa\n" +" (supybot.plugins.ChannelLogger.directories.timestamp.format määrittämänä\n" +"jos käytetään hakemistoja." + +#: config.py:88 +msgid "" +"Determines what timestamp format will be used in\n" +" the directory stucture for channel logs if\n" +" supybot.plugins.ChannelLogger.directories.timestamp is True." +msgstr "" +"Määrittää mitä aikaleima muotoa käytetöön\n" +"hakemistorakenteessa kanavalokeille jos\n" +" supybot.plugins.ChannelLogger.directories.timestamp on True." + From 32b21cbc18bd69aed1e1a15dc091d1ae2a993fa1 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Tue, 1 Mar 2011 08:47:53 +0100 Subject: [PATCH 75/85] Add a clue in solving issue with wrapping wrapped commands --- src/commands.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/commands.py b/src/commands.py index be0dc2d4b..2a2b65edd 100644 --- a/src/commands.py +++ b/src/commands.py @@ -929,6 +929,8 @@ def wrap(f, specList=[], name=None, **kw): code = f.func_code funcArgs = inspect.getargs(code)[0][len(self.commandArgs):] self.log.error('Extra args: %s', funcArgs) + self.log.debug('Make sure you did not wrap a wrapped ' + 'function ;)') raise return utils.python.changeFunctionName(newf, name, f.__doc__) From 2bcfd9e0e59439d4f9dc1630f89fd3b39d51c031 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Tue, 1 Mar 2011 14:35:40 +0100 Subject: [PATCH 76/85] Conditional & MessageParser: Add description. --- plugins/Conditional/__init__.py | 2 +- plugins/MessageParser/__init__.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/Conditional/__init__.py b/plugins/Conditional/__init__.py index 8a48b27be..73e1b14fe 100644 --- a/plugins/Conditional/__init__.py +++ b/plugins/Conditional/__init__.py @@ -38,7 +38,7 @@ 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__ = "" +__version__ = "0.1" # XXX Replace this with an appropriate author or supybot.Author instance. __author__ = supybot.authors.unknown diff --git a/plugins/MessageParser/__init__.py b/plugins/MessageParser/__init__.py index c2d0efb5a..8e321977c 100644 --- a/plugins/MessageParser/__init__.py +++ b/plugins/MessageParser/__init__.py @@ -29,8 +29,8 @@ ### """ -Add a description of the plugin (to be presented to the user inside the wizard) -here. This should describe *what* the plugin does. +MessageParser can be configured to run commands when a message matches a +given trigger. """ import supybot @@ -38,11 +38,12 @@ 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__ = "" +__version__ = "0.1" # 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. __contributors__ = {} From 7e1cd95f46475d0dd6747caf8ccc5c24493b5a96 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Tue, 1 Mar 2011 14:49:47 +0100 Subject: [PATCH 77/85] MessageParser: fix missing configure() in config.py --- plugins/MessageParser/config.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/MessageParser/config.py b/plugins/MessageParser/config.py index a3f020d83..0e496aee5 100644 --- a/plugins/MessageParser/config.py +++ b/plugins/MessageParser/config.py @@ -41,6 +41,14 @@ except: _ = lambda x:x internationalizeDocstring = lambda x:x +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('MessageParser', True) + MessageParser = conf.registerPlugin('MessageParser') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(MessageParser, 'someConfigVariableName', From 54e6e45bd7d021a94620a81d73e4267f533d07ca Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Tue, 1 Mar 2011 21:54:56 +0100 Subject: [PATCH 78/85] Misc: fix typo French translation (thanks to trashy) --- plugins/Misc/locale/fr.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Misc/locale/fr.po b/plugins/Misc/locale/fr.po index 521275059..855f91ff4 100644 --- a/plugins/Misc/locale/fr.po +++ b/plugins/Misc/locale/fr.po @@ -199,7 +199,7 @@ msgstr "" #: plugin.py:373 msgid "I couldn't find a message matching that criteria in my history of %s messages." -msgstr "Je ne peux trovuer de message correspondant à ce critère dans mon historique de %s messages." +msgstr "Je ne peux trouver de message correspondant à ce critère dans mon historique de %s messages." #: plugin.py:388 msgid "" From 58bc8d59b94b7833d8bba79af13a9772e6b2e6ce Mon Sep 17 00:00:00 2001 From: Mika Suomalainen Date: Wed, 2 Mar 2011 16:03:10 +0100 Subject: [PATCH 79/85] ChannelStats: add Finnish translation --- plugins/ChannelStats/locale/fi.po | 202 ++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 plugins/ChannelStats/locale/fi.po diff --git a/plugins/ChannelStats/locale/fi.po b/plugins/ChannelStats/locale/fi.po new file mode 100644 index 000000000..4e9020530 --- /dev/null +++ b/plugins/ChannelStats/locale/fi.po @@ -0,0 +1,202 @@ +msgid "" +msgstr "" +"Project-Id-Version: Supybot ChannelStats\n" +"POT-Creation-Date: 2010-10-16 09:41+CEST\n" +"PO-Revision-Date: \n" +"Last-Translator: Mika Suomalainen \n" +"Language-Team: \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Finnish\n" +"X-Poedit-Country: FINLAND\n" + +#: config.py:60 +msgid "" +"Determines whether the bot will keep channel\n" +" statistics on itself, possibly skewing the channel stats (especially in\n" +" cases where the bot is relaying between channels on a network)." +msgstr "" +"Määrittää pitääkö botti kanava\n" +"tilastot itsellään, mahdollisesti vinouttaen kanava tilastot (etenkin\n" +"tilanteissa, joissa botti on välittämässä kanavien välillä verkossa)." + +#: config.py:64 +msgid "" +"Determines what\n" +" words (i.e., pieces of text with no spaces in them) are considered\n" +" 'smileys' for the purposes of stats-keeping." +msgstr "" +"Määrittää mitkä\n" +"sanat (esim. tekstin paloja ilman välilyöntiä niiden välissä ) lasketaan\n" +" 'hymiöiksi' tilastojen pitämisen takia." + +#: config.py:68 +msgid "" +"Determines what words\n" +" (i.e., pieces of text with no spaces in them ) are considered 'frowns' for\n" +" the purposes of stats-keeping." +msgstr "" +"Määrittää mitkä sanat\n" +" (esim. paloja tekstiä ilman välilyöntiä välissä ) lasketaan 'surioiksi'\n" +"tilastojen pitämistä varten." + +#: plugin.py:246 +msgid "" +"[] []\n" +"\n" +" Returns the statistics for on . is only\n" +" necessary if the message isn't sent on the channel itself. If \n" +" isn't given, it defaults to the user sending the command.\n" +" " +msgstr "" +"[] []\n" +"\n" +" . on vaadittu\n" +"jos viestiä ei lähetetä kanavalla itsessään. Jos \n" +" ei ole annettu, se on oletuksena viestin lähettänyt henkilö.\n" +" " + +#: plugin.py:259 +msgid "I couldn't find you in my user database." +msgstr "Minä en voi löytää sinua käyttäjä tietokannastani." + +#: plugin.py:272 +#, fuzzy +msgid "%s has sent %n; a total of %n, %n, %n, and %n; %s of those messages %s. %s has joined %n, parted %n, quit %n, kicked someone %n, been kicked %n, changed the topic %n, and changed the mode %n." +msgstr "%s on lähettänyt %n; yhteensä %n, %n, %n, ja %n; %s noista viesteistä %s. %s on liittynyt %n, poistunut kanavalta %n, sulkenut %n, potkinut jonkun %n, ollut potkittu %n, vaihtanut aihetta %n, vaihtanut tilaa %n." + +#: plugin.py:279 +msgid "character" +msgstr "merkki" + +#: plugin.py:280 +#: plugin.py:363 +msgid "word" +msgstr "sana" + +#: plugin.py:281 +#: plugin.py:364 +msgid "smiley" +msgstr "hymiö" + +#: plugin.py:282 +#: plugin.py:365 +msgid "frown" +msgstr "surio" + +#: plugin.py:284 +#: plugin.py:366 +msgid "was an ACTION" +msgstr "oli TOIMINTO" + +#: plugin.py:285 +#: plugin.py:367 +msgid "were ACTIONs" +msgstr "olivat TOIMINTOja" + +#: plugin.py:287 +#: plugin.py:288 +#: plugin.py:289 +#: plugin.py:290 +#: plugin.py:291 +#: plugin.py:292 +#: plugin.py:293 +msgid "time" +msgstr "aika" + +#: plugin.py:296 +msgid "I have no stats for that %s in %s." +msgstr "Minulla ei ole tilastoja %s:stä %s:ään." + +#: plugin.py:304 +msgid "" +"[] \n" +"\n" +" Returns the ranking of users according to the given stat expression.\n" +" Valid variables in the stat expression include 'msgs', 'chars',\n" +" 'words', 'smileys', 'frowns', 'actions', 'joins', 'parts', 'quits',\n" +" 'kicks', 'kicked', 'topics', and 'modes'. Any simple mathematical\n" +" expression involving those variables is permitted.\n" +" " +msgstr "" +"[] \n" +"\n" +"Palauttaa käyttäjien arvoasteikon perustuen annettuun tilasto lausekkeeseen.\n" +"kelvolliset tilasto lausekkeet sisältävät 'msgs', 'chars',\n" +"'words', 'smileys', 'frowns', 'actions', 'joins', 'parts', 'quits',\n" +"'kicks', 'kicked', 'topics', and 'modes'. Mikä tahansa yksinkertainen matemaattinen\n" +" lauseke sisältäen nämä arvot on sallittu.\n" +" " + +#: plugin.py:315 +msgid "There's really no reason why you should have underscores or brackets in your mathematical expression. Please remove them." +msgstr "Ei ole mitään syytä miksi haluaisit alaviivoja tai sulkuja matemaattisessa lausekkeessa. Ole hyvä ja poista ne." + +#: plugin.py:319 +#, fuzzy +msgid "You can't use lambda in this command." +msgstr "Et voi käyttää lambdaa tässä komennossa." + +#: plugin.py:333 +msgid "stat variable" +msgstr "aloita muuttuja" + +#: plugin.py:349 +msgid "" +"[]\n" +"\n" +" Returns the statistics for . is only necessary if\n" +" the message isn't sent on the channel itself.\n" +" " +msgstr "" +"[]\n" +"\n" +"Palauttaa tilastot . on vaadittu vain jos viestiä ei lähetetä\n" +"kanavalla itsessään.\n" +" " + +#: plugin.py:357 +#, fuzzy +msgid "On %s there %h been %i messages, containing %i characters, %n, %n, and %n; %i of those messages %s. There have been %n, %n, %n, %n, %n, and %n. There %b currently %n and the channel has peaked at %n." +msgstr "%s:ssä %h on ollut %i viestiä, sisältäen %i merkkiä, %n, %n, ja %n; %i noista viesteistä %s. On ollut %n, %n, %n, %n, %n, ja %n. %b tällä hetkellä %n kanavalla on huipulla %n." + +#: plugin.py:368 +msgid "join" +msgstr "liity" + +#: plugin.py:369 +msgid "part" +msgstr "poistu" + +#: plugin.py:370 +msgid "quit" +msgstr "poistu" + +#: plugin.py:371 +msgid "kick" +msgstr "potki" + +#: plugin.py:372 +msgid "mode" +msgstr "tila" + +#: plugin.py:372 +#: plugin.py:373 +msgid "change" +msgstr "vaihdos" + +#: plugin.py:373 +msgid "topic" +msgstr "aihe" + +#: plugin.py:375 +#: plugin.py:376 +msgid "user" +msgstr "käyttäjä" + +#: plugin.py:379 +msgid "I've never been on %s." +msgstr "En ole ikinä ollut %s:llä." + From 74c634f67ad62e1fda00039ffb26c6bba43d4eff Mon Sep 17 00:00:00 2001 From: Mika Suomalainen Date: Thu, 3 Mar 2011 19:21:37 +0100 Subject: [PATCH 80/85] Config: Add Finnish translation --- plugins/Config/locale/fi.po | 176 ++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 plugins/Config/locale/fi.po diff --git a/plugins/Config/locale/fi.po b/plugins/Config/locale/fi.po new file mode 100644 index 000000000..404556c11 --- /dev/null +++ b/plugins/Config/locale/fi.po @@ -0,0 +1,176 @@ +msgid "" +msgstr "" +"Project-Id-Version: Finnish translation of Config plugin in Supybot\n" +"POT-Creation-Date: 2010-12-12 15:02+CET\n" +"PO-Revision-Date: \n" +"Last-Translator: Mika Suomalainen \n" +"Language-Team: \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Finnish\n" +"X-Poedit-Country: FINLAND\n" + +#: plugin.py:103 +msgid "configuration variable" +msgstr "asetusarvo" + +#: plugin.py:109 +msgid "settable configuration variable" +msgstr "asetettava asetusarvo" + +#: plugin.py:136 +msgid "" +"\n" +"\n" +" Returns the configuration variables available under the given\n" +" configuration . If a variable has values under it, it is\n" +" preceded by an '@' sign. If a variable is a 'ChannelValue', that is,\n" +" it can be separately configured for each channel using the 'channel'\n" +" command in this plugin, it is preceded by an '#' sign.\n" +" " +msgstr "" +"\n" +"\n" +"Palauttaa asetusarvot, jotka ovat annetun\n" +"asetus alla. Jos arvolla on toisia arvoja allaan, se on\n" +" merkitty '@' merkillä. Jos arvo on 'ChannelValue', se voi olla,\n" +"erikseen määritelty jokaiselle kanavalle käyttämällä 'channel'\n" +"komentoa tässä lisäosassa, se on merkitty '#' merkillä.\n" +" " + +#: plugin.py:148 +msgid "There don't seem to be any values in %s." +msgstr "%s:ssä ei näytä olevan yhtään asetusarvoja." + +#: plugin.py:154 +msgid "" +"\n" +"\n" +" Searches for in the current configuration variables.\n" +" " +msgstr "" +"\n" +"\n" +"Etsii nykyisistä asetus arvoista.\n" +" " + +#: plugin.py:167 +msgid "There were no matching configuration variables." +msgstr "Täsmääviä asetusarvoja ei löytynyt." + +#: plugin.py:174 +msgid "Global: %s; %s: %s" +msgstr "Globaali: %s; %s: %s" + +#: plugin.py:185 +msgid "That registry variable has no value. Use the list command in this plugin to see what variables are available in this group." +msgstr "Sillä rekisteriarvolla ei ole arvoa. Käytä list komentoa tässä lisäosassa nähdäksesi mitä arvoja on saatavilla tässä ryhmässä." + +#: plugin.py:200 +msgid "" +"[] []\n" +"\n" +" If is given, sets the channel configuration variable for \n" +" to for . Otherwise, returns the current channel\n" +" configuration value of . is only necessary if the\n" +" message isn't sent in the channel itself." +msgstr "" +"[] []\n" +"\n" +"Jos on annettu, asettaa kanavan asetusarvon\n" +" . Muutoin, palauttaa nykyisen \n" +" nykyisen kanava asetusarvon. on vaadittu vain\n" +"jos viestiä ei lähetetä kanavalla itsellään." + +#: plugin.py:207 +msgid "That configuration variable is not a channel-specific configuration variable." +msgstr "Tällä asetusarvolla ei ole kanava kohtaista asetusarvoa." + +#: plugin.py:220 +msgid "" +" []\n" +"\n" +" If is given, sets the value of to . Otherwise,\n" +" returns the current value of . You may omit the leading\n" +" \"supybot.\" in the name if you so choose.\n" +" " +msgstr "" +" []\n" +"\n" +"Jos on annettu, asettaa arvon . Muutoin palauttaa,\n" +" nykyisen arvon. Voit jättää pois seuraavan rivin pois \n" +" \"supybot.\" .\n" +" " + +#: plugin.py:234 +msgid "" +"\n" +"\n" +" Returns the description of the configuration variable .\n" +" " +msgstr "" +"\n" +"\n" +" Palauttaa asetusarvon kuvauksen .\n" +" " + +#: plugin.py:242 +msgid " (Current value: %s)" +msgstr " (Nykyinen arvo: %s)" + +#: plugin.py:245 +msgid "That configuration group exists, but seems to have no help. Try \"config list %s\" to see if it has any children values." +msgstr "Tuo asetusryhmä on olemassa, mutta sillä ei näytä olevan ohjetta. Käytä komentoa \"config list %s\" nähdäksesi onko sillä yhtään alempia arvoja." + +#: plugin.py:249 +msgid "%s has no help." +msgstr "%s:llä ei ole ohjetta." + +#: plugin.py:254 +msgid "" +"\n" +"\n" +" Returns the default value of the configuration variable .\n" +" " +msgstr "" +"\n" +"\n" +"Palauttaa asetusarvon oletusarvon .\n" +" " + +#: plugin.py:264 +msgid "" +"takes no arguments\n" +"\n" +" Reloads the various configuration files (user database, channel\n" +" database, registry, etc.).\n" +" " +msgstr "" +"ei ota parametrejä\n" +"\n" +"Lataa uudelleen joitain asetustiedostoja(käyttäjä tietokanta, kanava\n" +" tietokanta, rekisteri, jne.).\n" +" " + +#: plugin.py:275 +msgid "" +"\n" +"\n" +" Exports the public variables of your configuration to .\n" +" If you want to show someone your configuration file, but you don't\n" +" want that person to be able to see things like passwords, etc., this\n" +" command will export a \"sanitized\" configuration file suitable for\n" +" showing publicly.\n" +" " +msgstr "" +"\n" +"\n" +"Vie julkiset asetusarvot asetustiedostostasi .\n" +" Jos haluat näyttää jollekulle asetustiedostosi, mutta et\n" +"halua tuon henkilön näkevän salasanojasi, jne., tämä\n" +"komento vie \"järjellistetyn\" asetustiedoston, joka sopii\n" +"julkisesti näyttämiseen.\n" +" " + From ad95bc96a8ae35b1debf9739f05ef9d229aa23bf Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Thu, 3 Mar 2011 19:25:15 +0100 Subject: [PATCH 81/85] Config: Fix French translation --- plugins/Config/locale/fr.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Config/locale/fr.po b/plugins/Config/locale/fr.po index 0178e0a67..650a6d844 100644 --- a/plugins/Config/locale/fr.po +++ b/plugins/Config/locale/fr.po @@ -152,5 +152,5 @@ msgid "" msgstr "" "\n" "\n" -"Exporte les variables de configuration publiques dans le fichier Date: Sat, 5 Mar 2011 09:44:40 +0100 Subject: [PATCH 82/85] Add another solution in error message about TypeErrors raised in plugins' __init__() --- src/plugin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/plugin.py b/src/plugin.py index 25cbe67a7..6c4e93597 100644 --- a/src/plugin.py +++ b/src/plugin.py @@ -86,7 +86,10 @@ def loadPluginClass(irc, module, register=None): 'its __init__ method and needs to update its prototype ' \ 'to be \'def __init__(self, irc):\' as well as passing ' \ 'that irc object on to any calls to the plugin\'s ' \ - 'parent\'s __init__.' % module.__name__ + 'parent\'s __init__. Another possible cause: the code in ' \ + 'your __init__ raised a TypeError when calling a function ' + 'or creating an object, which doesn\'t take 2 arguments.' %\ + module.__name__ else: raise except AttributeError, e: From 39fa7e68d39a15e39e194c682f7bf00d7456ec30 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 5 Mar 2011 09:51:05 +0100 Subject: [PATCH 83/85] Fix syntax error --- src/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugin.py b/src/plugin.py index 6c4e93597..94f1e4610 100644 --- a/src/plugin.py +++ b/src/plugin.py @@ -87,7 +87,7 @@ def loadPluginClass(irc, module, register=None): 'to be \'def __init__(self, irc):\' as well as passing ' \ 'that irc object on to any calls to the plugin\'s ' \ 'parent\'s __init__. Another possible cause: the code in ' \ - 'your __init__ raised a TypeError when calling a function ' + 'your __init__ raised a TypeError when calling a function ' \ 'or creating an object, which doesn\'t take 2 arguments.' %\ module.__name__ else: From 8595c177dd52b93ac3948ecc80d6e1dd01d351ff Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Mon, 7 Mar 2011 18:20:10 +0100 Subject: [PATCH 84/85] Google: fix @calc --- plugins/Google/plugin.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/Google/plugin.py b/plugins/Google/plugin.py index 685e907c7..de5814b0e 100644 --- a/plugins/Google/plugin.py +++ b/plugins/Google/plugin.py @@ -339,9 +339,7 @@ class Google(callbacks.PluginRegexp): """ url = self._googleUrl(expr) html = utils.web.getUrl(url) - match = self._calcRe1.search(html) - if match is None: - match = self._calcRe2.search(html) + match = self._calcRe.search(html) if match is not None: s = match.group(1) s = self._calcSupRe.sub(r'^(\1)', s) From c6e54fb1b2eaadb8d04bdf2cf44c4c62cbc9f99d Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Tue, 8 Mar 2011 15:18:38 +0100 Subject: [PATCH 85/85] Owner: fix error message when the ImportError comes from the plugin --- plugins/Owner/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Owner/plugin.py b/plugins/Owner/plugin.py index e825de208..922387f51 100644 --- a/plugins/Owner/plugin.py +++ b/plugins/Owner/plugin.py @@ -435,7 +435,7 @@ class Owner(callbacks.Plugin): 'to force it to load.' % name.capitalize()) return except ImportError, e: - if name in str(e): + if str(e).endswith(' ' + name): irc.error('No plugin named %s exists.' % utils.str.dqrepr(name)) else: irc.error(str(e))