limnoria-2020.03.17/0000755000175000017500000000000013634634547013325 5ustar valval00000000000000limnoria-2020.03.17/.travis.yml0000644000175000017500000000246013634634532015432 0ustar valval00000000000000language: python sudo: true install: - if [ "$WITH_OPT_DEPS" = "true" ] ; then pip install -vr requirements.txt; fi - pip install -v git+https://github.com/ProgVal/irctest.git - echo "y" | pip uninstall limnoria || true # command to run tests, e.g. python setup.py test script: - echo $TRAVIS_PYTHON_VERSION - python setup.py install - supybot-test test -v --plugins-dir=./plugins/ --no-network - if [ "$WITH_OPT_DEPS" = "true" ] -a [[ "$TRAVIS_PYTHON_VERSION" =~ ^3\.[4-9] ]] ; then python -m irctest irctest.controllers.limnoria; fi notifications: email: false matrix: include: - python: "3.4" env: WITH_OPT_DEPS=true dist: trusty - python: "3.5" env: WITH_OPT_DEPS=true dist: trusty - python: "3.6" env: WITH_OPT_DEPS=true dist: trusty - python: "3.7" env: WITH_OPT_DEPS=false dist: xenial - python: "3.7" env: WITH_OPT_DEPS=true dist: xenial - python: "3.8-dev" env: WITH_OPT_DEPS=true dist: xenial - python: "nightly" env: WITH_OPT_DEPS=true dist: xenial - python: "pypy3" env: WITH_OPT_DEPS=true dist: trusty - python: "pypy3" env: WITH_OPT_DEPS=true dist: xenial allow_failures: - python: "nightly" env: WITH_OPT_DEPS=true dist: xenial limnoria-2020.03.17/LICENSE.md0000644000175000017500000000316613634634532014731 0ustar valval00000000000000Copyright (c) 2002-2009 Jeremiah Fincher and others 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 permission. 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. Portions of the included source code are copyright by its original author(s) and remain subject to its associated license. limnoria-2020.03.17/Makefile0000644000175000017500000000172713634634532014766 0ustar valval00000000000000PYTHON=`which python3` DESTDIR=/ PROJECT=limnoria all: @echo "make source - Create source package" @echo "make install - Install on local system" @echo "make buildrpm - Generate a rpm package" @echo "make builddeb_py2 - Generate a deb package for Python 2" @echo "make builddeb_py3 - Generate a deb package for Python 3" @echo "make clean - Get rid of scratch and byte files" test: PATH=./scripts/:${PATH} PYTHONPATH=. $(PYTHON) ./scripts/supybot-test test --plugins-dir=plugins/ source: $(PYTHON) setup.py sdist $(COMPILE) install: $(PYTHON) setup.py install --root $(DESTDIR) $(COMPILE) buildrpm: $(PYTHON) setup.py bdist_rpm builddeb_py2: cp debian/control.py2 debian/control debuild -us -uc rm debian/control builddeb_py3: cp debian/control.py3 debian/control debuild -us -uc rm debian/control clean: $(PYTHON) setup.py clean $(MAKE) -f $(CURDIR)/debian/rules clean rm -rf build/ MANIFEST find . -name '*.pyc' -delete rm debian/control .PHONY: test limnoria-2020.03.17/PKG-INFO0000644000175000017500000000315713634634547014430 0ustar valval00000000000000Metadata-Version: 1.1 Name: limnoria Version: 2020.03.17 Summary: A modified version of Supybot (an IRC bot and framework) Home-page: https://github.com/ProgVal/Limnoria Author: Valentin Lorentz Author-email: progval+limnoria@progval.net License: UNKNOWN Download-URL: https://pypi.python.org/pypi/limnoria Description: A robust, full-featured Python IRC bot with a clean and flexible plugin API. Equipped with a complete ACL system for specifying user permissions with as much as per-command granularity. Batteries are included in the form of numerous plugins already written. Platform: linux Platform: linux2 Platform: win32 Platform: cygwin Platform: darwin Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Environment :: No Input/Output (Daemon) Classifier: Intended Audience :: End Users/Desktop Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Natural Language :: English Classifier: Natural Language :: Finnish Classifier: Natural Language :: French Classifier: Natural Language :: Hungarian Classifier: Natural Language :: Italian Classifier: Operating System :: OS Independent Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Topic :: Communications :: Chat :: Internet Relay Chat Classifier: Topic :: Software Development :: Libraries :: Python Modules Provides: supybot limnoria-2020.03.17/README.md0000644000175000017500000000434413634634532014603 0ustar valval00000000000000Supybot is a robust (it doesn't crash), user friendly (it's easy to configure) and programmer friendly (plugins are *extremely* easy to write) Python IRC bot. It aims to be an adequate replacement for most existing IRC bots. It includes a very flexible and powerful ACL system for controlling access to commands, as well as more than 50 builtin plugins providing around 400 actual commands. Limnoria is the project which continues development of Supybot since 2010. # Build status Master branch: [![Build Status (master branch)](https://travis-ci.org/ProgVal/Limnoria.png?branch=master)](https://travis-ci.org/ProgVal/Limnoria) Testing branch: [![Build Status (testing branch)](https://travis-ci.org/ProgVal/Limnoria.png?branch=testing)](https://travis-ci.org/ProgVal/Limnoria) Limnoria supports CPython 3.4, 3.5, 3.6, 3.7, nightly; and Pypy 3. # Support ## Documentation If this is your first install, there is an [install guide](https://docs.limnoria.net/en/latest/use/install.html). You will probably be pointed to it if you ask on IRC how to install Limnoria. TL;DR version: ``` sudo apt-get install python3 python3-pip python3-wheel pip3 install --user limnoria # You might need to add $HOME/.local/bin to your PATH supybot-wizard ``` There is extensive documentation at [docs.limnoria.net] and at [Gribble wiki]. We took the time to write it; you should take the time to read it. [docs.limnoria.net]:https://docs.limnoria.net/ [Gribble wiki]:https://sourceforge.net/p/gribble/wiki/Main_Page/ ## IRC channels ### In English If you have any trouble, feel free to swing by [#supybot and #limnoria](ircs://chat.freenode.net:6697/#supybot,#limnoria) on [freenode](https://freenode.net/) or [#supybot](ircs://irc.oftc.net:6697/#supybot) at [OFTC](http://oftc.net/) (we have a Limnoria there relaying, so either network works) and ask questions. We'll be happy to help wherever we can. And by all means, if you find anything hard to understand or think you know of a better way to do something, *please* post it on the [issue tracker] so we can improve the bot! [issue tracker]:https://github.com/ProgVal/Limnoria/issues ### In Other languages Only in French at the moment, located at [#supybot-fr on freenode](ircs://chat.freenode.net:6697/#supybot-fr). limnoria-2020.03.17/locales/0000755000175000017500000000000013634634547014747 5ustar valval00000000000000limnoria-2020.03.17/locales/__init__.py0000644000175000017500000000000013634634532017040 0ustar valval00000000000000limnoria-2020.03.17/locales/de.po0000644000175000017500000012326113634634532015676 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2011-06-25 14:17+CEST\n" "PO-Revision-Date: 2011-10-28 18:53+0100\n" "Last-Translator: Florian Besser \n" "Language-Team: German \n" "Language: de\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: German\n" "X-Poedit-Country: Germany\n" #: ../src/callbacks.py:184 msgid "Error: " msgstr "Fehler: " #: ../src/callbacks.py:198 msgid "Error: I tried to send you an empty message." msgstr "Fehler: Ich habe versucht eine leere Nachricht zu senden." #: ../src/callbacks.py:288 msgid "Missing \"%s\". You may want to quote your arguments with double quotes in order to prevent extra brackets from being evaluated as nested commands." msgstr "Fehlend \"%s\". Vielleicht solltest du deine Argumente in doppelte Anführungszeichen setzen um zu verhindern das eine Klammern als Verschachtelung gewertet werden." #: ../src/callbacks.py:318 msgid "\"|\" with nothing preceding. I obviously can't do a pipe with nothing before the |." msgstr "\"|\" mit nichts vorhergehendem. Eine pipe ohne Eingangsmaterial vor dem | funktioniert nicht." #: ../src/callbacks.py:326 msgid "Spurious \"%s\". You may want to quote your arguments with double quotes in order to prevent extra brackets from being evaluated as nested commands." msgstr "Falsche \"%s\". Du solltest Zitate in doppelte Anführungszeichen packen um zu verhindern, dass zusätzliche Anführungszeichen als verschachtelte Befehle ausgewertet werden." #: ../src/callbacks.py:335 msgid "\"|\" with nothing following. I obviously can't do a pipe with nothing after the |." msgstr "\"|\" mit nichts nachfolgendem. Eine pipe ohne Ziel nach dem | funktioniert nicht." #: ../src/callbacks.py:519 msgid "%s is not a valid %s." msgstr "%s nicht zulässig als %s" #: ../src/callbacks.py:521 msgid "That's not a valid %s." msgstr "%s nicht zulässig" #: ../src/callbacks.py:599 msgid "You've attempted more nesting than is currently allowed on this bot." msgstr "Du hast versucht mehr zu Verschachteln als bei diesem Bot momentan erlaubt ist." #: ../src/callbacks.py:778 msgid "The command %q is available in the %L plugins. Please specify the plugin whose command you wish to call by using its name as a command before %q." msgstr "Der Befehl %q ist verfügbar in den %L Plugins. Bitte gebe an welches Plugin diesen Befehl ausführen soll, indem du es vor dem Befehl %q angibst." #: ../src/callbacks.py:863 #: ../src/callbacks.py:876 msgid "(XX more messages)" msgstr "(XX mehr Nachrichten)" #: ../src/callbacks.py:908 msgid "more message" msgstr "mehr Nachrichten" #: ../src/callbacks.py:910 msgid "more messages" msgstr "mehr Nachrichten" #: ../src/callbacks.py:1010 #, fuzzy msgid "" "Determines what commands are currently disabled. Such\n" " commands will not appear in command lists, etc. They will appear not even\n" " to exist." msgstr "" "Legt fest welche Befehle momentan abgeschaltet sind.\n" " Diese Befehle werden nicht in Befehlslisten, etc... erscheinen.\n" " Es wird den anschein haben, das diese nicht einmal existieren." #: ../src/callbacks.py:1201 msgid "Invalid arguments for %s." msgstr "Unzulässige Argumente für %s." #: ../src/callbacks.py:1227 msgid "The %q command has no help." msgstr "%q hat keine Hilfe." #: ../src/commands.py:171 msgid "integer" msgstr "Ganzzahl" #: ../src/commands.py:182 msgid "non-integer value" msgstr "Kein Ganzzahlwert" #: ../src/commands.py:193 msgid "floating point number" msgstr "Fließkommazahl" #: ../src/commands.py:202 msgid "positive integer" msgstr "positive Ganzzahl" #: ../src/commands.py:206 msgid "non-negative integer" msgstr "nicht negative Ganzzahl" #: ../src/commands.py:209 msgid "index" msgstr "Index" #: ../src/commands.py:234 msgid "number of seconds" msgstr "eingen Sekunden" #: ../src/commands.py:241 msgid "boolean" msgstr "Boolean" #: ../src/commands.py:255 msgid "do that" msgstr "tu dass" #: ../src/commands.py:259 msgid "I'm not even in %s." msgstr "Ich bin nicht mal in %s." #: ../src/commands.py:261 msgid "I need to be opped to %s." msgstr "Ich benötige Op zu %s." #: ../src/commands.py:278 msgid "nick or hostmask" msgstr "Nick oder Hostmaske" #: ../src/commands.py:330 #: ../src/commands.py:333 msgid "regular expression" msgstr "Regulärer Ausdruck" #: ../src/commands.py:344 msgid "That nick is too long for this server." msgstr "Dieser Nick ist zu lang für den Server." #: ../src/commands.py:393 #: ../src/commands.py:412 msgid "I'm not in %s." msgstr "Ich bin nicht in %s." #: ../src/commands.py:397 #, fuzzy msgid "This command may only be given in a channel that I am in." msgstr "Dieser Befehl kann mir nur in einem Kanal gegeben werden in dem ich bin." #: ../src/commands.py:410 msgid "You must be in %s." msgstr "Du musst in %s sein." #: ../src/commands.py:414 msgid "channel" msgstr "Kanal" #: ../src/commands.py:421 msgid "%s is not in %s." msgstr "%s ist nicht in %s." #: ../src/commands.py:455 #, fuzzy msgid "You must not give the empty string as an argument." msgstr "Du darfst keinen leeren String als Argument angeben." #: ../src/commands.py:472 msgid "This message must be sent in a channel." msgstr "Diese Nachricht muss in einem Kanal gesendet werden." #: ../src/commands.py:518 msgid "http url" msgstr "HTTP URL" #: ../src/commands.py:525 msgid "command name" msgstr "Befehlsname" #: ../src/commands.py:533 msgid "ip" msgstr "IP" #: ../src/commands.py:539 msgid "letter" msgstr "Buchstabe" #: ../src/commands.py:571 msgid "plugin" msgstr "Plugin" #: ../src/commands.py:579 msgid "irc color" msgstr "IRC Farbe" #: ../src/conf.py:104 msgid "" "Determines whether this plugin is loaded\n" " bydefault." msgstr "" "Legt fest ob das Plugin standardgemäß\n" " geladen wird" #: ../src/conf.py:108 msgid "" "Determines whether this plugin is\n" " publicly visible." msgstr "" "Legt fest ob das Plugin öffentlich\n" " zu sehen ist" #: ../src/conf.py:194 msgid "Determines the bot's default nick." msgstr "Legt den Standardnick des Bots fest." #: ../src/conf.py:197 #, fuzzy msgid "" "Determines what alternative\n" " nicks will be used if the primary nick (supybot.nick) isn't available. A\n" " %s in this nick is replaced by the value of supybot.nick when used. If no\n" " alternates are given, or if all are used, the supybot.nick will be perturbed\n" " appropriately until an unused nick is found." msgstr "" "Legt fest welche alternativ Nicknamen der Bot benutzt, falls der Primärnick (supybot.nick) nicht verfügbar ist.\n" " Ein %s in diesem Nicknamen wird durch den Wert von supybot.nick ersetzt. Falls kein alternativ Nickname angegeben wird oder alle bereits vergeben sind, wird supybot.nick verändert bis ein unbenuzter Nickname gefunden wurde." #: ../src/conf.py:204 msgid "" "Determines the bot's ident string, if the server\n" " doesn't provide one by default." msgstr "Legt die Ident Zeichenkette des Bots fest, falls der Server keine vergibt." #: ../src/conf.py:215 msgid "" "Determines the user the bot sends to the server.\n" " A standard user using the current version of the bot will be generated if\n" " this is left empty." msgstr "Legt den User fest den der Bot an den Server sendet. Er wird die momentane Version des Bots als User benutzen, falls diese Variable leer bleibt." #: ../src/conf.py:223 msgid "Determines what networks the bot will connect to." msgstr "Legt fest zu welchen Netzwerken der Bot sich verbindet." #: ../src/conf.py:264 msgid "" "Determines what password will be used on %s. Yes, we know that\n" " technically passwords are server-specific and not network-specific,\n" " but this is the best we can do right now." msgstr "Legt fest welches Passwort für %s genutzt wird. Ja wir wissen das Passwörter nicht Netzwerk spezifisch sondern Server spezifisch sind , aber das ist das beste was wir momentan anbieten können." #: ../src/conf.py:268 msgid "" "Determines what servers the bot will connect to for %s. Each will\n" " be tried in order, wrapping back to the first when the cycle is\n" " completed." msgstr "Legt fest zu welchem Server der Bot für %s verbindet. Jeder Server wird in der Reihenfolge probiert, falls der letzte Server erreicht ist, wird wieder beim Ersten angefangen." #: ../src/conf.py:272 msgid "Determines what channels the bot will join only on %s." msgstr "Legt fest welche Kanäle der Bot nur bei %s betritt." #: ../src/conf.py:275 #, fuzzy msgid "" "Determines whether the bot will attempt to connect with SSL\n" " sockets to %s." msgstr "Legt fest ob der Bot versuchen soll per SSL Sockets zu %s zu verbinden." #: ../src/conf.py:278 msgid "" "Determines what key (if any) will be used to join the\n" " channel." msgstr "Legt den Schlüssel fest, den der Bot benutzt um einen Kanal zu betreten (falls vorhanden)." #: ../src/conf.py:298 msgid "" "Determines how timestamps\n" " printed for human reading should be formatted. Refer to the Python\n" " documentation for the time module to see valid formatting characters for\n" " time formats." msgstr "Legt fest wie Zeitstempel für das menschliche Lesen formattiert werden sollen. Schlag in der Python Dokumentation das time Modul nach um gültige Zeichen zu erfahren." #: ../src/conf.py:312 msgid "" "Determines whether elapsed times will be given\n" " as \"1 day, 2 hours, 3 minutes, and 15 seconds\" or as \"1d 2h 3m 15s\"." msgstr "Legt fest wie die vergangene Zeit angeben wird, als \"1 Tag, 2 Stunden, 3 Minuten und 15 Sekunden\" oder als \"1d 2h 3m 15s\"" #: ../src/conf.py:322 msgid "" "Determines the absolute maximum length of\n" " the bot's reply -- no reply will be passed through the bot with a length\n" " greater than this." msgstr "Legt die absolut maximale Länge für Bot-Antworten fest. Keine Antwort länger als dieser Wert wird vom Bot zugelassen." #: ../src/conf.py:327 msgid "" "Determines whether the bot will break up long\n" " messages into chunks and allow users to use the 'more' command to get the\n" " remaining chunks." msgstr "Legt fest ob der Bot lange Nachrichten in mehrere Stücke unterteilt und der Nutzer mit dem more-Befehl die restlichen Stücke erhalten kann." #: ../src/conf.py:332 msgid "" "Determines what the maximum number of\n" " chunks (for use with the 'more' command) will be." msgstr "Legt die maximal Anzahl an Teilen fest (für die Nutzung mit dem 'more' Befehl)" #: ../src/conf.py:336 msgid "" "Determines how long individual chunks\n" " will be. If set to 0, uses our super-tweaked,\n" " get-the-most-out-of-an-individual-message default." msgstr "Legt fest wie lang einzelne Stücke sein werden. Beim Wert 0 wird unser super-getunter Nutze-alles-was-aus-einzelnen-Nachrichten-machbar-ist-Standard verwendet." #: ../src/conf.py:341 msgid "" "Determines how many mores will be sent\n" " instantly (i.e., without the use of the more command, immediately when\n" " they are formed). Defaults to 1, which means that a more command will be\n" " required for all but the first chunk." msgstr "" #: ../src/conf.py:347 msgid "" "Determines whether the bot will send\n" " multi-message replies in a single message or in multiple messages. For\n" " safety purposes (so the bot is less likely to flood) it will normally send\n" " everything in a single message, using mores if necessary." msgstr "" #: ../src/conf.py:353 msgid "" "Determines whether the bot will reply with an\n" " error message when it is addressed but not given a valid command. If this\n" " value is False, the bot will remain silent, as long as no other plugins\n" " override the normal behavior." msgstr "" #: ../src/conf.py:360 msgid "" "Determines whether error messages that result\n" " from bugs in the bot will show a detailed error message (the uncaught\n" " exception) or a generic error message." msgstr "" #: ../src/conf.py:364 msgid "" "Determines whether the bot will send error\n" " messages to users in private. You might want to do this in order to keep\n" " channel traffic to minimum. This can be used in combination with\n" " supybot.reply.error.withNotice." msgstr "" #: ../src/conf.py:369 msgid "" "Determines whether the bot will send error\n" " messages to users via NOTICE instead of PRIVMSG. You might want to do this\n" " so users can ignore NOTICEs from the bot and not have to see error\n" " messages; or you might want to use it in combination with\n" " supybot.reply.errorInPrivate so private errors don't open a query window\n" " in most IRC clients." msgstr "" #: ../src/conf.py:376 msgid "" "Determines whether the bot will send an error\n" " message to users who attempt to call a command for which they do not have\n" " the necessary capability. You may wish to make this True if you don't want\n" " users to understand the underlying security system preventing them from\n" " running certain commands." msgstr "" #: ../src/conf.py:383 msgid "" "Determines whether the bot will reply\n" " privately when replying in a channel, rather than replying to the whole\n" " channel." msgstr "" #: ../src/conf.py:388 msgid "" "Determines whether the bot will reply with a\n" " notice when replying in a channel, rather than replying with a privmsg as\n" " normal." msgstr "" #: ../src/conf.py:394 msgid "" "Determines whether the bot will reply with a\n" " notice when it is sending a private message, in order not to open a /query\n" " window in clients. This can be overridden by individual users via the user\n" " configuration variable reply.withNoticeWhenPrivate." msgstr "" #: ../src/conf.py:400 msgid "" "Determines whether the bot will always prefix\n" " theuser's nick to its reply to that user's command." msgstr "" #: ../src/conf.py:404 msgid "" "Determines whether the bot should attempt to\n" " reply to all messages even if they don't address it (either via its nick\n" " or a prefix character). If you set this to True, you almost certainly want\n" " to set supybot.reply.whenNotCommand to False." msgstr "" #: ../src/conf.py:410 msgid "" "Determines whether the bot will allow you to\n" " send channel-related commands outside of that channel. Sometimes people\n" " find it confusing if a channel-related command (like Filter.outfilter)\n" " changes the behavior of the channel but was sent outside the channel\n" " itself." msgstr "" #: ../src/conf.py:417 msgid "" "Determines whether the bot will unidentify\n" " someone when that person changes their nick. Setting this to True\n" " will cause the bot to track such changes. It defaults to False for a\n" " little greater security." msgstr "" #: ../src/conf.py:423 msgid "" "Determines whether the bot will always join a\n" " channel when it's invited. If this value is False, the bot will only join\n" " a channel if the user inviting it has the 'admin' capability (or if it's\n" " explicitly told to join the channel using the Admin.join command)" msgstr "" #: ../src/conf.py:429 msgid "" "Supybot normally replies with the full help\n" " whenever a user misuses a command. If this value is set to True, the bot\n" " will only reply with the syntax of the command (the first line of the\n" " help) rather than the full help." msgstr "" #: ../src/conf.py:443 msgid "" "Determines what prefix characters the bot will\n" " reply to. A prefix character is a single character that the bot will use\n" " to determine what messages are addressed to it; when there are no prefix\n" " characters set, it just uses its nick. Each character in this string is\n" " interpreted individually; you can have multiple prefix chars\n" " simultaneously, and if any one of them is used as a prefix the bot will\n" " assume it is being addressed." msgstr "" #: ../src/conf.py:452 msgid "" "Determines what strings the\n" " bot will reply to when they are at the beginning of the message. Whereas\n" " prefix.chars can only be one character (although there can be many of\n" " them), this variable is a space-separated list of strings, so you can\n" " set something like '@@ ??' and the bot will reply when a message is\n" " prefixed by either @@ or ??." msgstr "" #: ../src/conf.py:459 msgid "" "Determines whether the bot will reply when\n" " people address it by its nick, rather than with a prefix character." msgstr "" #: ../src/conf.py:462 msgid "" "Determines whether the bot will reply when\n" " people address it by its nick at the end of the message, rather than at\n" " the beginning." msgstr "" #: ../src/conf.py:466 msgid "" "Determines what extra nicks\n" " the bot will always respond to when addressed by, even if its current nick\n" " is something else." msgstr "" #: ../src/conf.py:476 msgid "The operation succeeded." msgstr "Anweisung erfolgreich." #: ../src/conf.py:477 msgid "" "Determines what message the bot replies with when a command succeeded.\n" " If this configuration variable is empty, no success message will be\n" " sent." msgstr "Legt fest mit welcher Nachricht der Bot antwortet, wenn ein Befehl erfolgreich war. Falls diese Konfigurationsvariable leer ist wird keine Erfolgsmeldung gesendet." #: ../src/conf.py:482 msgid "" "An error has occurred and has been logged.\n" " Please contact this bot's administrator for more information." msgstr "" "Ein Fehler ist aufgetreten und wurde gelogged.\n" " Bitte kontaktiere den Botadministartor für mehr Informationen." #: ../src/conf.py:483 msgid "" "\n" " Determines what error message the bot gives when it wants to be\n" " ambiguous." msgstr "Bestimmt welche Fehlermeldung der Bot ausgibt, wenn er zweideutig sein möchte." #: ../src/conf.py:488 msgid "" "An error has occurred and has been logged.\n" " Check the logs for more informations." msgstr "" "Ein Fehler ist augetreten und wurde protokolliert.\n" " Überprüfe die Protokolldateien." #: ../src/conf.py:489 msgid "" "Determines what error\n" " message the bot gives to the owner when it wants to be ambiguous." msgstr "Bestimmt welche Fehlermeldung der Bot an den Besitzer ausgibt, wenn er zweideutig sein möchte." #: ../src/conf.py:493 msgid "" "Your hostmask doesn't match or your password\n" " is wrong." msgstr "" "Deine Hostmaske passt nicht oder dein Passwort\n" " ist falsch." #: ../src/conf.py:494 msgid "" "Determines what message the bot replies with when\n" " someone tries to use a command that requires being identified or having a\n" " password and neither credential is correct." msgstr "Bestimmt welche Fehlermeldung der Bot ausgibt, wenn jemand einen Befehl versucht der es erforderlich macht sich identifiziert zu haben oder korrekte Anmeldedaten zu haben." #: ../src/conf.py:500 msgid "" "I can't find %s in my user\n" " database. If you didn't give a user name, then I might not know what your\n" " user is, and you'll need to identify before this command might work." msgstr "Ich konnte %s nicht in meiner Benutzerdatenbank finden. Falls du keinen Benutzernamen angegeben hast, weiß ich vielleicht nicht wer du bist unnd du mich dich identifizieren bevor dieser Befehl funktioniert." #: ../src/conf.py:503 msgid "" "Determines what error message the bot replies with when someone tries\n" " to accessing some information on a user the bot doesn't know about." msgstr "Bestimmt welche Fehlermeldung der Bot ausgibt, wenn jemand versucht auf Informationen zuzugreifen über die der Bot nichts weiß." #: ../src/conf.py:507 msgid "" "You must be registered to use this command.\n" " If you are already registered, you must either identify (using the identify\n" " command) or add a hostmask matching your current hostmask (using the\n" " \"hostmask add\" command)." msgstr "Du musst registiert sein um diesen Befehl benutzen zu können. Wenn du bereits registriert bist musst du dich identifizieren (durch den identify Befehl) oder eine Hostmaske hinzufügen die zu deiner momentanen Hostmaske passt (durch den \"hostmask add\" Befehl)." #: ../src/conf.py:510 msgid "" "Determines what error message the bot\n" " replies with when someone tries to do something that requires them to be\n" " registered but they're not currently recognized." msgstr "Bestimmt welche Fehlermeldung der Bot antortet wenn jemand etwas versucht, das es erforderlich macht registriert zu sein, es aber nicht ist." #: ../src/conf.py:515 msgid "" "You don't have the %s capability. If you\n" " think that you should have this capability, be sure that you are identified\n" " before trying again. The 'whoami' command can tell you if you're\n" " identified." msgstr "Du hast die %s Fähigkeit nicht. Falls du denkst du solltest diese haben, sei dir sicher das du identifiziert bist, bevor du es erneurt versuchst. Der Befehl 'whoami' kann dir sagen ob du identifiziert bist." #: ../src/conf.py:518 msgid "" "Determines what error message is given when the bot\n" " is telling someone they aren't cool enough to use the command they tried to\n" " use." msgstr "Bestimmt welche Fehlermeldung der Bot einer Person ausgibt, die nicht cool genug ist den Befehl auszuführen." #: ../src/conf.py:523 msgid "" "You're missing some capability you need.\n" " This could be because you actually possess the anti-capability for the\n" " capability that's required of you, or because the channel provides that\n" " anti-capability by default, or because the global capabilities include\n" " that anti-capability. Or, it could be because the channel or\n" " supybot.capabilities.default is set to False, meaning that no commands are\n" " allowed unless explicitly in your capabilities. Either way, you can't do\n" " what you want to do." msgstr "Dir fehlen ein paar Fähigkeiten. Es könnte sein, dass du die Anti-Fähigkeit für diese Fähigkeit besitzt oder der Channel diese Anti-Fähigkeit als Standard bereitstellt oder die globalen Anti-Fähigkeiten diese als Standard bereitstellen. Es könnte auch sein, dass der Kanal oder supybot.capabilities.default auf False eingestellt sind, bedeutet es sind keinerlei Befehle erlaubt ausgenommen der explizit zugestandenen mittels Fähigkeiten. Wie dem auch sei, das was du versuchst funktioniert gerade nicht." #: ../src/conf.py:531 msgid "" "Determines what generic error message is given when the bot is telling\n" " someone that they aren't cool enough to use the command they tried to use,\n" " and the author of the code calling errorNoCapability didn't provide an\n" " explicit capability for whatever reason." msgstr "Bestimt welche generische Fehlernachricht der Bot einer Person ausgibt, die nicht cool genug ist den Befehl auszuführen den sie versucht hat und der Autor des Codes hat keine bestimmte Fähigkeit dafür bereitgestellt." #: ../src/conf.py:537 #, fuzzy msgid "" "That operation cannot be done in a\n" " channel." msgstr "Diese Anweisung kann nicht in einem Kanal gegeben werden." #: ../src/conf.py:538 msgid "" "Determines what error messages the bot sends to people\n" " who try to do things in a channel that really should be done in\n" " private." msgstr "Bestimmt welche Fehlermeldung der Bot an Leute schickt, die Dinge im Kanal versuchen, welche jedoch besser per privater Nachricht getan werden sollten." #: ../src/conf.py:543 msgid "" "This may be a bug. If you think it is,\n" " please file a bug report at\n" " ." msgstr "Das könnte ein Programmierfehler sein. Falls du das denkst, melden diesen Fehler bitte bei ." #: ../src/conf.py:546 msgid "" "Determines what message the bot sends when it thinks you've\n" " encountered a bug that the developers don't know about." msgstr "Bestimmt welche Meldung der Bot schickt, wenn er glaubt, dass du einen Programmierfehler gefunden hast von dem die Entwickler nichts wissen." #: ../src/conf.py:553 msgid "" "A floating point number of seconds to throttle\n" " snarfed URLs, in order to prevent loops between two bots snarfing the same\n" " URLs and having the snarfed URL in the output of the snarf message." msgstr "Eine Fließkommazahl in Sekunden um das Erschnüffeln von URLs zu bremsen. Notwendig um zu verhindern, dass zum einen zwei Bots die gleichen URLs erschnüffeln und zum anderen die erschnüffelte URL in der Ausgabe der erschnüffelten Nachricht zu haben." #: ../src/conf.py:558 msgid "" "Determines the number of seconds\n" " between running the upkeep function that flushes (commits) open databases,\n" " collects garbage, and records some useful statistics at the debugging\n" " level." msgstr "Bestimmt die Anzahl der Sekunden zwischen einem Durchlauf der upkeep-Funktion, die offene in die Datenbank schreibt, Müll sammelt und ein paar nützliche Statistiken zum Zwecke der Fehlersuche sammelt." #: ../src/conf.py:564 msgid "" "Determines whether the bot will periodically\n" " flush data and configuration files to disk. Generally, the only time\n" " you'll want to set this to False is when you want to modify those\n" " configuration files by hand and don't want the bot to flush its current\n" " version over your modifications. Do note that if you change this to False\n" " inside the bot, your changes won't be flushed. To make this change\n" " permanent, you must edit the registry yourself." msgstr "Bestimmt ob der Bot perodisch alle Daten auf die Festplatte schreibt. Generell sollte das nur auf False stehen wenn du die Konfigurationsdateien von Hand ändern und verhindern willst, dass der Bot deine Änderungen überschreibt. Wenn du diesen Wert auf False änderst bedenke, dass deine Änderungen innerhalb des Bots nicht gespeichert werden. Um diese Änderungen permanent zu machen musst du die Registrierung händisch anpassen." #: ../src/conf.py:589 msgid "" "Determines what characters are valid for quoting\n" " arguments to commands in order to prevent them from being tokenized.\n" " " msgstr "Bestimmt welche Zeichen für das Zitieren von Argumenten an Befehle gültig sind um zu verhindern, dass diese in Tokens übersetzt werden." #: ../src/conf.py:596 msgid "" "Determines whether the bot will allow nested\n" " commands, which rule. You definitely should keep this on." msgstr "" #: ../src/conf.py:599 msgid "" "Determines what the maximum number of\n" " nested commands will be; users will receive an error if they attempt\n" " commands more nested than this." msgstr "" #: ../src/conf.py:607 msgid "" "Supybot allows you to specify what brackets are\n" " used for your nested commands. Valid sets of brackets include [], <>, and\n" " {} (). [] has strong historical motivation, as well as being the brackets\n" " that don't require shift. <> or () might be slightly superior because they\n" " cannot occur in a nick. If this string is empty, nested commands will\n" " not be allowed in this channel." msgstr "" #: ../src/conf.py:614 msgid "" "Supybot allows nested commands. Enabling this\n" " option will allow nested commands with a syntax similar to UNIX pipes, for\n" " example: 'bot: foo | bar'." msgstr "Supybot erlaubt verschachtelte Befehle. Wenn man diese Option aktiviert wird der Bot verschachtelte Befehle erlauben, mit einer Syntax die ähnlich ist wie UNIX Pipes, zum Beispiel 'bot: foo | bar'." #: ../src/conf.py:619 msgid "" "Determines what commands have default\n" " plugins set, and which plugins are set to be the default for each of those\n" " commands." msgstr "" #: ../src/conf.py:625 msgid "" "Determines what plugins automatically get precedence over all\n" " other plugins when selecting a default plugin for a command. By\n" " default, this includes the standard loaded plugins. You probably\n" " shouldn't change this if you don't know what you're doing; if you do\n" " know what you're doing, then also know that this set is\n" " case-sensitive." msgstr "" #: ../src/conf.py:640 msgid "" "Determines whether the bot will defend itself\n" " against command-flooding." msgstr "" #: ../src/conf.py:643 msgid "" "Determines how many commands users are\n" " allowed per minute. If a user sends more than this many commands in any\n" " 60 second period, they will be ignored for\n" " supybot.abuse.flood.command.punishment seconds." msgstr "" #: ../src/conf.py:648 msgid "" "Determines how many seconds the bot\n" " will ignore users who flood it with commands." msgstr "" #: ../src/conf.py:652 msgid "" "Determines whether the bot will defend itself\n" " against invalid command-flooding." msgstr "" #: ../src/conf.py:655 msgid "" "Determines how many invalid commands users\n" " are allowed per minute. If a user sends more than this many invalid\n" " commands in any 60 second period, they will be ignored for\n" " supybot.abuse.flood.command.invalid.punishment seconds. Typically, this\n" " value is lower than supybot.abuse.flood.command.maximum, since it's far\n" " less likely (and far more annoying) for users to flood with invalid\n" " commands than for them to flood with valid commands." msgstr "" #: ../src/conf.py:663 msgid "" "Determines how many seconds the bot\n" " will ignore users who flood it with invalid commands. Typically, this\n" " value is higher than supybot.abuse.flood.command.punishment, since it's far\n" " less likely (and far more annoying) for users to flood witih invalid\n" " commands than for them to flood with valid commands." msgstr "" #: ../src/conf.py:669 msgid "" "Determines whether the bot will notify people\n" " that they're being ignored for invalid command flooding." msgstr "" #: ../src/conf.py:678 msgid "" "Determines the default length of time a\n" " driver should block waiting for input." msgstr "" #: ../src/conf.py:685 msgid "" "Determines what driver module the bot\n" " will use. Socket, a simple driver based on timeout sockets, is used by\n" " default because it's simple and stable. Twisted is very stable and simple,\n" " and if you've got Twisted installed, is probably your best bet." msgstr "" #: ../src/conf.py:691 msgid "" "Determines the maximum time the bot will\n" " wait before attempting to reconnect to an IRC server. The bot may, of\n" " course, reconnect earlier if possible." msgstr "" #: ../src/conf.py:740 msgid "" "Determines what directory configuration data is\n" " put into." msgstr "Legt fest in welchem Verzeichnis Konfigurationsdaten gespeichert werden." #: ../src/conf.py:743 msgid "Determines what directory data is put into." msgstr "Legt fest in welchem Verzeichnis Daten gespeichert werden." #: ../src/conf.py:745 msgid "" "Determines what directory backup data is put\n" " into." msgstr "Legt fest in welchem Verzeichnis Sicherungskopien gespeichert werden." #: ../src/conf.py:748 msgid "" "Determines what directory temporary files\n" " are put into." msgstr "Legt fest in welchem Verzeichnis temporäre Dateien gespeichert werden." #: ../src/conf.py:755 msgid "" "Determines what directories\n" " the bot will look for plugins in. Accepts a comma-separated list of\n" " strings.\n" " This means that to add another directory, you can nest the former value and\n" " add a new one. E.g. you can say: bot: 'config supybot.directories.plugins\n" " [config supybot.directories.plugins], newPluginDirectory'." msgstr "" #: ../src/conf.py:763 msgid "" "Determines what plugins will\n" " be loaded." msgstr "Legt fest welche Plugins geladen werden." #: ../src/conf.py:766 msgid "" "Determines whether the bot will always load\n" " important plugins (Admin, Channel, Config, Misc, Owner, and User)\n" " regardless of what their configured state is. Generally, if these plugins\n" " are configured not to load, you didn't do it on purpose, and you still\n" " want them to load. Users who don't want to load these plugins are smart\n" " enough to change the value of this variable appropriately :)" msgstr "" #: ../src/conf.py:793 msgid "" "Determines what databases are available for use. If this\n" " value is not configured (that is, if its value is empty) then sane defaults\n" " will be provided." msgstr "" #: ../src/conf.py:799 msgid "" "Determines what filename will be used\n" " for the users database. This file will go into the directory specified by\n" " the supybot.directories.conf variable." msgstr "" #: ../src/conf.py:803 msgid "" "Determines how long it takes identification to\n" " time out. If the value is less than or equal to zero, identification never\n" " times out." msgstr "" #: ../src/conf.py:807 msgid "" "Determines whether the bot will allow users to\n" " unregister their users. This can wreak havoc with already-existing\n" " databases, so by default we don't allow it. Enable this at your own risk.\n" " (Do also note that this does not prevent the owner of the bot from using\n" " the unregister command.)\n" " " msgstr "" #: ../src/conf.py:816 msgid "" "Determines what filename will be used\n" " for the ignores database. This file will go into the directory specified\n" " by the supybot.directories.conf variable." msgstr "" #: ../src/conf.py:822 msgid "" "Determines what filename will be used\n" " for the channels database. This file will go into the directory specified\n" " by the supybot.directories.conf variable." msgstr "" #: ../src/conf.py:852 msgid "" "Determines whether database-based plugins that\n" " can be channel-specific will be so. This can be overridden by individual\n" " channels. Do note that the bot needs to be restarted immediately after\n" " changing this variable or your db plugins may not work for your channel;\n" " also note that you may wish to set\n" " supybot.databases.plugins.channelSpecific.link appropriately if you wish\n" " to share a certain channel's databases globally." msgstr "" #: ../src/conf.py:860 msgid "" "Determines what channel global\n" " (non-channel-specific) databases will be considered a part of. This is\n" " helpful if you've been running channel-specific for awhile and want to turn\n" " the databases for your primary channel into global databases. If\n" " supybot.databases.plugins.channelSpecific.link.allow prevents linking, the\n" " current channel will be used. Do note that the bot needs to be restarted\n" " immediately after changing this variable or your db plugins may not work\n" " for your channel." msgstr "" #: ../src/conf.py:869 msgid "" "Determines whether another channel's global\n" " (non-channel-specific) databases will be allowed to link to this channel's\n" " databases. Do note that the bot needs to be restarted immediately after\n" " changing this variable or your db plugins may not work for your channel.\n" " " msgstr "" #: ../src/conf.py:886 msgid "" "Determines\n" " whether CDB databases will be allowed as a database implementation." msgstr "" #: ../src/conf.py:889 msgid "" "Determines how often CDB databases will have\n" " their modifications flushed to disk. When the number of modified records\n" " is greater than this fraction of the total number of records, the database\n" " will be entirely flushed to disk." msgstr "" #: ../src/conf.py:974 msgid "" "Determines what will be used as the\n" " default banmask style." msgstr "Legt fest was als Standard Banmaskenstil verwendet wird." #: ../src/conf.py:978 msgid "" "Determines whether the bot will strictly follow\n" " the RFC; currently this only affects what strings are considered to be\n" " nicks. If you're using a server or a network that requires you to message\n" " a nick such as services@this.network.server then you you should set this to\n" " False." msgstr "" #: ../src/conf.py:985 msgid "" "Determines what user modes the bot will request\n" " from the server when it first connects. Many people might choose +i; some\n" " networks allow +x, which indicates to the auth services on those networks\n" " that you should be given a fake host." msgstr "" #: ../src/conf.py:991 msgid "" "Determines what vhost the bot will bind to before\n" " connecting to the IRC server." msgstr "" #: ../src/conf.py:995 msgid "" "Determines how many old messages the bot will\n" " keep around in its history. Changing this variable will not take effect\n" " until the bot is restarted." msgstr "" #: ../src/conf.py:1000 msgid "" "A floating point number of seconds to throttle\n" " queued messages -- that is, messages will not be sent faster than once per\n" " throttleTime seconds." msgstr "" #: ../src/conf.py:1005 msgid "" "Determines whether the bot will send PINGs to\n" " the server it's connected to in order to keep the connection alive and\n" " discover earlier when it breaks. Really, this option only exists for\n" " debugging purposes: you always should make it True unless you're testing\n" " some strange server issues." msgstr "" #: ../src/conf.py:1012 msgid "" "Determines the number of seconds between sending\n" " pings to the server, if pings are being sent to the server." msgstr "" #: ../src/conf.py:1017 msgid "" "Determines whether the bot will refuse\n" " duplicate messages to be queued for delivery to the server. This is a\n" " safety mechanism put in place to prevent plugins from sending the same\n" " message multiple times; most of the time it doesn't matter, unless you're\n" " doing certain kinds of plugin hacking." msgstr "" #: ../src/conf.py:1025 msgid "" "Determines how many seconds must elapse between\n" " JOINs sent to the server." msgstr "Legt fest wieviel Zeit, zwischen den JOINs die an den Server gesendet werden, vergehen muss." #: ../src/conf.py:1033 msgid "" "Determines how many bytes the bot will\n" " 'peek' at when looking through a URL for a doctype or title or something\n" " similar. It'll give up after it reads this many bytes, even if it hasn't\n" " found what it was looking for." msgstr "" #: ../src/conf.py:1039 msgid "" "Determines what proxy all HTTP requests should go\n" " through. The value should be of the form 'host:port'." msgstr "" #: ../src/conf.py:1059 msgid "Determines what host the HTTP server will bind." msgstr "Legt fest an welchen host sich der HTTP Server bindet." #: ../src/conf.py:1061 msgid "" "Determines what port the HTTP server will\n" " bind." msgstr "Legt den Port fest an den der HTTP Server bindet." #: ../src/conf.py:1064 msgid "" "Defines whether the server will stay alive if\n" " no plugin is using it. This also means that the server will start even\n" " if it is not used." msgstr "" "Legt fest ob der Server gestartet bleiben soll, wenn\n" " kein Plugin ihn benutzt. Das bedeutet außerdem das der Server startet\n" " obwohl er nicht benutzt wird." #: ../src/conf.py:1073 msgid "" "Determines whether the bot will ignore\n" " unregistered users by default. Of course, that'll make it particularly\n" " hard for those users to register or identify with the bot, but that's your\n" " problem to solve." msgstr "" #: ../src/conf.py:1080 msgid "" "A string that is the external IP of the bot. If this is the\n" " empty string, the bot will attempt to find out its IP dynamically (though\n" " sometimes that doesn't work, hence this variable)." msgstr "" #: ../src/conf.py:1094 msgid "" "Determines what the default timeout for socket\n" " objects will be. This means that *all* sockets will timeout when this many\n" " seconds has gone by (unless otherwise modified by the author of the code\n" " that uses the sockets)." msgstr "" #: ../src/conf.py:1100 msgid "" "Determines what file the bot should write its PID\n" " (Process ID) to, so you can kill it more easily. If it's left unset (as is\n" " the default) then no PID file will be written. A restart is required for\n" " changes to this variable to take effect." msgstr "" #: ../src/conf.py:1110 msgid "" "Determines whether the bot will automatically\n" " thread all commands." msgstr "Legt fest ob der Bot automatisch alles als Befehl behandeln soll." #: ../src/conf.py:1113 msgid "" "Determines whether the bot will automatically\n" " flush all flushers *very* often. Useful for debugging when you don't know\n" " what's breaking or when, but think that it might be logged." msgstr "" #: ../src/questions.py:60 msgid "Sorry, that response was not an option." msgstr "Entschuldigung, diese Antwort war keine Option." #: ../src/questions.py:106 msgid "Sorry, you must enter a value." msgstr "Entschuldigung du musst einen Wert angeben." #: ../src/questions.py:126 msgid "Enter password: " msgstr "Passwort eingeben:" #: ../src/questions.py:128 msgid "Re-enter password: " msgstr "Passwort erneut eingeben:" #: ../src/questions.py:141 msgid "Passwords don't match." msgstr "Passwörter stimmen nicht überein" #: ../src/utils/httpserver.py:129 #, fuzzy msgid "" "\n" " This is a default response of the Supybot HTTP server. If you see this\n" " message, it probably means you are developping a plugin, and you have\n" " neither overriden this message or defined an handler for this query." msgstr "" "\n" " Das ist eine Standardantwort eines Supybot HTTP Servers.\n" " Wenn du diese Nachricht siehst, heißt das womöglich das du ein Plugin\n" " entwickelst und das weder diese Nachricht übersüringst oder einen Handler für diese Anfrage definiert hast." #: ../src/utils/httpserver.py:150 msgid "" "\n" " I am a pretty clever IRC bot, but I suck at serving Web pages, particulary\n" " if I don't know what to serve.\n" " What I'm saying is you just triggered a 404 Not Found, and I am not\n" " trained to help you in such a case." msgstr "" #: ../src/utils/httpserver.py:167 #, fuzzy msgid "Request not handled." msgstr "Anfrage nicht behandelt." #: ../src/utils/httpserver.py:171 msgid "Supybot Web server index" msgstr "Supybot Web Server Index" #: ../src/utils/httpserver.py:174 msgid "Here is a list of the plugins that have a Web interface:" msgstr "Das ist eine Liste der Plugins die ein Webinterface haben:" #: ../src/utils/httpserver.py:183 msgid "No plugins available." msgstr "Keine Plugins verfügbar" limnoria-2020.03.17/locales/fi.po0000644000175000017500000020763513634634532015714 0ustar valval00000000000000# Limnoria # Copyright (C) 2011-2014 Limnoria # Mikaela Suomalainen , 2011-2014. # msgid "" msgstr "" "Project-Id-Version: Limnoria core\n" "POT-Creation-Date: 2014-12-20 11:17+EET\n" "PO-Revision-Date: 2014-12-20 11:27+0200\n" "Last-Translator: Mikaela Suomalainen \n" "Language-Team: Finnish <>\n" "Language: fi\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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.10\n" #: src/callbacks.py:189 msgid "Error: " msgstr "Virhe: " #: src/callbacks.py:203 msgid "Error: I tried to send you an empty message." msgstr "Virhe: Yritin lähettää sinulle tyhjän viestin." #: src/callbacks.py:318 msgid "" "Missing \"%s\". You may want to quote your arguments with double quotes in " "order to prevent extra brackets from being evaluated as nested commands." msgstr "" "Puttuva \"%s\". Voit tahtoa laittaa parametrisi kaksiin lainausmerkkeihin " "estääksesi ylimääräisiä hakasulkuja tulemasta tulkituiksi sisäkkäisiksi " "komennoiksi." #: src/callbacks.py:348 msgid "" "\"|\" with nothing preceding. I obviously can't do a pipe with nothing " "before the |." msgstr "" "\"|\" ennen mitään. En ilmiselvästi voi putkittaa tyhjyyttä ilman mitään " "ennen |-merkkiä." #: src/callbacks.py:356 msgid "" "Spurious \"%s\". You may want to quote your arguments with double quotes in " "order to prevent extra brackets from being evaluated as nested commands." msgstr "" "Pettävää \"%s\". Voit tahtoa laittaa parametrisi kaksiin lainausmerkkeihin " "estääksesiylimääräisiä hakasulkuja tulemasta tulkituiksi sisäkkäisiksi " "komennoiksi." #: src/callbacks.py:365 msgid "" "\"|\" with nothing following. I obviously can't do a pipe with nothing " "after the |." msgstr "" "\"|\" ilman mitään sen perässä. En ilmeisesti voi putkittaa tyhjyyttä |:n " "jälkeen." #: src/callbacks.py:563 msgid "%s is not a valid %s." msgstr "%s ei ole kelvollinen %s." #: src/callbacks.py:565 msgid "That's not a valid %s." msgstr "Tuo ei ole kelvollinen %s." #: src/callbacks.py:643 msgid "You've attempted more nesting than is currently allowed on this bot." msgstr "" "Yritit enempiä sisäkkäisiä komentoja, kuin on tällä hetkellä sallittu tässä " "botissa." #: src/callbacks.py:823 msgid "" "The command %q is available in the %L plugins. Please specify the plugin " "whose command you wish to call by using its name as a command before %q." msgstr "" "Komento %q on saatavilla %L lisäosissa. Ole hyvä ja määritä minkä lisäosan " "komentoa tahdot kutsua laittamalla sen nimi komentoon ennen %q:ta." #: src/callbacks.py:909 src/callbacks.py:922 msgid "(XX more messages)" msgstr "(XX viestiä jatkoa)" #: src/callbacks.py:955 msgid "more message" msgstr "viesti jatkoa" #: src/callbacks.py:957 msgid "more messages" msgstr "viestejä jatkoa" #: src/callbacks.py:1074 msgid "" "Determines what commands are currently disabled. Such\n" " commands will not appear in command lists, etc. They will appear not " "even\n" " to exist." msgstr "" "Määrittää mitkä komennon ovat tällä hetkellä pois käytöstä. Sellaiset\n" " komennot eivät ilmesty komento listoissa, jne. Ne eivät näytä edes\n" " olevan olemassa." #: src/callbacks.py:1278 msgid "Invalid arguments for %s." msgstr "Virheelliset parametrit kohteelle %s." #: src/callbacks.py:1304 msgid "The %q command has no help." msgstr "Komennolla %q ei ole ohjetta." #: src/commands.py:257 msgid "integer" msgstr "kokonaisluku" #: src/commands.py:268 msgid "non-integer value" msgstr "ei-kokonaisluku arvo" #: src/commands.py:279 msgid "floating point number" msgstr "liukuluku numero" #: src/commands.py:288 msgid "positive integer" msgstr "positiivinen kokonaisluku" #: src/commands.py:292 msgid "non-negative integer" msgstr "ei-negatiivinen kokonaisluku" #: src/commands.py:295 msgid "index" msgstr "indeksi" #: src/commands.py:320 msgid "number of seconds" msgstr "määrä sekunteja" #: src/commands.py:327 msgid "boolean" msgstr "boolean" #: src/commands.py:341 src/commands.py:348 src/commands.py:357 #: src/commands.py:364 src/commands.py:373 msgid "do that" msgstr "tee se" #: src/commands.py:344 src/commands.py:351 src/commands.py:360 #: src/commands.py:367 src/commands.py:376 msgid "I'm not even in %s." msgstr "En edes ole kanavalla %s." #: src/commands.py:346 msgid "I need to be voiced to %s." msgstr "Minulla täytyy olla ääni tehdäkseni %s." #: src/commands.py:354 #, fuzzy msgid "I need to be at least voiced to %s." msgstr "Minulla täytyy olla ainakin voice, jotta voin %s" #: src/commands.py:362 msgid "I need to be halfopped to %s." msgstr "" "Minulla täytyy olla puolikanavaoperaattorin valtuudet voidakseni tehdä %s." #: src/commands.py:370 msgid "I need to be at least halfopped to %s." msgstr "" "Minulla täytyy olla vähintään puolioperaattorin oikeudet voidakseni tehdä %s" #: src/commands.py:378 msgid "I need to be opped to %s." msgstr "Minun täytyy olla opattuna, jotta voin %s." #: src/commands.py:384 src/commands.py:533 msgid "channel" msgstr "kanava" #: src/commands.py:397 msgid "nick or hostmask" msgstr "nimimerkki tai hostmask" #: src/commands.py:448 src/commands.py:451 msgid "regular expression" msgstr "säännöllinen lauseke" #: src/commands.py:461 src/commands.py:465 msgid "nick" msgstr "nimimerkki" #: src/commands.py:462 msgid "That nick is too long for this server." msgstr "Tuo nimimerkki on liian pitkä tälle palvelimelle." #: src/commands.py:473 msgid "I haven't seen %s." msgstr "En ole nähnyt käyttäjää %s." #: src/commands.py:512 src/commands.py:531 msgid "I'm not in %s." msgstr "En ole kanavalla %s." #: src/commands.py:516 msgid "This command may only be given in a channel that I am in." msgstr "Tämä komento voidaan antaa vain kanavalla, jolla minä olen." #: src/commands.py:529 msgid "You must be in %s." msgstr "Sinun täytyy olla kanavalla %s." #: src/commands.py:540 msgid "%s is not in %s." msgstr "%s ei ole kanavalla %s." #: src/commands.py:588 msgid "You must not give the empty string as an argument." msgstr "Et voi antaa tyhjää merkkiketjua parametriksi." #: src/commands.py:596 #, fuzzy msgid "You must not give a string containing spaces as an argument." msgstr "Et voi antaa välilyöntejä sisältävää merkkiketjua parametriksi." #: src/commands.py:606 msgid "This message must be sent in a channel." msgstr "Tämä viesti täytyy lähettää kanavalla." #: src/commands.py:638 msgid "url" msgstr "url" #: src/commands.py:644 msgid "email" msgstr "sähköposti" #: src/commands.py:652 msgid "http url" msgstr "http URL-osoite" #: src/commands.py:659 msgid "command name" msgstr "komennon nimi" #: src/commands.py:667 msgid "ip" msgstr "IP" #: src/commands.py:673 msgid "letter" msgstr "kirjain" #: src/commands.py:705 msgid "plugin" msgstr "lisä-osa" #: src/commands.py:713 msgid "irc color" msgstr "irc väri" #: src/conf.py:98 msgid "" "Determines whether this plugin is loaded\n" " by default." msgstr "Määrittää ladataanko tämä lisäosa oletuksena." #: src/conf.py:102 msgid "" "Determines whether this plugin is\n" " publicly visible." msgstr "" "Määrittää onko tämä lisäosa\n" " julkisesti näkyvillä." #: src/conf.py:196 msgid "Determines the bot's default nick." msgstr "Määrittää botin oletus nimimerkin." #: src/conf.py:199 msgid "" "Determines what alternative\n" " nicks will be used if the primary nick (supybot.nick) isn't available. " "A\n" " %s in this nick is replaced by the value of supybot.nick when used. If " "no\n" " alternates are given, or if all are used, the supybot.nick will be " "perturbed\n" " appropriately until an unused nick is found." msgstr "" "Määrittää mitä vaihtoehtoisia nimimerkkejä käytetään, jos\n" " ensisijainen nimimerkki (supybot.nick) ei ole saatavilla. \n" " %s tässä nimimerkissä korvataa asetusarvolla supybot.nick, kun sitä " "käytetään. Jos\n" " vaihtoehtoja ei anneta, tai jos kaikki ovat käytössä, supybot.nick " "tehdään levottomaksi, \n" " kunnes käyttämätön nimimerkki löydetään." #: src/conf.py:206 msgid "" "Determines the bot's ident string, if the server\n" " doesn't provide one by default." msgstr "" "Määrittää botin ident merkkiketjun, mikäli käyttäjä\n" " ei tarjoa yhtä oletuksena." #: src/conf.py:217 msgid "" "Determines the real name which the bot sends to\n" " the server. A standard real name using the current version of the bot\n" " will be generated if this is left empty." msgstr "" "Määrittää minkä oikean nimen botti lähettää palvelimelle.\n" " Perus user-merkkiketju luodaan botin nykyisestä versiosta, mikäli\n" " tämä on jätetty tyhjäksi." #: src/conf.py:225 msgid "Determines what networks the bot will connect to." msgstr "Määrittää mihin verkkoihin botti muodostaa yhdeyden." #: src/conf.py:307 msgid "" "Determines what password will be used on %s. Yes, we know that\n" " technically passwords are server-specific and not network-specific,\n" " but this is the best we can do right now." msgstr "" "Määrittää mitä salasanaa käytetään verkossa %s. Kyllä, me tiedämme, että \n" " tietyille palvelimille, eivätkä tietyille verkoille, mutta\n" " tämä on paras johon pystymme juuri nyt." #: src/conf.py:311 msgid "" "Space-separated list of servers the bot will connect to for %s.\n" " Each will be tried in order, wrapping back to the first when the " "cycle\n" " is completed." msgstr "" "Välilyönneillä erotettu lista palvelimista, joihin botti muodostaa yhteyden " "muodostaakseen yhteyden\n" " verkkoon %s. Jokaista palvelinta yritetään, palaten takaisin ensimmäiseen, " "kun kaikkia on yritetty." #: src/conf.py:315 msgid "Space-separated list of channels the bot will join only on %s." msgstr "" "Välilyönneille eroteltu lista kanavista, joille botti liittyy vain verkossa " "%s. " #: src/conf.py:318 msgid "" "Determines whether the bot will attempt to connect with SSL\n" " sockets to %s." msgstr "" "Määrittää yrittääkö botti yhdistää SSL\n" " solmuilla verkkoon %s." #: src/conf.py:321 msgid "" "Determines what certificate file (if any) the bot will use to\n" " connect with SSL sockets to %s." msgstr "" "Määrittää mitä SSL-varmennetta (jos mitään) botti käyttää yhdistäessään\n" " verkkoon %s käyttäen SSL-solmuja." #: src/conf.py:324 msgid "" "Determines what key (if any) will be used to join the\n" " channel." msgstr "" "Määrittää mitä salasanaa (jos mitään) käytetään kanavalle\n" " liittymisessä." #: src/conf.py:326 msgid "" "Determines\n" " what nick the bot will use on this network. If empty, defaults to\n" " supybot.nick." msgstr "" "Määrittää mitä nimimerkkiä botti käyttää tässä verkossa. Jos tämä on tyhjä, " "niin se on\n" " oletuksena supybot.nick" #: src/conf.py:329 msgid "" "Determines\n" " the bot's ident string, if the server doesn't provide one by " "default.\n" " If empty, defaults to supybot.ident." msgstr "" "Määrittää botin identin, ellei palvelin määritä sitä valmiiksi.\n" " Ollessaan tyhjä, tämä on oletuksena supybot.ident." #: src/conf.py:332 msgid "" "Determines\n" " the real name which the bot sends to the server. If empty, defaults " "to\n" " supybot.user" msgstr "" "Määrittää oikean nimen, jonka botti lähettää palvelimelle. Ollessaan tyhjä " "tämä on supybot.user:in\n" " asetusarvo." #: src/conf.py:336 #, fuzzy msgid "" "Determines what user modes the bot will request\n" " from the server when it first connects. If empty, defaults to\n" " supybot.protocols.irc.umodes" msgstr "" "Määrittää mitä umodeja botti pyytää palvelimelta yhdistäessään. Jos tämä on " "tyhjä\n" " oletusasetus on supybot.protocols.irc.umodes." #: src/conf.py:341 msgid "" "Determines what SASL username will be used on %s. This should\n" " be the bot's account name. Due to the way SASL works, you can't use\n" " any grouped nick." msgstr "" "Määrittää mitä SASL käyttäjätunnusta käytetään verkossa %s. Tämän pitäisi " "olla botin\n" " käyttäjätunnus. Johtuen SASLin toimintatavasta, mitään ryhmitettyä " "nimimerkkiä ei\n" " voida käyttää." #: src/conf.py:345 msgid "Determines what SASL password will be used on %s." msgstr "Määrittää mitä SASL salasanaa käytetään verkossa %s." #: src/conf.py:348 msgid "" "If not empty, determines the hostname of the socks proxy that\n" " will be used to connect to this network." msgstr "" "Ollessaan täytetty, määrittää sen socks-välityspalvelimen isäntämimen, jota " "käytetään tähän verkkoon yhdistettäessä." #: src/conf.py:368 msgid "Determines how urls should be formatted." msgstr "Määrittää kuinka URL:t muotoillaan." #: src/conf.py:370 msgid "" "Determines how timestamps\n" " printed for human reading should be formatted. Refer to the Python\n" " documentation for the time module to see valid formatting characters " "for\n" " time formats." msgstr "" "Määrittää, kuinka aikaleimat, jotka on tarkoitettu ihmisten luettaviksi\n" " pitäisi muotoilla. Katso Pythonin dokumentaatiota\n" " aika moduulille nähdäksesi kelvolliset muotoilumerkit \n" " ajan muodoille." #: src/conf.py:385 msgid "" "Determines whether elapsed times will be given\n" " as \"1 day, 2 hours, 3 minutes, and 15 seconds\" or as \"1d 2h 3m 15s\"." msgstr "" "Määrittää näytetäänkö kulunut aika muodossa\n" " \"1 päivä, 2 tuntia, 3 minuuttia, ja 15 sekuntia\" vai \"1d 2h 3m 15s\"." #: src/conf.py:395 msgid "" "Determines the absolute maximum length of\n" " the bot's reply -- no reply will be passed through the bot with a " "length\n" " greater than this." msgstr "" "Määrittää botin vastauksen lopullisen enimmäis pituuden\n" " -- yksikään vastaus, joka on suurempi, kuin tämä\n" " ei mene botin läpi." #: src/conf.py:400 msgid "" "Determines whether the bot will break up long\n" " messages into chunks and allow users to use the 'more' command to get " "the\n" " remaining chunks." msgstr "" "Määrittää hajottaako botti pitkät viestit\n" " paloihin ja sallii käyttäjien käyttää 'more' komentoa\n" " saadakseen jäljelläolevat palat." #: src/conf.py:405 msgid "" "Determines what the maximum number of\n" " chunks (for use with the 'more' command) will be." msgstr "" "Määrittää maksimimäärän\n" " paloja (käytettäväksi 'more' komennon) kanssa." #: src/conf.py:409 msgid "" "Determines how long individual chunks\n" " will be. If set to 0, uses our super-tweaked,\n" " get-the-most-out-of-an-individual-message default." msgstr "" "Määrittää, kuinka pitkiä yksittäiset palat tulevat\n" " olemaan. Jos tämä on asetettu arvoon 0, tämä käyttää meidän\n" " super-muokattua-saa-suurin-osa-ulos-yksittäisestä-viestistä oletusta." #: src/conf.py:414 msgid "" "Determines how many mores will be sent\n" " instantly (i.e., without the use of the more command, immediately when\n" " they are formed). Defaults to 1, which means that a more command will " "be\n" " required for all but the first chunk." msgstr "" "Määrittää, kuinka monta \"more\"a lähetetään heti \n" " (esim., ilman \"more\" command käyttöä, heti, kun ne ovat " "muodostettuja. \n" " On oletuksena 1, joka tarkoittaa, että \"more\" komentoa vaaditaan " "kaikille muille, paitsi\n" " ensinmäiselle palalle." #: src/conf.py:420 msgid "" "Determines whether the bot will send\n" " multi-message replies in a single message. This defaults to True \n" " in order to prevent the bot from flooding. If this is set to False\n" " the bot will send multi-message replies on multiple lines." msgstr "" "Määrittää lähettääkö botti monen viestin vastaukset yhdessä viestissä. Tämä " "on\n" " oletuksena \"True\", ettei botti floodaa. Jos tämä asetetaan arvoon \"False" "\", botti\n" " lähettää monen viestin vastaukset usealla rivillä." #: src/conf.py:426 msgid "" "Determines whether the bot will reply with an\n" " error message when it is addressed but not given a valid command. If " "this\n" " value is False, the bot will remain silent, as long as no other plugins\n" " override the normal behavior." msgstr "" "Määrittää vastaako botti virheilmoituksella, kun sille puhutaan, mutta " "annetaan viallinen \n" " komento. Jos tämä arvo on False, \n" " botti pysyy hiljaisena, mikäli muita lisäosia, jotka ohittavat \n" " tämän käytöksen ei ole." #: src/conf.py:433 msgid "" "Determines whether error messages that result\n" " from bugs in the bot will show a detailed error message (the uncaught\n" " exception) or a generic error message." msgstr "" "Määrittää näyttävätkö virheilmoitukset bugeista botissa tarkan " "virheilmoituksen \n" " (selittämätön poikkeus )\n" " vai tavallisen virheilmoituksen." #: src/conf.py:437 msgid "" "Determines whether the bot will send error\n" " messages to users in private. You might want to do this in order to " "keep\n" " channel traffic to minimum. This can be used in combination with\n" " supybot.reply.error.withNotice." msgstr "" "Määrittää lähettääkö botti virheilmoitukset käyttäjille yksityisesti. \n" " Voit haluta tehdä tämän, pitääksesi kanavan ruuhkan mahdollisimman " "alhaisena. \n" " Tämä voidaan yhdistää asetusarvon \n" " supybot.reply.error.withNotice kanssa." #: src/conf.py:442 msgid "" "Determines whether the bot will send error\n" " messages to users via NOTICE instead of PRIVMSG. You might want to do " "this\n" " so users can ignore NOTICEs from the bot and not have to see error\n" " messages; or you might want to use it in combination with\n" " supybot.reply.errorInPrivate so private errors don't open a query " "window\n" " in most IRC clients." msgstr "" "Määrittää lähettääkö botti virheilmoitukset käyttäjille käyttäen \n" " NOTICEa PRIVMSG:een sijaan. Voit tahtoa tehdä tämän, jotta käyttäjät " "voivat \n" " jättää NOTICEt botilta huomioimatta ja heidän ei tarvitse nähdä " "virheilmoituksia; \n" " tai voit haluta yhdistää tämän asetusarvon\n" " supybot.reply.errorInPrivate kanssa, jotta virheilmoitukset eivät avaa " "yksityiskeskustelu ikkunaa \n" " enimmissä IRC-asiakasohjelmissa." #: src/conf.py:449 msgid "" "Determines whether the bot will send an error\n" " message to users who attempt to call a command for which they do not " "have\n" " the necessary capability. You may wish to make this True if you don't " "want\n" " users to understand the underlying security system preventing them from\n" " running certain commands." msgstr "" "Määrittää lähettääkö botti virheilmoituksen käyttäjille, jotka \n" " yrittävät kutsua komennon, johon heillä ei ole valtuuksia. \n" " Tämä voidaan tahtoa asettaa arvoon True jos käyttäjien ei tahdota \n" " ymmärtävän piilossa olevaa turvallisuusjärjestelmää, joka estää heitä " "suorittamasta \n" " tiettyjä komentoja." #: src/conf.py:456 msgid "" "Determines whether the bot will reply\n" " privately when replying in a channel, rather than replying to the " "whole\n" " channel." msgstr "" "Määrittää vastaako botti yksityisesti, kun vastaa \n" " kanavalla mielummin, kuin vastaa koko \n" " kanavalle." #: src/conf.py:461 msgid "" "Determines whether the bot will reply with a\n" " notice when replying in a channel, rather than replying with a privmsg " "as\n" " normal." msgstr "" "Määrittää vastaako botti huomautuksella, kun se vastaa kanavalla\n" " mielummin kuin PRIVMSG:llä, jolla se vastaa \n" " tavallisesti." #: src/conf.py:467 msgid "" "Determines whether the bot will reply with a\n" " notice when it is sending a private message, in order not to open a /" "query\n" " window in clients. This can be overridden by individual users via the " "user\n" " configuration variable reply.withNoticeWhenPrivate." msgstr "" "Määrittää vastaako botti huomautuksella, kun se lähettää yksityisviestejä\n" " välttääkseen /query ikkunan avaamista asiakasohjelmissa. \n" " Yksittäiset käyttäjät voivat ohittaa tämän asetusarvolla \n" " reply.withNoticeWhenPrivate." #: src/conf.py:473 #, fuzzy msgid "" "Determines whether the bot will always prefix\n" " the user's nick to its reply to that user's command." msgstr "" "Määrittää aloittaako botti rivin sen käyttäjän nimimerkillä, joka antoi \n" " komennon." #: src/conf.py:477 msgid "" "Determines whether the bot should attempt to\n" " reply to all messages even if they don't address it (either via its " "nick\n" " or a prefix character). If you set this to True, you almost certainly " "want\n" " to set supybot.reply.whenNotCommand to False." msgstr "" "Määrittää pitäisikö botin yrittää vastata kaikkiin viesteihin, vaikka ne " "eivät\n" " olisi tarkoitettuja sille (joko nimimerkillä tai\n" " sen etuliitemerkillä. Jos tämä asetetaan arvoon True,\n" " supybot.reply.whenNotCommand tahdotaan asettaa Falseksi." #: src/conf.py:483 msgid "" "Determines whether the bot will allow you to\n" " send channel-related commands outside of that channel. Sometimes " "people\n" " find it confusing if a channel-related command (like Filter.outfilter)\n" " changes the behavior of the channel but was sent outside the channel\n" " itself." msgstr "" "Määrittää salliiko botti kanavaan liittyvien komentojen lähettämisen\n" " kyseisen kanavan ulkopuolella. Joskus ihmisten mielestä on hämmentävää, " "mikäli\n" " kanavaan liittyvä komento (kuten Filter.outfilter), muuttaa botin\n" " käyttäytymistä kanavalla, mutta komento lähetettiin kanavan itsensä \n" " ulkopuolella." #: src/conf.py:490 msgid "" "Determines whether the bot will unidentify\n" " someone when that person changes their nick. Setting this to True\n" " will cause the bot to track such changes. It defaults to False for a\n" " little greater security." msgstr "" "Määrittää kirjaako botti ulos käyttäjän, mikäli tuo käyttäjä vaihtaa " "nimimerkkiään.\n" " Tämän asettaminen arvoon True asettaa botin seuraamaan sellaisia " "mmuutoksia. Se\n" " on oletuksena False hiukan paremman turvallisuuden vuoksi." #: src/conf.py:496 msgid "" "Determines whether the bot will always join a\n" " channel when it's invited. If this value is False, the bot will only " "join\n" " a channel if the user inviting it has the 'admin' capability (or if " "it's\n" " explicitly told to join the channel using the Admin.join command)." msgstr "" "Määrittää liittyykö botti kanavalle aina kutsuttaessa.\n" " Jos tämä on False, botti liittyy kanavalle vain, mikäli kutsuvalla " "käyttäjällä on 'admin' valtuus (tai sen\n" " on käsketty liittyä kanavalle käyttäen komentoa Admin.join)." #: src/conf.py:502 msgid "" "Supybot normally replies with the full help\n" " whenever a user misuses a command. If this value is set to True, the " "bot\n" " will only reply with the syntax of the command (the first line of the\n" " help) rather than the full help." msgstr "" "Supybot vastaa tavallisesti täydellä ohjeteksillä, kun käyttäjä " "väärinkäyttää komentoa. Mikäli tämä on asetettu arvoon \"True\", botti " "vastaa vain komennon syntaksilla (ensinmäinen rivi ohjetekstiä) ennemmin, " "kuin täydellä ohjetekstillä." #: src/conf.py:516 msgid "" "Determines what prefix characters the bot will\n" " reply to. A prefix character is a single character that the bot will " "use\n" " to determine what messages are addressed to it; when there are no " "prefix\n" " characters set, it just uses its nick. Each character in this string " "is\n" " interpreted individually; you can have multiple prefix chars\n" " simultaneously, and if any one of them is used as a prefix the bot will\n" " assume it is being addressed." msgstr "" "Määrittää mihin etuliitemerkkeihin botti vastaa.\n" " Etuliitemerkki on yksi merkki, jota botti käyttää määrittääkseen ovatko " "viestit osoitettuja sille; kun etuliitemerkkiä ei ole asetettu, se käyttää " "vain nimimerkkiään. Jokainen merkki tässä merkkiketjussa tulkitaan erikseen; " "etuliitemerkkijä voidaan käyttää monia samanaikaisesti, ja jos yhtäkään " "niistä käyetään etuliitemerkkinä, botti olettaa, että viesti on osoitettu " "sille." #: src/conf.py:525 msgid "" "Determines what strings the\n" " bot will reply to when they are at the beginning of the message. " "Whereas\n" " prefix.chars can only be one character (although there can be many of\n" " them), this variable is a space-separated list of strings, so you can\n" " set something like '@@ ??' and the bot will reply when a message is\n" " prefixed by either @@ or ??." msgstr "" "Määrittää mihin merkkiketjuihin botti vastaa, kun ne ovat rivin alussa.\n" "Kun prefix.chars voi olla vain yhden merkin pituinen (vaikka niitä voikin " "olla \n" "monia), tämä asetusarvo on välilyönneillä eroiteltu lista, joten voit " "asettaa sen \n" "joksikin, kuten '@@ ??' ja botti vastaa, kun viestissä on etuliiteenä joko " "@@ tai ??." #: src/conf.py:532 msgid "" "Determines whether the bot will reply when\n" " people address it by its nick, rather than with a prefix character." msgstr "" "Määrittää vastaako botti, kun ihmiset osoittavat sitä \n" " nimimerkillä, mielummin kuin aloitusmerkillä." #: src/conf.py:535 msgid "" "Determines whether the bot will reply when\n" " people address it by its nick at the end of the message, rather than at\n" " the beginning." msgstr "" "Määrittää vastaako botti, kun \n" " ihmiset osoittavat viestinsä sille, kun sen nimimerkki on lopussa, " "eikä \n" " alussa." #: src/conf.py:539 msgid "" "Determines what extra nicks\n" " the bot will always respond to when addressed by, even if its current " "nick\n" " is something else." msgstr "" "Määrittää mille nimimerkeille osoitetuille viesteille botti \n" " vastaa aina, jopa jos sen nykyinen nimimerkki on jokin \n" " muu." #: src/conf.py:549 msgid "The operation succeeded." msgstr "Tehtävä suoritettu onnistuneesti." #: src/conf.py:550 msgid "" "Determines what message the bot replies with when a command succeeded.\n" " If this configuration variable is empty, no success message will be\n" " sent." msgstr "" "Määrittää millä viestillä botti vastaa, kun komento on onnistunut.\n" " Jos tämä asetusarvo on tyhjä, onnistumisviestejä ei lähetetä." #: src/conf.py:555 msgid "" "An error has occurred and has been logged.\n" " Please contact this bot's administrator for more information." msgstr "" "Virhe on tapahtunut ja tallennettu lokiin.\n" " Ole hyvä ja ota yhteyttä tämän botin ylläpitäjään saadaksesi lisätietoja." # Google Kääntäjän mukaan "ambiguos" tarkoittaa epäselvää, mutta miksi botti haluaisi olla epäselvä? #: src/conf.py:556 msgid "" "\n" " Determines what error message the bot gives when it wants to be\n" " ambiguous." msgstr "" "\n" " Määrittää minkä virheilmoituksen botti antaa tahtoessaan olla epäselvä." #: src/conf.py:561 msgid "" "An error has occurred and has been logged.\n" " Check the logs for more information." msgstr "" "Virhe on tapahtunut ja tallennettu lokiin. Lisätietoja saadaan lokeista." # Miksi botti tahtoisi olevansa epäselvä? Google Kääntäjä... #: src/conf.py:562 msgid "" "Determines what error\n" " message the bot gives to the owner when it wants to be ambiguous." msgstr "" "Määrittää millaisen virheilmoituksen botti antaa omistajalle ollessaan " "epäselvä." # Pitäisikö "hostmask" kääntää "isäntänaamioksi" vai pitää kuten se on? #: src/conf.py:566 msgid "" "Your hostmask doesn't match or your password\n" " is wrong." msgstr "Hostmask ei täsmää tai salasana on väärä." #: src/conf.py:567 msgid "" "Determines what message the bot replies with when\n" " someone tries to use a command that requires being identified or having " "a\n" " password and neither credential is correct." msgstr "" "Määrittää millä viestillä botti vastaa, kun joku yrittää antaa komennon, " "joka vaatii tunnistautumista, ja kumpikaan vaatimus ei ole täytetty." #: src/conf.py:573 msgid "" "I can't find %s in my user\n" " database. If you didn't give a user name, then I might not know what " "your\n" " user is, and you'll need to identify before this command might work." msgstr "" "En voi löytää käyttäjää %s käyttäjätietokannastani. Mikäli käyttäjänimeä ei " "annettu,\n" " en ehkä tiedä kuka etsimäsi käyttäjä on, ja sinun täytyy tunnistautua ennen " "kuin tämä\n" " komento saattaa toimia." #: src/conf.py:576 msgid "" "Determines what error message the bot replies with when someone tries\n" " to accessing some information on a user the bot doesn't know about." msgstr "" "Määrittää millä virheilmoituksella botti vastaa, kun joku yrittää päästä " "tietoihin käyttäjästä, jota ei ole olemassakaan." #: src/conf.py:580 msgid "" "You must be registered to use this command.\n" " If you are already registered, you must either identify (using the " "identify\n" " command) or add a hostmask matching your current hostmask (using the\n" " \"hostmask add\" command)." msgstr "" "Tätä komentoa käyttääkseen on oltava rekisteröitynt.\n" " Mikäli olet jo rekisteröitynyt, tunnistaudu (käyttämällä identify komentoa) " "tai lisää\n" " hostmask, joka täsmää nykyiseen hostmaskiisi (käyttämällä \"hostmask add\" " "komentoa)." #: src/conf.py:583 msgid "" "Determines what error message the bot\n" " replies with when someone tries to do something that requires them to " "be\n" " registered but they're not currently recognized." msgstr "" "Määrittää millä virheilmoituksella botti vastaa, kun joku yrittää käyttää " "komentoa,\n" " joka vaatii hänen olevan rekisteröitynyt, mutta häntä ei ole tällä " "hetkellä\n" " tunnistettu." # Tämän pitäisi mahdollisesti olla passiivissa, mutta en ole varma miten tämä pitäisi olla passiivissa. Merkitsen sen epäselväksi. #: src/conf.py:588 #, fuzzy msgid "" "You don't have the %s capability. If you\n" " think that you should have this capability, be sure that you are " "identified\n" " before trying again. The 'whoami' command can tell you if you're\n" " identified." msgstr "" "Sinulla ei ole valtuutta %s. Mikäli uskot, että sinulla pitäisi olla tämä " "valtuus,\n" " varmista että olet tunnistautunut ennen kuin yrität uudelleen. Komento " "'whoami' voi\n" " kertoa sinulle oletko tunnistautunut." #: src/conf.py:591 msgid "" "Determines what error message is given when the bot\n" " is telling someone they aren't cool enough to use the command they tried " "to\n" " use." msgstr "" "Määrittää mikä virheilmoitus annetaan, kun botti kertoo jollekin, ettei hän " "ole\n" " riittävän kuuli käyttääkseen komentoa, jota hän yritti käyttää." #: src/conf.py:596 msgid "" "You're missing some capability you need.\n" " This could be because you actually possess the anti-capability for the\n" " capability that's required of you, or because the channel provides that\n" " anti-capability by default, or because the global capabilities include\n" " that anti-capability. Or, it could be because the channel or\n" " supybot.capabilities.default is set to False, meaning that no commands " "are\n" " allowed unless explicitly in your capabilities. Either way, you can't " "do\n" " what you want to do." msgstr "" "Jokin valtuus, jota tarvitaan puuttuu.\n" " Tämä voi johtua siitä, että vaaditulla valtuudella on antivaltuus, tai " "koska kanava\n" " tarjoaa antivaltuuden oletuksena. Tai se voi johtua siitä, että kanava tai\n" " supybot.capabilities.default on asetettu arvoon False, tarkoittaen ettei " "komentoja\n" " sallita, ellei niitä ole erikseen sallittu. Miten vain, et voi tehdä sitä, " "mitä tahdot\n" " tehdä." #: src/conf.py:604 msgid "" "Determines what generic error message is given when the bot is telling\n" " someone that they aren't cool enough to use the command they tried to " "use,\n" " and the author of the code calling errorNoCapability didn't provide an\n" " explicit capability for whatever reason." msgstr "" "Määrittää mikä geneerinen virheilmoitus annetaan, kun botti kertoo jollekin, " "ettei\n" " hän ole tarpeeksi kuuli käyttääkseen komentoa, jota yritti käyttää ja " "koodin, joka\n" " kutsuu errorNoCapability koodin ei tarjonnut tarkkaa valtuutta ihansama " "mistä syystä." #: src/conf.py:610 msgid "" "That operation cannot be done in a\n" " channel." msgstr "Tätä tehtävää ei voida suorittaa kanavalla." #: src/conf.py:611 msgid "" "Determines what error messages the bot sends to people\n" " who try to do things in a channel that really should be done in\n" " private." msgstr "" "Määrittää virheilmoituksen, jonka botti lähettää ihmisille, ketkä yrittävät " "tehdä\n" " asioita, jotka todella pitäisi tehdä yksityisesti, kanavalla." #: src/conf.py:616 msgid "" "This may be a bug. If you think it is,\n" " please file a bug report at\n" " ." msgstr "" "Tämä voi olla bugi. Mikäli uskotaan niin, raportoidaan bugi osoitteessa\n" " . Bugiraportit kirjoitetaan " "englanniksi." #: src/conf.py:619 msgid "" "Determines what message the bot sends when it thinks you've\n" " encountered a bug that the developers don't know about." msgstr "" "Määrittää minkä viestin botti lähettää uskoessaan, että on kohdattu bugi, " "josta\n" " kehittäjät eivät ole tietoisia." #: src/conf.py:626 msgid "" "A floating point number of seconds to throttle\n" " snarfed URLs, in order to prevent loops between two bots snarfing the " "same\n" " URLs and having the snarfed URL in the output of the snarf message." msgstr "" "Liukuluku numero sekunteja, joilla jarrutetaan kaapattuja URL ositteita, " "jotta estetään\n" " toistot kahden botin välillä, jotka sisältävät saman URL-osoitteen " "ulostulevassa\n" " kaappausviestissä." #: src/conf.py:631 msgid "" "Determines the number of seconds\n" " between running the upkeep function that flushes (commits) open " "databases,\n" " collects garbage, and records some useful statistics at the debugging\n" " level." msgstr "" "Määrittää sekuntimäärän, joka on ylläpito toiminnon, joka tallentaa avoimet " "tietokannat\n" " , kerää jätteet ja tallentaa hyödyllisiä tilastotietoja debuggaus tasolle, " "välissä." #: src/conf.py:637 msgid "" "Determines whether the bot will periodically\n" " flush data and configuration files to disk. Generally, the only time\n" " you'll want to set this to False is when you want to modify those\n" " configuration files by hand and don't want the bot to flush its current\n" " version over your modifications. Do note that if you change this to " "False\n" " inside the bot, your changes won't be flushed. To make this change\n" " permanent, you must edit the registry yourself." msgstr "" "Määrittää kirjoittaako botti säännöllisesti asetustiedostot levylle. Yleensä " "ainut\n" " tapaus, jossa tämä tahdotaan asettaa arvoon False on kun kyseisiä " "asetustiedostoja\n" " tahdotaan muokata käsin, eikä botin tahdota tallentavan nykyistä versiotaan " "omien\n" " muokkaustesi päälle. Huomaa, että mikäli vaihdat tämän arvoon False botin " "sisällä,\n" " muutoksiasi ei tallenneta. Tehdäksesi tämän muutoksen pysyväksi, rekisteriä " "täytyy\n" " muokata itse." # Google Kääntäjä ei tiedä mitä "tokenized" tarkoittaa. En tiedä minäkään. #: src/conf.py:662 #, fuzzy msgid "" "Determines what characters are valid for quoting\n" " arguments to commands in order to prevent them from being tokenized.\n" " " msgstr "" "Määrittää mitkä merkit ovat kelvollisia parametrien lainaamiseen, jotta " "estetään niitä\n" " tulemasta tokenisoiduiksi " #: src/conf.py:669 msgid "" "Determines whether the bot will allow nested\n" " commands, which rule. You definitely should keep this on." msgstr "" "Määrittää salliiko botti sisäkkäisten komentojen käytön, jotka ovat " "parhaita. Tämä\n" " täytyy varmasti pitää käytössä." #: src/conf.py:672 msgid "" "Determines what the maximum number of\n" " nested commands will be; users will receive an error if they attempt\n" " commands more nested than this." msgstr "" "Määrittää mikä on enimmäinen sallittu määrä sisäkkäisiä komentoja; " "käyttäjät\n" " vastaanottavat virheilmoituksen yrittäessään käyttää useampia sisäkkäisiä " "komentoja,\n" " kuin tämä arvo sallii." #: src/conf.py:680 #, fuzzy msgid "" "Supybot allows you to specify what brackets\n" " are used for your nested commands. Valid sets of brackets include\n" " [], <>, {}, and (). [] has strong historical motivation, but <> or\n" " () might be slightly superior because they cannot occur in a nick.\n" " If this string is empty, nested commands will not be allowed in this\n" " channel." msgstr "" "Supybot sallii sinun määrittää millaisia sulkuja käytetään sisäkkäisille " "komennoille.\n" " Kelvolliset sulkusarjat ovat [], <> ja {} (). []:lla on vahva\n" " historiallinen motivaatio mutta <> tai () voi olla huomattavasti parempi, " "koska se\n" " ei voi olla nimimerkissä. Mikäli tämä asetusarvo on tyhjä, sisäkkäiset " "komennot\n" " eivät ole sallittuja kanavalla." #: src/conf.py:687 msgid "" "Supybot allows nested commands. Enabling this\n" " option will allow nested commands with a syntax similar to UNIX pipes, " "for\n" " example: 'bot: foo | bar'." msgstr "" "Supybot sallii sisäkkäiset komennot. Tämän asetuksen käyttöönottaminen " "sallii\n" " sisäkkäisten komentojen käyttämisen samanlaisella syntaksilla, kuin UNIX " "putket,\n" " esimerkiksi 'botti: foo | bar'." #: src/conf.py:692 msgid "" "Determines what commands have default\n" " plugins set, and which plugins are set to be the default for each of " "those\n" " commands." msgstr "" "Määrittää millä komennoilla on oletuslisäosa määritettynä, ja mitkä lisäosat " "on\n" " määritetty olemann oletuksia kaikille noille komennoille." #: src/conf.py:698 msgid "" "Determines what plugins automatically get precedence over all\n" " other plugins when selecting a default plugin for a command. By\n" " default, this includes the standard loaded plugins. You probably\n" " shouldn't change this if you don't know what you're doing; if you " "do\n" " know what you're doing, then also know that this set is\n" " case-sensitive." msgstr "" "Määrittää mitkä lisäosat saavat automaattisesti suosion yli käikkien muiden " "lisäosien,\n" " kun valitaan oletuslisäosaa komennolle. Oletuksena tämä sisältää kaikki " "perus lisäosat\n" ", jotka ovat käytössä. Tätä ei pitäisi muuttaa, ellei olla täysin varmoja " "mitä tehdään;\n" " jos tiedetään mitä ollaan tekemisessä, tiedetään myös, että tässä " "asetusarvossa\n" " kirjainkoolla on merkitystä." #: src/conf.py:713 #, fuzzy msgid "" "Determines the interval used for\n" " the history storage." msgstr "Määrittää historian tallennukseen käytettävän aikavälin." #: src/conf.py:716 msgid "" "Determines whether the bot will defend itself\n" " against command-flooding." msgstr "Määrittää puolustaako botti itseään komento tulvimista vastaan." #: src/conf.py:719 #, fuzzy msgid "" "Determines how many commands users are\n" " allowed per minute. If a user sends more than this many commands in " "any\n" " 60 second period, they will be ignored for\n" " supybot.abuse.flood.command.punishment seconds." msgstr "" "Määrittää kuinka monta komentoa käyttäjien on sallittua suorittaa " "minuutissa. Jos\n" " käyttäjä lähettää enemmän, kuin tämän verran komentoja missä tahansa 60 " "sekuntin\n" " jaksossa, hänet jätetään huomioitta sekunteiksi, jotka on määritetty " "asetusarvossa\n" " supybot.abuse.flood.command.punishment." #: src/conf.py:724 msgid "" "Determines how many seconds the bot\n" " will ignore users who flood it with commands." msgstr "" "Määrittää kuinka moneksi sekuntiksi botti jättää käyttäjät, jotka tulvivat " "sitä\n" " komennoilla, botti jättää huomioimatta." #: src/conf.py:728 msgid "" "Determines whether the bot will defend itself\n" " against invalid command-flooding." msgstr "" "Määrittää puolustaako botti itseään \n" " viallisten komentojen tulvaa vastaan." #: src/conf.py:731 #, fuzzy msgid "" "Determines how many invalid commands users\n" " are allowed per minute. If a user sends more than this many invalid\n" " commands in any 60 second period, they will be ignored for\n" " supybot.abuse.flood.command.invalid.punishment seconds. Typically, " "this\n" " value is lower than supybot.abuse.flood.command.maximum, since it's far\n" " less likely (and far more annoying) for users to flood with invalid\n" " commands than for them to flood with valid commands." msgstr "" "Määrittää kuinka monta virheellistä komentoa käyttäjät voivat antaa " "minuutissa. Jos\n" " käyttäjä lähettää näin monta komentoa millä tahansa minuutin jaksolla, " "hänet jätetään\n" " huomioitta sekunti määräksi, joka on määritetty asetusarvossa\n" " supybot.abuse.flood.command.invalid.punishment. Tyypillisesti tämä arvo on " "alhaisempi,\n" " kuin supybot.abuse.flood.command.maximum, koska on erittäin vähemmän " "todennäköistä (ja\n" " paljon ärsyttävämpää) käyttäjille tulvia virheellisillä komennoilla, kuin " "heille\n" " tulvia kelvollisilla komennoilla." #: src/conf.py:739 msgid "" "Determines how many seconds the bot\n" " will ignore users who flood it with invalid commands. Typically, this\n" " value is higher than supybot.abuse.flood.command.punishment, since it's " "far\n" " less likely (and far more annoying) for users to flood with invalid\n" " commands than for them to flood with valid commands." msgstr "" "Määrittää, kuinka moneksi sekuntiksi botti jättää huomioitta käyttäjät, " "jotka tulvivat\n" " sitä virheellisillä komennoilla. Tyypillisesti tämä arvo on korkeampi, " "kuin\n" " supybot.abuse.flood.punishment, koska on paljon vähemmän todennäköistä (ja " "paljon\n" " ärsyttävämpää) käyttäjille tulvia virheellisillä komennoilla, kuin heille " "tulvia\n" " kelvollisilla komennoilla." #: src/conf.py:745 msgid "" "Determines whether the bot will notify people\n" " that they're being ignored for invalid command flooding." msgstr "" "Määrittää huomauttaako bitti ihmisiä siitä, että heidän olemassa olostaan ei " "välitetä, jos \n" " heidän olemassa olostaan ei välitetä viallisten komentojen tulvan takia." # Mitäköhän tämä tarkoittaa? #: src/conf.py:754 #, fuzzy msgid "" "Determines the default length of time a\n" " driver should block waiting for input." msgstr "" "Määrittää, oletus ajanjakson, joka ajurin pitäisi estää odottaen sisääntuloa." #: src/conf.py:761 msgid "" "Determines what driver module the \n" " bot will use. The default is Socket which is simple and stable \n" " and supports SSL. Twisted doesn't work if the IRC server which \n" " you are connecting to has IPv6 (most of them do)." msgstr "" "Määrittää botin käyttämän ajurimoduulin. Oletus on Socket, joka on\n" " yksinkertainen ja vakaa ja tukee SSL:ää. Twisted ei toimi mikäli " "yhdistettävällä\n" " IRC-palvelimella on IPv6 (useimmilla niistä on)." #: src/conf.py:767 msgid "" "Determines the maximum time the bot will\n" " wait before attempting to reconnect to an IRC server. The bot may, of\n" " course, reconnect earlier if possible." msgstr "" "Määrittää maksimi ajan, jonka botti odottaa, ennen kuin yrittää yhdistää " "uudelleen\n" " IRC palvelimeen. Botti voi tietysti yhdistää palvelimeen uudelleen " "aikaisemminkin,\n" " mikäli se on mahdollista." #: src/conf.py:816 msgid "" "Determines what directory configuration data is\n" " put into." msgstr "Määrittää mihin hakemistoon asetustiedostot laitetaan." #: src/conf.py:819 msgid "Determines what directory data is put into." msgstr "Määrittää mihin hakemistoon data laitetaan." #: src/conf.py:821 msgid "" "Determines what directory backup data is put\n" " into. Set it to /dev/null to disable backup (it is a special value,\n" " so it also works on Windows and systems without /dev/null)." msgstr "" "Määrittää mihin hakemistoon varmuuskopio tiedot laitetaan. Tämä asetetaan " "arvoon\n" " /dev/null, mikäli tahdotaan poistaa varmuuskopioiden ottaminen käytöstä (se " "on\n" " erityisarvo, joten se toimi myös Windowsilla ja käyttöjärjestelmillä,\n" " joilla ei ole hakemistoa /dev/null)." #: src/conf.py:825 msgid "" "Determines what directory temporary files\n" " are put into." msgstr "Määrittää mihin hakemistoon väliaikaistiedostot laitetaan." #: src/conf.py:828 msgid "" "Determines what directory files of the\n" " web server (templates, custom images, ...) are put into." msgstr "" "Määrittää minne verkkopalvelimen hakemistotiedostot (pohjat, mukautetut " "kuvat\n" " jne.) laitetaan." #: src/conf.py:835 msgid "" "Determines what directories\n" " the bot will look for plugins in. Accepts a comma-separated list of\n" " strings.\n" " This means that to add another directory, you can nest the former value " "and\n" " add a new one. E.g. you can say: bot: 'config supybot.directories." "plugins\n" " [config supybot.directories.plugins], newPluginDirectory'." msgstr "" "Määrittää mistä hakemistoista botti etsii lisäosia. Hyväksyy pilkuilla " "eristetyn listan\n" " merkkiketjuista. Tämä tarkoittaa, että voit lisätä toisen hakemiston. Voit " "sisäistää\n" " aiemman arvon ja lisätä uuden. Esimerkiksi voit sanoa: botti:\n" " 'config supybot.directories.plugins [config supybot.directories.plugins],\n" " UusiLisäosaHakemisto." #: src/conf.py:843 msgid "" "Determines what plugins will\n" " be loaded." msgstr "Määrittää mitkä lisäosat ladataan." #: src/conf.py:846 msgid "" "Determines whether the bot will always load\n" " important plugins (Admin, Channel, Config, Misc, Owner, and User)\n" " regardless of what their configured state is. Generally, if these " "plugins\n" " are configured not to load, you didn't do it on purpose, and you still\n" " want them to load. Users who don't want to load these plugins are " "smart\n" " enough to change the value of this variable appropriately :)" msgstr "" "Määrittää lataako botti aina tärkeäy lisäosat (Admin, Channel, Config, Misc, " "Owner ja\n" " User) riippumatta, mikä niiden määritetty tila on. Yleensä, mikäli nämä " "lisäosat on\n" " määritetty olemaan latautumatta, sitä ei tahdottu tehdä tarkoituksella ja " "niiden\n" " tahdotaan silti latautuvan. Käyttäjät, jotka eivät tahdo ladata näitä " "lisäosia ovat\n" " tarpeeksi teräviä vaihtaakseen tätä asetusarvoa sopivasti :)" #: src/conf.py:873 msgid "" "Determines what databases are available for use. If this\n" " value is not configured (that is, if its value is empty) then sane " "defaults\n" " will be provided." msgstr "" "Määrittää mitkä tietokannat ovat käytettävissä. Mikäli tätä arvoa ei ole " "määritetty,\n" " (se tarkottaa, että sen arvo on tyhjä) niin järkevät oletukset tarjotaan." #: src/conf.py:879 msgid "" "Determines what filename will be used\n" " for the users database. This file will go into the directory specified " "by\n" " the supybot.directories.conf variable." msgstr "" "Määrittää mitä nimeä käytetään tiedostolle, jota käytetään " "käyttäjätietokantana. Tämä\n" " tiedosto menee hakemistoon, jonka määrittää asetusarvo supybot.directories." "conf." #: src/conf.py:883 msgid "" "Determines how long it takes identification to\n" " time out. If the value is less than or equal to zero, identification " "never\n" " times out." msgstr "" "Määrittää kuinka pitkän ajan tunnistautuminen on voimassa. Mikäli tänä arvo " "on vähemmän\n" " tai yhtäsuuri kuin nolla, tunnistautuminen ei ikinä vanhene." #: src/conf.py:887 msgid "" "Determines whether the bot will allow users to\n" " unregister their users. This can wreak havoc with already-existing\n" " databases, so by default we don't allow it. Enable this at your own " "risk.\n" " (Do also note that this does not prevent the owner of the bot from " "using\n" " the unregister command.)\n" " " msgstr "" "Määrittää salliiko botti käyttäjien poistaa tunnuksensa botista. Tämä voi " "aiheuttaa\n" " kaaosta tietokannoilla, jotka ovat jo olemassa, joten oletuksena me emme " "salli sitä.\n" " Tämä asetusarvo otetaan käyttöön omalla vastuulla. (Huomaa, ettei tämä " "asetusarvo estä\n" " botin omistajaa käyttänästä \"unregister\" komentoa.)" #: src/conf.py:896 msgid "" "Determines what filename will be used\n" " for the ignores database. This file will go into the directory " "specified\n" " by the supybot.directories.conf variable." msgstr "" "Määrittää nimen tiedostolle, jota käytetään huomioitta jättämisen seurannan\n" " tietokantana. Tämä tiedosto laitetaan hakemistoon, jonka määrittää " "asetusarvo\n" " supybot.directories.conf." #: src/conf.py:902 msgid "" "Determines what filename will be used\n" " for the channels database. This file will go into the directory " "specified\n" " by the supybot.directories.conf variable." msgstr "" "Määrittää nimen tiedostolle, jota käytetään kanavatietokantana. Tämä " "tiedosto laitetaan\n" " hakemistoon, joka määritetään asetusarvolla supybot.directories.conf." #: src/conf.py:932 msgid "" "Determines whether database-based plugins that\n" " can be channel-specific will be so. This can be overridden by " "individual\n" " channels. Do note that the bot needs to be restarted immediately after\n" " changing this variable or your db plugins may not work for your " "channel;\n" " also note that you may wish to set\n" " supybot.databases.plugins.channelSpecific.link appropriately if you " "wish\n" " to share a certain channel's databases globally." msgstr "" "Määrittää ovatko tietokanta-pohjaiset lisäosat, jotka voivat olla " "kanavakohtaisia,\n" " kanavakohtaisia. Yksittäiset kanavat voivat ohittaa tämän. Huomaa, että " "botti täytyy\n" " käynnistää uudelleen välittömästi, kun tätä asetusarvoa on muutettu tai " "muuten\n" " tietokanta-pohjaiset lisäosat eivät välttämättä toimi kanavalla; huomaa " "myös, että\n" " supybot.databases-plugins.channelSpecific.link voidaan myös tahtoa asettaa " "sopivaksi,\n" " mikäli tietyn kanavan tietokannat, tahdotaan jakaa globaalisti." #: src/conf.py:940 msgid "" "Determines what channel global\n" " (non-channel-specific) databases will be considered a part of. This is\n" " helpful if you've been running channel-specific for awhile and want to " "turn\n" " the databases for your primary channel into global databases. If\n" " supybot.databases.plugins.channelSpecific.link.allow prevents linking, " "the\n" " current channel will be used. Do note that the bot needs to be " "restarted\n" " immediately after changing this variable or your db plugins may not " "work\n" " for your channel." msgstr "" "Määrittää mihin kanavaan kuuluviksi globaalit (eivät kanavakohtaiset) " "tietokannat\n" " määritetään kuuluviksi. Tämä on avuliasta, mikäli kanavakohtaisia " "tietokantoja on\n" " käytetty pitkän aikaa ja ensisijaisen kanavan tietokannat tahdotaan " "muuttaa\n" " globaaleiksi tietokannoiksi. Jos supybot.databases.plugins.channelSpecific." "link.allow\n" " estää linkittämisen, nykyistä kanavaa käytetään. Huomaa, että botti täytyy " "käynnistää\n" " uudelleen välittömästi tämän asetusarvon muuttamisen jälkeen tai tietokanta " "lisäosat\n" " eivät välttämättä toimi kanavalla." #: src/conf.py:949 msgid "" "Determines whether another channel's global\n" " (non-channel-specific) databases will be allowed to link to this " "channel's\n" " databases. Do note that the bot needs to be restarted immediately " "after\n" " changing this variable or your db plugins may not work for your " "channel.\n" " " msgstr "" "Määrittää onko toisen kanavan globaaleiden (ei kanavakohtaisten) " "tietokantojen\n" " sallittua linkittyä tämän kanavan tietokantoihin. Huomaa, että botti täytyy " "käynnistää\n" " uudelleen välittömästi tämän asetusarvon muuttamisen jälkeen tai tietokanta " "lisäosat\n" " eivät välttämättä toimi kanavalla." #: src/conf.py:966 msgid "" "Determines\n" " whether CDB databases will be allowed as a database implementation." msgstr "" "Määrittää sallitaanko CDB tietokantojen käyttö tietokantaimplementaationa." #: src/conf.py:969 msgid "" "Determines how often CDB databases will have\n" " their modifications flushed to disk. When the number of modified " "records\n" " is greater than this fraction of the total number of records, the " "database\n" " will be entirely flushed to disk." msgstr "" "Määrittää, kuinka usein DDB lisäosat tallentavat muutoksensa levylle. Kun " "muokattujen\n" " tallenteiden määrä on suurempi kuin tämä murto-osa tallenteiden " "yhteismäärästä,\n" " tietokanta tallennetaan levylle kokonaan." #: src/conf.py:1058 msgid "" "Determines what will be used as the\n" " default banmask style." msgstr "Määrittää mitä käytetään oletus porttikiellon anto tyylinä." #: src/conf.py:1062 #, fuzzy msgid "" "Determines whether the bot will strictly\n" " follow the RFC; currently this only affects what strings are\n" " considered to be nicks. If you're using a server or a network that\n" " requires you to message a nick such as services@this.network.server\n" " then you you should set this to False." msgstr "" "Määrittää noudattaako botti tiukasti RFC standardeja; tällä hetkellä tämä " "vaikuttaa\n" " siihen mitten merkkiketjujen uskotaan olevan nimimerkkejä. Jos käytät " "palvelinta, tai\n" " verkkoa, joka vaatii viestien lähetyksen nimimerkille, kuten esimerkiksi\n" " services@tämän.verkon.palvelin, niin tämä pitäisi asettaa arvoon \"False\"." #: src/conf.py:1069 msgid "" "Determines what certificate file (if any) the bot\n" " will use connect with SSL sockets by default." msgstr "" "Määrittää mitä SSL-varmennetta (jos mitään) botti käyttää oletuksena\n" " yhdistäessään SSL-solmuilla." #: src/conf.py:1073 msgid "" "Determines what user modes the bot will request\n" " from the server when it first connects. Many people might choose +i; " "some\n" " networks allow +x, which indicates to the auth services on those " "networks\n" " that you should be given a fake host." msgstr "" "Määrittää mitä käyttäjätiloja botti pyytää palvelimelta, kun se muodostaa " "yhteyden.\n" " Monet ihmiset saattavat valita tilan +i; jotkut verkot sallivat tilan +x, " "joka\n" " ilmaisee tuon verkon tunnistautumispalveluille, että käyttäjälle pitäisi " "antaa\n" " väärennetty isäntä." #: src/conf.py:1079 #, fuzzy msgid "" "Determines what vhost the bot will bind to before\n" " connecting a server (IRC, HTTP, ...) via IPv4." msgstr "" "Määrittää mihin vhostiin botti sitoutuu, ennen kuin ottaa yhdeyden (IRC, " "HTTP\n" " jne.) palvelimeen käyttäen IPv4:ää." #: src/conf.py:1083 #, fuzzy msgid "" "Determines what vhost the bot will bind to before\n" " connecting a server (IRC, HTTP, ...) via IPv6." msgstr "" "Määrittää mihin vhostiin botti sitoutuu, ennen kuin ottaa yhdeyden (IRC, " "HTTP\n" " jne.) palvelimeen käyttäen IPv6:tta." #: src/conf.py:1087 msgid "" "Determines how many old messages the bot will\n" " keep around in its history. Changing this variable will not take " "effect\n" " until the bot is restarted." msgstr "" "Määrittää kuinka vanhoja viestejä botti pitää historiassaan. Tämän " "asetusarvon\n" " muuttaminen ei vaikuta ennen uudelleenkäynnistystä." #: src/conf.py:1092 #, fuzzy msgid "" "A floating point number of seconds to throttle\n" " queued messages -- that is, messages will not be sent faster than once " "per\n" " throttleTime seconds." msgstr "" "Liukuluku sekunteja, joilla jarrutetaan jonossa olevia viestejä. -- Tämä " "estää viestejä\n" " tulemasta lähetetyiksi nopeammin kuin kerran throttleTime sekunteja." #: src/conf.py:1097 msgid "" "Determines whether the bot will send PINGs to\n" " the server it's connected to in order to keep the connection alive and\n" " discover earlier when it breaks. Really, this option only exists for\n" " debugging purposes: you always should make it True unless you're " "testing\n" " some strange server issues." msgstr "" "Määrittää lähettääkö botti PINGejä palvelimelle, johon se on muodostanut " "yhdeyden\n" " pitääkseen yhdeyden elossa ja saadakseen tietää aiemmin, jos se hajoaa. " "Oikeasti, tämä\n" " asetus on olemassa vain debuggaus tarkoituksia varten: tämä pitäisi aina " "olla \"True\",\n" " mikäli et kokeile omituisia palvelinongelmia." #: src/conf.py:1104 msgid "" "Determines the number of seconds between sending\n" " pings to the server, if pings are being sent to the server." msgstr "" "Määrittää määrän sekunteja palvelimelle lähetettävien pingien välissä, jos " "pingejä\n" " lähetetään palvelimelle." #: src/conf.py:1109 msgid "" "Determines whether the bot will refuse\n" " duplicated messages to be queued for delivery to the server. This is a\n" " safety mechanism put in place to prevent plugins from sending the same\n" " message multiple times; most of the time it doesn't matter, unless " "you're\n" " doing certain kinds of plugin hacking." msgstr "" "Määrittää kieltäytyykö botti pistämästä viestien kaksoiskappaleita jonoon " "palvelimelle\n" " toimitettaviksi. Tämä on turvallisuusmekanismi, joka on asetettu estämään " "lisäosia\n" " lähettämästä samaa viestiä palvelimelle monia kertoja; yleensä sillä ei ole " "merkitystä\n" " mikäli et ole tekemässä lisäosien hakkerointia." #: src/conf.py:1117 msgid "" "Determines how many seconds must elapse between\n" " JOINs sent to the server." msgstr "" "Määrittää, kuinka monta sekuntia täytyy kulua palvelimelle lähetettyjen \n" " JOINien välissä." #: src/conf.py:1125 msgid "" "Determines how many bytes the bot will\n" " 'peek' at when looking through a URL for a doctype or title or " "something\n" " similar. It'll give up after it reads this many bytes, even if it " "hasn't\n" " found what it was looking for." msgstr "" "Määrittää kuinka monta bittiä botti \"kurkistaa\" katsoessaan URL:än läpi " "etsien doctypeä\n" " tai otsikkoa tai jotakin sinnepäin. Se luovuttaa kun on lukenut tämän " "määrän bittejä,\n" " vaikka ei löytäisikään etsimäänsä." #: src/conf.py:1131 msgid "" "Determines what proxy all HTTP requests should go\n" " through. The value should be of the form 'host:port'." msgstr "" "Määrittää minkä välityspalvelimen läpi kaikkien HTTP pyyntöjen pitäisi " "mennä. \n" " Arvon pitäisi olla muodossa 'isäntä:portti'." #: src/conf.py:1151 msgid "" "Space-separated list of IPv4 hosts the HTTP server\n" " will bind." msgstr "" "Välilyönneille eroiteltu lista IPv4-osoitteista, joita HTTP-palvelin " "kuuntelee." #: src/conf.py:1154 msgid "" "Space-separated list of IPv6 hosts the HTTP server will\n" " bind." msgstr "" "Välilyönneilla erotettu lista IPv6-osoitteista, joita HTTP-palvelin " "kuuntelee." #: src/conf.py:1157 msgid "" "Determines what port the HTTP server will\n" " bind." msgstr "" "Määrittää mihin porttiin HTTP palvelin \n" " sitoutuu." #: src/conf.py:1160 msgid "" "Determines whether the server will stay\n" " alive if no plugin is using it. This also means that the server will\n" " start even if it is not used." msgstr "" "Määrittää pysyykö palvelin elossa, vaikka yksikään \n" " lisäosa ei käytä sitä. Tämä tarkoittaa, että palvelin käynnistyy myös, " "mikäli sitä \n" " ei käytetä." #: src/conf.py:1164 #, fuzzy msgid "" "Determines the path of the file served as\n" " favicon to browsers." msgstr "Määrittää polun tiedostoon, joka annetaan faviconina selaimille." #: src/conf.py:1172 msgid "" "Determines whether the bot will ignore\n" " unidentified users by default. Of course, that'll make it\n" " particularly hard for those users to register or identify with the bot\n" " without adding their hostmasks, but that's your problem to solve." msgstr "" "Määrittää jättääkö botti tunnistautumattomat käyttäjät huomiotta.\n" " Tietysti se tekee heidän rekisteröitymisestään tai tunnistautumisestaan " "botin\n" " kanssa vaikeaksi, ellei heillä ole lisättyjä hostmaskeja, mutta se on sinun " "ongelmasi." #: src/conf.py:1179 msgid "" "A string that is the external IP of the bot. If this is the\n" " empty string, the bot will attempt to find out its IP dynamically " "(though\n" " sometimes that doesn't work, hence this variable)." msgstr "" "Merkkiketju, joka on botin ulkoinen IP osoite. Jos tämä on\n" " tyhjä merkkiketju, botti yrittää löytää IP osoiteensa dynaamisesti " "(vaikko\n" " joskus se ei toimi, siksi tämä asetusarvo)." #: src/conf.py:1193 msgid "" "Determines what the default timeout for socket\n" " objects will be. This means that *all* sockets will timeout when this " "many\n" " seconds has gone by (unless otherwise modified by the author of the " "code\n" " that uses the sockets)." msgstr "" "Määrittää mikä on oletus aikakatkaisuaika solmuobjekteille. Se takoittaa, " "että *kaikki*\n" " solmut tekevät aikakatkaisun, kun nämä sekuntit olevat kuluneet (paitsi jos " "solmuja\n" " käyttävän koodin kirjoittaja on erikseen muokannut tätä)." #: src/conf.py:1199 msgid "" "Determines what file the bot should write its PID\n" " (Process ID) to, so you can kill it more easily. If it's left unset (as " "is\n" " the default) then no PID file will be written. A restart is required " "for\n" " changes to this variable to take effect." msgstr "" "Määrittää mihin tiedostoon botin pitäisi kirjoittaa PIDinsä \n" " (Prosessi ID:n), jotta voit tappaa sen helpommin. Jos jätetty tyhjäksi " "(kuten se on \n" " oletuksena) niin silloin PID tiedostoa ei kirjoiteta. Uudelleen " "käynnistys on vaadittu\n" " tämän asetusarvon muutosten vaikutuksen aikaansaamiseksi." #: src/conf.py:1209 msgid "" "Determines whether the bot will automatically\n" " thread all commands." msgstr "" "Määrittää ketjuttaako botti kaikki\n" " komennot." #: src/conf.py:1212 msgid "" "Determines whether the bot will automatically\n" " flush all flushers *very* often. Useful for debugging when you don't " "know\n" " what's breaking or when, but think that it might be logged." msgstr "" "Määrittää kuinka usein botti tallentaa automaattisesti\n" " kaikki puskurit *erittäin* usein. Hyödyllinen bugien korjaamiseen, kun " "et tiedä\n" " mikä hajoaa ja milloin, mutta uskot, että se saattaa tallentua lokiin." #: src/httpserver.py:62 msgid "Supybot Web server index" msgstr "Supybot verkkopalvelin indeksi." #: src/httpserver.py:67 msgid "Here is a list of the plugins that have a Web interface:" msgstr "Tässä on lista lisäosista, joilla on verkko käyttöliittymä:" #: src/httpserver.py:278 msgid "" "\n" " This is a default response of the Supybot HTTP server. If you see this\n" " message, it probably means you are developing a plugin, and you have\n" " neither overriden this message or defined an handler for this query." msgstr "" "\n" "Tämä on Supybotin HTTP-palvelimen oletusvastaus. Jos näet tämän viestin, se " "luultavasti\n" " tarkoittaa, että lisäosaa kehitetään, eikä tätä viestiä ole ohitettu eikä " "tälle\n" " pyynnölle ole määritetty käsittelijää." #: src/httpserver.py:312 msgid "" "\n" " I am a pretty clever IRC bot, but I suck at serving Web pages, " "particulary\n" " if I don't know what to serve.\n" " What I'm saying is you just triggered a 404 Not Found, and I am not\n" " trained to help you in such a case." msgstr "" "\n" " Olen aika älykäs IRC botti, mutta olen huono verkkosivujen " "tarjoamisessa, erityisesti, jos en\n" " tiedä mitä minun pitäisi tarjota.\n" " Mitä minä olen kertomassa on vain, että laukaisit 404 sivua ei löydy " "virheen, ja minua ei ole koulutettu\n" " auttamaan sinua sellaisessa tapauksessa." #: src/httpserver.py:333 msgid "Request not handled." msgstr "Pyyntöä ei hyväksytty." #: src/httpserver.py:337 msgid "No plugins available." msgstr "Ei lisäosia saatavilla." #: src/httpserver.py:354 src/httpserver.py:371 msgid "Request not handled" msgstr "Pyyntöä ei käsitelty." #: src/httpserver.py:396 #, fuzzy msgid "No favicon set." msgstr "Faviconia ei ole asetettu." #: src/questions.py:59 msgid "Sorry, that response was not an option." msgstr "Anteeksi, mutta tuo vastaus ei ollut vaihtoehto." #: src/questions.py:108 msgid "Sorry, you must enter a value." msgstr "Anteeksi, mutta sinun täytyy antaa arvo." #: src/questions.py:128 msgid "Enter password: " msgstr "Anna salasana: " #: src/questions.py:130 msgid "Re-enter password: " msgstr "Anna salasana uudelleen: " #: src/questions.py:143 msgid "Passwords don't match." msgstr "Salasanat eivät täsmää." #: src/registry.py:416 msgid "Value must be either True or False (or On or Off), not %r." msgstr "" "Arvon täytyy olla \"True\" tai \"False (tai \"On\" tai \"Off\"), ei %r." #: src/registry.py:432 msgid "Value must be an integer, not %r." msgstr "Arvon täytyy olla kokonaisluku, eikä %r." #: src/registry.py:441 msgid "Value must be a non-negative integer, not %r." msgstr "Arvon täytyy olla positiivinen kokonaisluku, ei %r." #: src/registry.py:449 msgid "Value must be positive (non-zero) integer, not %r." msgstr "Arvon täytyy olla positiivinen (muu kuin nolla) kokonaisluku eikä %r." #: src/registry.py:457 msgid "Value must be a floating-point number, not %r." msgstr "Arvon täytyy olla likuluku, ei %r." #: src/registry.py:472 msgid "Value must be a floating-point number greater than zero, not %r." msgstr "Arvon täytyy olla nollaa suurempi liukuluku, ei %r." #: src/registry.py:482 #, fuzzy msgid "Value must be a floating point number in the range [0, 1], not %r." msgstr "Arvon täytyy olla liukuluku alueella [0,1] eikä %r." #: src/registry.py:496 msgid "Value is not a valid Python string, not %r." msgstr "Arvon täytyy olla kelvollinen Python merkkijono eikä %r." #: src/registry.py:528 msgid "Valid values include %L." msgstr "Kelvolliset arvot ovat %L." #: src/registry.py:530 msgid "Valid values include %L, not %%r." msgstr "Kelvolliset arvot ovat %L, eikä %%r." #: src/registry.py:601 msgid "Value must be a valid regular expression, not %r." msgstr "Arvon täytyy olla kelvollinen säännöllinen lauseke, eikä %r." #: src/utils/gen.py:102 msgid "year" msgstr "vuosi" #: src/utils/gen.py:105 msgid "week" msgstr "viikko" #: src/utils/gen.py:108 msgid "day" msgstr "päivä" #: src/utils/gen.py:111 msgid "hour" msgstr "tunti" #: src/utils/gen.py:115 msgid "minute" msgstr "minuutti" #: src/utils/gen.py:118 msgid "second" msgstr "sekunti" #: src/utils/gen.py:127 #, fuzzy msgid "%s ago" msgstr "%s sitten" #: src/utils/str.py:254 msgid "and" msgstr "ja" #~ msgid "" #~ "Determines what driver module the bot\n" #~ " will use. Socket, a simple driver based on timeout sockets, is used " #~ "by\n" #~ " default because it's simple and stable. Twisted is very stable and " #~ "simple,\n" #~ " and if you've got Twisted installed, is probably your best bet." #~ msgstr "" #~ "Määrittää mitä ajurimoduulia botti käyttää. Socket, yksinkertainen ajuri, " #~ "joka perustuu\n" #~ " aikakatkaisu sokkeleihin, on käytössä oletuksena, koska se on " #~ "yksinkertainen ja vakaa.\n" #~ " Twisted on erittäin vakaa ja yksinkertainen, ja jos Twisted on " #~ "asennettuna, se on\n" #~ " luultavasti paras vaihtoehto." #~ msgid "" #~ "Determines the content of the robots.txt file,\n" #~ " served on the server to search engine." #~ msgstr "" #~ "Määrittää robots.txt tiedoston sisällön, joka laitetaan palvelimelle " #~ "annettavaksi\n" #~ " hakukoneille." limnoria-2020.03.17/locales/fr.po0000644000175000017500000022241013634634532015711 0ustar valval00000000000000# Valentin Lorentz , 2012. msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2019-11-24 12:42+CET\n" "PO-Revision-Date: 2019-11-24 12:41+0100\n" "Last-Translator: \n" "Language-Team: French \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 2.2.1\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: src/callbacks.py:189 msgid "Error: " msgstr "Erreur : " #: src/callbacks.py:205 msgid "Error: I tried to send you an empty message." msgstr "Erreur : J'ai essayé de vous envoyer un message vide." #: src/callbacks.py:325 msgid "" "Missing \"%s\". You may want to quote your arguments with double quotes in " "order to prevent extra brackets from being evaluated as nested commands." msgstr "" "\"%s\" manquant. Vous devriez encadrer vos arguments par des guillements " "pour éviter que les crochets ne soient évalués comme étant des commandes " "imbriquées." #: src/callbacks.py:354 msgid "" "\"|\" with nothing preceding. I obviously can't do a pipe with nothing " "before the |." msgstr "" "\"|\" avec rien ne le précédant. Je ne peux évidtemment pas faire un pipe " "avec rien avant le |." #: src/callbacks.py:362 msgid "" "Spurious \"%s\". You may want to quote your arguments with double quotes in " "order to prevent extra brackets from being evaluated as nested commands." msgstr "" "\"%s\" en trop. Vous devriez encadrer vos arguments par des guillements pour " "éviter que les crochets ne soient évalués comme étant des commandes " "imbriquées." #: src/callbacks.py:371 msgid "" "\"|\" with nothing following. I obviously can't do a pipe with nothing " "after the |." msgstr "" "\"|\" avec rien ne le suivant. Je ne peux évidtemment pas faire un pipe avec " "rien après le |." #: src/callbacks.py:570 msgid "%s is not a valid %s." msgstr "%s n'est pas un(e) %s valide." #: src/callbacks.py:572 msgid "That's not a valid %s." msgstr "Ce n'est pas un(e) %s valide." #: src/callbacks.py:652 msgid "You've attempted more nesting than is currently allowed on this bot." msgstr "" "Vous avez essayé de faire plus d'imbrication que ce qui est actuellement " "autorisé sur ce bot." #: src/callbacks.py:833 msgid "" "The command %q is available in the %L plugins. Please specify the plugin " "whose command you wish to call by using its name as a command before %q." msgstr "" "La commande %q est disponible dans les plugins %L. Veuillez spécifier dans " "quel plugin se trouve la commande que vous souhaitez appeler, en ajoutant le " "nom du plugin avant %q." #: src/callbacks.py:955 msgid "(XX more messages)" msgstr "(XX messages supplémentaires)" #: src/callbacks.py:978 msgid "more message" msgstr "message supplémentaire" #: src/callbacks.py:980 msgid "more messages" msgstr "messages supplémentaires" #: src/callbacks.py:1122 msgid "" "Determines what commands are currently disabled. Such\n" " commands will not appear in command lists, etc. They will appear not " "even\n" " to exist." msgstr "" "Détermine quelles commandes sont actuellement désactivées. De telles " "commandes n'apparaitront pas dans la liste des commandes, etc. Ça sera comme " "si elles n'existaient pas." #: src/callbacks.py:1344 msgid "Invalid arguments for %s." msgstr "Argument invalide pour %s" #: src/callbacks.py:1370 msgid "The %q command has no help." msgstr "La commande %q n'a pas d'aide." #: src/commands.py:273 msgid "integer" msgstr "entier" #: src/commands.py:284 msgid "non-integer value" msgstr "valeur non entière" #: src/commands.py:295 msgid "floating point number" msgstr "nombre à virgule flottante" #: src/commands.py:304 msgid "positive integer" msgstr "entier positif" #: src/commands.py:308 msgid "non-negative integer" msgstr "entier non négatif" #: src/commands.py:311 msgid "index" msgstr "index" #: src/commands.py:336 msgid "number of seconds" msgstr "nombre de secondes" #: src/commands.py:343 msgid "boolean" msgstr "booléen" #: src/commands.py:357 src/commands.py:364 src/commands.py:373 #: src/commands.py:380 src/commands.py:389 msgid "do that" msgstr "faire ça" #: src/commands.py:360 src/commands.py:367 src/commands.py:376 #: src/commands.py:383 src/commands.py:392 msgid "I'm not even in %s." msgstr "Je ne suis pas sur %s." #: src/commands.py:362 msgid "I need to be voiced to %s." msgstr "Je doit être voicé pour %s" #: src/commands.py:370 msgid "I need to be at least voiced to %s." msgstr "Je doit être au moins voice pour %s" #: src/commands.py:378 msgid "I need to be halfopped to %s." msgstr "Je doit être halfop pour %s" #: src/commands.py:386 msgid "I need to be at least halfopped to %s." msgstr "Je doit être au moins halfop pour %s" #: src/commands.py:394 msgid "I need to be opped to %s." msgstr "Je doit être opé pour %s" #: src/commands.py:400 src/commands.py:561 msgid "channel" msgstr "canal" #: src/commands.py:413 msgid "nick or hostmask" msgstr "nick ou masque d'hôte" #: src/commands.py:467 msgid "regular expression" msgstr "expression régulière" #: src/commands.py:478 src/commands.py:482 msgid "nick" msgstr "nick" #: src/commands.py:479 msgid "That nick is too long for this server." msgstr "Ce nick est trop long pour ce serveur." #: src/commands.py:490 msgid "I haven't seen %s." msgstr "Je n'ai pas vu %s." #: src/commands.py:540 src/commands.py:559 msgid "I'm not in %s." msgstr "Je ne suis pas sur %s" #: src/commands.py:544 msgid "This command may only be given in a channel that I am in." msgstr "" "Cette commande ne peut être donnée que sur un canal sur lequel je suis." #: src/commands.py:557 msgid "You must be in %s." msgstr "Vous devez être sur %s" #: src/commands.py:568 msgid "%s is not in %s." msgstr "%s n'est pas sur %s" #: src/commands.py:616 msgid "You must not give the empty string as an argument." msgstr "Vous ne pouvez me donner une chaine vide comme argument." #: src/commands.py:624 msgid "You must not give a string containing spaces as an argument." msgstr "" "Vous ne pouvez me donner une chaine contenant des espaces comme argument." #: src/commands.py:634 msgid "This message must be sent in a channel." msgstr "Ce message doit être envoyé sur un canal." #: src/commands.py:666 msgid "url" msgstr "url" #: src/commands.py:672 msgid "email" msgstr "courriel" #: src/commands.py:680 msgid "http url" msgstr "URL HTTP" #: src/commands.py:687 msgid "command name" msgstr "nom de commande" #: src/commands.py:695 msgid "ip" msgstr "IP" #: src/commands.py:701 msgid "letter" msgstr "lettre" #: src/commands.py:733 msgid "plugin" msgstr "plugin" #: src/commands.py:741 msgid "irc color" msgstr "couleur IRC" #: src/conf.py:128 msgid "" "Determines whether this plugin is loaded\n" " by default." msgstr "Détermine si ce plugin est chargé par défaut." #: src/conf.py:132 msgid "" "Determines whether this plugin is\n" " publicly visible." msgstr "Détermine si ce plugin est visible publiquement" #: src/conf.py:233 msgid "Determines the bot's default nick." msgstr "Détermine le nick par défaut du bot." #: src/conf.py:236 msgid "" "Determines what alternative\n" " nicks will be used if the primary nick (supybot.nick) isn't available. " "A\n" " %s in this nick is replaced by the value of supybot.nick when used. If " "no\n" " alternates are given, or if all are used, the supybot.nick will be " "perturbed\n" " appropriately until an unused nick is found." msgstr "" "Détermine quels nicks alternatifs peuvent être utilisés si le nick " "principal (supybot.nick) n'est pas disponible. Un %s dans ce nick est " "remplacé par la valeur de supybot.nick. Si aucune alternative n'est " "donnée, ou si elles sont toutes déjà utilisées, supybot.nick sera modifié " "jusqu'à ce qu'un nick non utilisé soit trouvé." #: src/conf.py:243 msgid "" "Determines the bot's ident string, if the server\n" " doesn't provide one by default." msgstr "" "Détermine l'ident du bot (ce qui précède le @ dans le masque d'hôte), si " "le serveur n'en fourni par une par défaut." #: src/conf.py:260 msgid "" "Determines the real name which the bot sends to\n" " the server. A standard real name using the current version of the bot\n" " will be generated if this is left empty." msgstr "" "Détermine le nom réel que le bot envoie au serveur. Un nom standard " "contenant la version actuelle du bot sera généré si vous laisser cette " "variable vide." #: src/conf.py:269 msgid "Determines what networks the bot will connect to." msgstr "Détermine à quels réseaux le bot se connecte." #: src/conf.py:368 msgid "" "Determines what password will be used on %s. Yes, we know that\n" " technically passwords are server-specific and not network-specific,\n" " but this is the best we can do right now." msgstr "" "Détermine quel mot de passe sera utilisé sur %s. Oui, nous savons que " "les mots de passe sont en fait spécifiques aux serveurs et non et " "réseaux, mais nous pensons que c'est mieux comme ça." #: src/conf.py:372 msgid "" "Space-separated list of servers the bot will connect to for %s.\n" " Each will be tried in order, wrapping back to the first when the " "cycle\n" " is completed." msgstr "" "Liste des serveurs (éléments séparés par des espaces) auxquels le bot se " "connectera pour %s. Chacun sera essayé dans l’ordre, retournant au début " "lorsque le cycle est terminé." #: src/conf.py:376 msgid "Space-separated list of channels the bot will join only on %s." msgstr "" "Liste (éléments séparés par des espaces) des salons que le bot rejoindra sur " "ce réseau %s." #: src/conf.py:380 msgid "" "Determines whether the bot will attempt to connect with SSL\n" " sockets to %s." msgstr "" "Détermine si le bot tentera de se connecter avec des sockets SSL à %s." #: src/conf.py:383 msgid "" "Space-separated list\n" " of fingerprints of trusted certificates for this network.\n" " Supported hash algorithms are: %L.\n" " If non-empty, Certification Authority signatures will not be used " "to\n" " verify certificates." msgstr "" "Liste séparée par des espaces d'empreintes de certificats de confiance pour " "ce réseau. Les algorithmes de hashage supportés sont : %L. Si non-vide, les " "signatures d'Autorités de Certification ne seront pas utilisées pour " "vérifier les certificats." #: src/conf.py:389 msgid "" "A certificate that is trusted to verify\n" " certificates of this network (aka. Certificate Authority)." msgstr "" "Un certificat de confiance pour vérifier les certificats de ce réseau (c'est-" "à-dire une Autorité de Certification)." #: src/conf.py:392 msgid "Deprecated config value, keep it to False." msgstr "Valeur dépréciée, laissez-la à False." #: src/conf.py:395 msgid "" "Determines what certificate file (if any) the bot will use to\n" " connect with SSL sockets to %s." msgstr "" "Détermine quel fichier de certificat (s’il y en a un) le bot utilisera pour " "se connecter par SSL à %s." #: src/conf.py:398 msgid "" "Determines what key (if any) will be used to join the\n" " channel." msgstr "" "Détermine quelle clef (s'il y en a) sera utilisée pour rejoindre le " "canal." #: src/conf.py:400 msgid "" "Determines\n" " what nick the bot will use on this network. If empty, defaults to\n" " supybot.nick." msgstr "" "Détermine quel nick le bot utilisera sur ce réseau. Si cette variable de " "configuration est vide, le nick par défaut sera pris dans supybot.nick." #: src/conf.py:403 msgid "" "Determines\n" " the bot's ident string, if the server doesn't provide one by " "default.\n" " If empty, defaults to supybot.ident." msgstr "" "Détermine l'ident du bot (ce qui précède le @ dans le masque d'hôte), si " "le serveur n'en fourni par une par défaut." #: src/conf.py:406 msgid "" "Determines\n" " the real name which the bot sends to the server. If empty, defaults " "to\n" " supybot.user" msgstr "" "Détermine quel nick le bot utilisera sur ce réseau. Si cette variable de " "configuration est vide, le nick par défaut sera pris dans supybot.nick." #: src/conf.py:410 msgid "" "Determines what user modes the bot will request\n" " from the server when it first connects. If empty, defaults to\n" " supybot.protocols.irc.umodes" msgstr "" "Détermine quels modes utilisateur le bot demandera au serveur lorsqu'il s'y " "connectera. Si cette variable de configuration est vide, le nick par défaut " "sera pris dans supybot.protocols.irc.umodes." #: src/conf.py:415 msgid "" "Determines what SASL username will be used on %s. This should\n" " be the bot's account name. Due to the way SASL works, you can't use\n" " any grouped nick." msgstr "" "Détermine quel nom d'utilisateur SASL sera utilisé sur %s. Ce doit être le " "nom du compte du bot. À cause de la façon dont SASL fonctionne, vous ne " "pouvez pas utiliser un nick groupé." #: src/conf.py:419 msgid "Determines what SASL password will be used on %s." msgstr "Détermine quel mot de passe SASL sera utilisé sur %s." #: src/conf.py:422 msgid "" "Determines what SASL ECDSA key (if any) will be used on %s.\n" " The public key must be registered with NickServ for SASL\n" " ECDSA-NIST256P-CHALLENGE to work." msgstr "" "Détermine quelle clé ECDSA sera utilisée pour l'authentification par SASL " "sur %s.\n" "La clé publique doit être enregistrée avec NickServ avec SASL ECDSA-NIST256P-" "CHALLENGE pour qu'elle puisse être utilisée." #: src/conf.py:426 msgid "" "Determines\n" " what SASL mechanisms will be tried and in which order." msgstr "Détermine quels mécanismes SASL seront utilisés et dans quel ordre." #: src/conf.py:429 msgid "" "Determines whether the bot will abort the connection if the\n" " none of the enabled SASL mechanism succeeded." msgstr "" "Détermine si le bot doit terminer la connexion si aucun des mécanismes SASL " "activés ne fonctionne." #: src/conf.py:432 msgid "" "If not empty, determines the hostname of the socks proxy that\n" " will be used to connect to this network." msgstr "" "Si non vide, détermine l'hôte du proxy socks qui sera utilisé pour se " "connecter à ce réseau." #: src/conf.py:452 msgid "Determines how urls should be formatted." msgstr "Détermine comment les URLs seront formatées." #: src/conf.py:460 msgid "" "Determines how timestamps\n" " printed for human reading should be formatted. Refer to the Python\n" " documentation for the time module to see valid formatting characters " "for\n" " time formats." msgstr "" "Détermine quels timestamps sont affichés pour être lus par des humaines. " "Référez-vous à la documentation Python sur le module time pour plus " "d'information sur les formats valides." #: src/conf.py:475 msgid "" "Determines whether elapsed times will be given\n" " as \"1 day, 2 hours, 3 minutes, and 15 seconds\" or as \"1d 2h 3m 15s\"." msgstr "" "Détermine si un utilise des temps plus courts, c'est à dire par exemple " "\"1d 2h 3m 15s\" au lieu de \"1 day, 2 hours, 3 minutes, and 15 seconds" "\"." #: src/conf.py:486 msgid "" "Maximum number of items in a list\n" " before the end is replaced with 'and others'. Set to 0 to always\n" " show the entire list." msgstr "" "Nombre maximum d'éléments dans une liste avant que la fin de la liste soit " "remplacée par 'et autres'. Définir à 0 pour toujours afficher la liste " "complète." #: src/conf.py:503 msgid "other" msgstr "autre" #: src/conf.py:508 msgid "" "Determines the absolute maximum length of\n" " the bot's reply -- no reply will be passed through the bot with a " "length\n" " greater than this." msgstr "" "Détermine la longueur maximum absolue des réponses du bot ; le bot " "n'enverra aucune réponse qui dépassera ce nombre." #: src/conf.py:513 msgid "" "Determines whether the bot will break up long\n" " messages into chunks and allow users to use the 'more' command to get " "the\n" " remaining chunks." msgstr "" "Détermine si le bot séparera les longs messages en morceaux et permettra " "aux utilisateurs et utilisatrices d'utiliser la commande 'more' pour " "récupérer les morceaux restants." #: src/conf.py:518 msgid "" "Determines what the maximum number of\n" " chunks (for use with the 'more' command) will be." msgstr "" "Détermine quel est le nombre maximum de morceaux (que l'on récupère avec " "la commande 'more')." #: src/conf.py:522 msgid "" "Determines how long individual chunks\n" " will be. If set to 0, uses our super-tweaked,\n" " get-the-most-out-of-an-individual-message default." msgstr "" "Détermine quelle est la longueur maximale des morceaux. Définir cette " "variable à 0 permet d'utiliser notre super algorithme pour optimiser " "cette longueur en fonction des paramètres du serveur." #: src/conf.py:527 msgid "" "Determines how many mores will be sent\n" " instantly (i.e., without the use of the more command, immediately when\n" " they are formed). Defaults to 1, which means that a more command will " "be\n" " required for all but the first chunk." msgstr "" "Détermine combien de 'more's sont envoyés immédiatement (c'est à dire, " "avant l'utilisation de la commande 'more', juste après que l'utilisateur/" "trice ait envoyé la commande. Par défaut, cela vaut 1, ce qui signifie " "que seul le premier morceau est envoyé. Avant l'appel de 'more'." #: src/conf.py:533 msgid "" "Determines whether the bot will send\n" " multi-message replies in a single message. This defaults to True \n" " in order to prevent the bot from flooding. If this is set to False\n" " the bot will send multi-message replies on multiple lines." msgstr "" "Détermine si le bot enverra des réponses multi-messages dans un seul " "message ou dans plusieurs. Cela vaut True par défaut pour empêcher le bot de " "flooder. Si cela vaut False, alors le bot enverra les réponses multi-" "messages sur plusieurs lignes." #: src/conf.py:539 msgid "" "Determines whether the bot will reply with an\n" " error message when it is addressed but not given a valid command. If " "this\n" " value is False, the bot will remain silent, as long as no other plugins\n" " override the normal behavior." msgstr "" "Détermine si le bot répondra avec un message d'erreur lorsqu'un message " "lui est adressé, mais qu'il ne s'agit pas d'une commande valide. Si cette " "valeur est à False, le bot restera silencieux, du moment qu'aucun plugin " "ne modifie le comportement normal." #: src/conf.py:546 msgid "" "Determines whether error messages that result\n" " from bugs in the bot will show a detailed error message (the uncaught\n" " exception) or a generic error message." msgstr "" "Détermine si les messages d'erreur résultant de bugs seront affichés dans " "un message d'erreur (l'exception non interceptée) ou si un message " "d'erreur générique est utilisé." #: src/conf.py:550 msgid "" "Determines whether the bot will send error\n" " messages to users in private. You might want to do this in order to " "keep\n" " channel traffic to minimum. This can be used in combination with\n" " supybot.reply.error.withNotice." msgstr "" "Détermine si le bot enverra les messages d'erreur en privé. Vous pouvez " "vouloir ceci dans le but de limiter le traffic de données sur le canal. " "Vous pouvez utiliser ceci en combinaison avec supybot.reply.error." "withNotice." #: src/conf.py:555 msgid "" "Determines whether the bot will send error\n" " messages to users via NOTICE instead of PRIVMSG. You might want to do " "this\n" " so users can ignore NOTICEs from the bot and not have to see error\n" " messages; or you might want to use it in combination with\n" " supybot.reply.errorInPrivate so private errors don't open a query " "window\n" " in most IRC clients." msgstr "" "Détermine si le bot enverra les erreurs par NOTICE plutôt que par " "PRIVMSG. Vous pouvez faire cela pour que les personnes qui ne veulent pas " "recevoir de message d'erreur ignorent les NOTICEs du bot. Vous pouvez " "utiliser cette fonctionnalité en combinaison avec supybot.reply." "errorInPrivate, pour que les erreurs en privé n'ouvrent pas une nouvelle " "fenêtre de requête, sur la plupart des clients." #: src/conf.py:562 msgid "" "Determines whether the bot will *not* provide\n" " details in the error\n" " message to users who attempt to call a command for which they do not " "have\n" " the necessary capability. You may wish to make this True if you don't " "want\n" " users to understand the underlying security system preventing them from\n" " running certain commands." msgstr "" "Détermine si le bot n'enverra *pas* un message d'erreur aux personnes qui " "tentent d'appeler une commande pour laquelle elles n'ont pas de capacité " "suffisante. Vous pouvez le mettre à True si vous ne voulez pas que les " "utilisateurs/trices comprennent le système sous-jacent de capacités pour " "lancer certaines commandes." #: src/conf.py:570 msgid "" "Determines whether the bot will reply\n" " privately when replying in a channel, rather than replying to the " "whole\n" " channel." msgstr "" "Détermine si le bot répondra en privé, plutôt que de répondre sur le " "canal." #: src/conf.py:575 msgid "" "Determines whether the bot will reply with a\n" " notice when replying in a channel, rather than replying with a privmsg " "as\n" " normal." msgstr "" "Détermine si le bot répondra par notice sur un canal plutôt que par " "privmsg comme d'habitude" #: src/conf.py:581 msgid "" "Determines whether the bot will reply with a\n" " notice when it is sending a private message, in order not to open a /" "query\n" " window in clients." msgstr "" "Détermine si le bot répondra avec une notice lorsque l'on s'adresse à lui " "en privé, pour éviter de devoir ouvrir une fenêtre /query." #: src/conf.py:586 msgid "" "Determines whether the bot will always prefix\n" " the user's nick to its reply to that user's command." msgstr "" "Détermine si le bot préfixera toujours ses réponses par le nick de " "l’utilisateur ayant invoqué la commande." #: src/conf.py:590 msgid "" "Determines whether the bot should attempt to\n" " reply to all messages even if they don't address it (either via its " "nick\n" " or a prefix character). If you set this to True, you almost certainly " "want\n" " to set supybot.reply.whenNotCommand to False." msgstr "" "Détermine si le bot tentera de répondre à tous les messages, même si ils " "ne lui sont pas adressés (par son nick ou par un caractère de préfixe). " "Si vous définissez ceci à True, vous voudrez probablement mettre supybot." "reply.whenNotCommand à False." #: src/conf.py:596 msgid "" "Determines whether the bot will allow you to\n" " send channel-related commands outside of that channel. Sometimes " "people\n" " find it confusing if a channel-related command (like Filter.outfilter)\n" " changes the behavior of the channel but was sent outside the channel\n" " itself." msgstr "" "Détermine si le bot vous permettra d'envoyer des commandes liées à un " "canal en-dehors de ce canal. Parfois, certaines personnes confondent " "lorsqu'une commande liée à un canal (comme Filter.outfilter) changent le " "comportement du canal, alors que la commande est envoyée en-dehors du " "canal." #: src/conf.py:603 msgid "" "Determines whether the bot will unidentify\n" " someone when that person changes their nick. Setting this to True\n" " will cause the bot to track such changes. It defaults to False for a\n" " little greater security." msgstr "" "Détermine si le bot will désidentifiera une personne lorsque celle-ci " "change son nick. Mettre ceci à True fera que le bot traquera ces " "changements. Il est par défaut à False pour améliorer légèrement la " "sécurité." #: src/conf.py:609 msgid "" "Determines whether the bot will always join a\n" " channel when it's invited. If this value is False, the bot will only " "join\n" " a channel if the user inviting it has the 'admin' capability (or if " "it's\n" " explicitly told to join the channel using the Admin.join command)." msgstr "" "Détermine si le bot joindra toujours les canaux sur lesquels il est " "invité. Si cette valeur est à False, le bot ne joindra un canal que si la " "personne l'invitant a la capacité 'admin' (ou si il lui a directement " "envoyé la commande Admin join)" #: src/conf.py:615 msgid "" "Supybot normally replies with the full help\n" " whenever a user misuses a command. If this value is set to True, the " "bot\n" " will only reply with the syntax of the command (the first line of the\n" " help) rather than the full help." msgstr "" "Supybot répond normalement avec une aide complète lorsque quelqu'un se " "trompe dans une commande. Si cette valeur est à True, le bot ne répondra " "qu'avec la syntaxe de la commande (la première ligne de l'aide), plutôt " "que par toute l'aide." #: src/conf.py:630 msgid "" "Determines what prefix characters the bot will\n" " reply to. A prefix character is a single character that the bot will " "use\n" " to determine what messages are addressed to it; when there are no " "prefix\n" " characters set, it just uses its nick. Each character in this string " "is\n" " interpreted individually; you can have multiple prefix chars\n" " simultaneously, and if any one of them is used as a prefix the bot will\n" " assume it is being addressed." msgstr "" "Détermine à quel caractère de préfixe le bot réagira. Un caractère de " "préfixe est un caractère servant à indiquer que le message est destiné " "au bot ; lorsqu'il n'y a pas de caractère de préfixe, vous ne pouvez " "qu'utiliser le nick du bot. Chaque caractère de cette chaîne sera " "interprété individuellement ; vous pouvez avoir plusieurs caractères " "simultanément, et un seul d'entre eux suffira pour s'adresser au bot." #: src/conf.py:639 msgid "" "Determines what strings the\n" " bot will reply to when they are at the beginning of the message. " "Whereas\n" " prefix.chars can only be one character (although there can be many of\n" " them), this variable is a space-separated list of strings, so you can\n" " set something like '@@ ??' and the bot will reply when a message is\n" " prefixed by either @@ or ??." msgstr "" "Détermine à quelles chaînes le bot répondra lorsqu'elles sont au début du " "message. Alors que prefex.chars n'est utilisé que pour un caractère seul " "(même si on peut en utiliser plusieurs), cette variable est une liste de " "chaînes séparées par des espaces, vous pouvez donc par exemple mettre " "'@@ ??' et le bot répondra lorsqu'un message est préfixé par @@ ou par ??." #: src/conf.py:646 msgid "" "Determines whether the bot will reply when\n" " people address it by its nick, rather than with a prefix character." msgstr "" "Détermine si le bot will répondra lorsque des gens s'adresseront à lui " "par si nick, au lieu de ne répondre qu'aux caractères de préfixe." #: src/conf.py:649 msgid "" "Determines whether the bot will reply when\n" " people address it by its nick at the end of the message, rather than at\n" " the beginning." msgstr "" "Détermine si le bot répondra lorsque les personnes mettent son nick à la " "fin d'un message, plutôt qu'au début." #: src/conf.py:653 msgid "" "Determines what extra nicks\n" " the bot will always respond to when addressed by, even if its current " "nick\n" " is something else." msgstr "" "Détermine à quels nicks supplémentaires le bot répondra lorsqu'on " "s'adressera à lui par ceux-ci, même si le nick est actuellement utilisé." #: src/conf.py:663 msgid "The operation succeeded." msgstr "Opération effectuée avec succès." #: src/conf.py:664 msgid "" "Determines what message the bot replies with when a command succeeded.\n" " If this configuration variable is empty, no success message will be\n" " sent." msgstr "" "Détermine quel message le bot répondra lorsqu'une commande a été " "effectuée avec succès. Si cette variable de configuration est vide, aucun " "message de succès ne sera envoyé." #: src/conf.py:669 msgid "" "An error has occurred and has been logged.\n" " Please contact this bot's administrator for more information." msgstr "" "Une erreur est survenue et a été logguée. Contactez l'administrateur du " "bot pour plus d'informations." #: src/conf.py:670 msgid "" "\n" " Determines what error message the bot gives when it wants to be\n" " ambiguous." msgstr "" "\n" "Détermine quel message d'erreur le bot donnera, quand il décidera de ne " "pas être plus précis." #: src/conf.py:675 msgid "" "An error has occurred and has been logged.\n" " Check the logs for more information." msgstr "" "Une erreur est survenue et a été logguée. R les logs pour plus " "d'informations." #: src/conf.py:676 msgid "" "Determines what error\n" " message the bot gives to the owner when it wants to be ambiguous." msgstr "" "Détermine quel message d'erreur le bot donnera au propriétaire lorsqu'il " "veut être ambigu." #: src/conf.py:680 msgid "" "Your hostmask doesn't match or your password\n" " is wrong." msgstr "" "Votre masque d'hôte ne correspond pas, ou votre mot de passe est " "incorrect." #: src/conf.py:681 msgid "" "Determines what message the bot replies with when\n" " someone tries to use a command that requires being identified or having " "a\n" " password and neither credential is correct." msgstr "" "Détermine quel message le bot répondra lorsque quelqu'un utiliser une " "commande qui nécessite d'être identifié." #: src/conf.py:687 msgid "" "I can't find %s in my user\n" " database. If you didn't give a user name, then I might not know what " "your\n" " user is, and you'll need to identify before this command might work." msgstr "" "Je ne peux trouver %s dans ma base de données d'utilisateurs. Si vous ne " "m'avez pas donné un nom d'utilisateur, je ne peux savoir qui est cet " "utilisateur, et vous devrez vous identifier avant que cette commande " "fonctionne." #: src/conf.py:690 msgid "" "Determines what error message the bot replies with when someone tries\n" " to accessing some information on a user the bot doesn't know about." msgstr "" "Détermine quel message d'erreur est envoyé lorsque quelqu'un essaye " "d'accéder à une information sur une personne que le bot ne connait pas." #: src/conf.py:694 msgid "" "You must be registered to use this command.\n" " If you are already registered, you must either identify (using the " "identify\n" " command) or add a hostmask matching your current hostmask (using the\n" " \"hostmask add\" command)." msgstr "" "Vous devez être enregistré(e) pour utiliser cette commande. Si vous êtes " "déjà enregistré(e), vous devez vous identifier (en utilisant la commande " "'identify') ou ajouter un masque d'hôte correspondant à votre masque " "d'hôte courant (en utilisant la commande 'hostmask add')." #: src/conf.py:697 msgid "" "Determines what error message the bot\n" " replies with when someone tries to do something that requires them to " "be\n" " registered but they're not currently recognized." msgstr "" "Détermine quel message d'erreur le bot renvoie lorsque quelqu'un essaye " "de faire quelque chose qui nécessit d'être enregistré, alors qu'il n'est " "pas reconnu." #: src/conf.py:702 msgid "" "You don't have the %s capability. If you\n" " think that you should have this capability, be sure that you are " "identified\n" " before trying again. The 'whoami' command can tell you if you're\n" " identified." msgstr "" "Vous n'avez pas la capacité %s. Si vous pensez que vous l'avez, assurez-" "vous d'être identifié(e) et réessayez. La commande 'whoami' vous dit si " "vous êtes identifié(e)." #: src/conf.py:705 msgid "" "Determines what error message is given when the bot\n" " is telling someone they aren't cool enough to use the command they tried " "to\n" " use." msgstr "" "Détermine quel message d'erreur est donné lorsqu'une personne tente " "d'utiliser une commande à laquelle il n'y pas accès." #: src/conf.py:710 msgid "" "You're missing some capability you need.\n" " This could be because you actually possess the anti-capability for the\n" " capability that's required of you, or because the channel provides that\n" " anti-capability by default, or because the global capabilities include\n" " that anti-capability. Or, it could be because the channel or\n" " supybot.capabilities.default is set to False, meaning that no commands " "are\n" " allowed unless explicitly in your capabilities. Either way, you can't " "do\n" " what you want to do." msgstr "" "Il vous manque une capacité. Ceci peut être dû au fait que vous possédez " "actuellement une anti-capacité quant à la commande que vous tentez " "d'utiliser, ou que le canal fourni cette anti-capacité par défaut, ou " "parce que les capacités globales incluent cette anti-capacité. Cela " "peut également être du au fait que supybot.capabilities.default est " "défini à False, ce qui signifie qu'une commande n'est autorisée par " "défaut (elles doivent être autorisées une par une par un administrateur). " "En clair, vous ne pouvez pas le faire." #: src/conf.py:718 msgid "" "Determines what generic error message is given when the bot is telling\n" " someone that they aren't cool enough to use the command they tried to " "use,\n" " and the author of the code calling errorNoCapability didn't provide an\n" " explicit capability for whatever reason." msgstr "" "Détermine le message d'erreur générique qui est donné par le bot pour " "dire à quelqu'un qu'il ou elle n'a pas les capacités requises pour " "utiliser une commande qu'il ou elle essaye d'utiliser, si l'auteur du " "code appelant errorNoCapability ne fourni pas d'information explicite " "sur la raison de ce refus." #: src/conf.py:724 msgid "" "That operation cannot be done in a\n" " channel." msgstr "Cette opération ne peut être faite sur un canal." #: src/conf.py:725 msgid "" "Determines what error messages the bot sends to people\n" " who try to do things in a channel that really should be done in\n" " private." msgstr "" "Détermine quel message d'erreur le bot envoie aux personnes qui tentent " "de faire quelque chose sur un canal, alors que cela doit être fait en " "privé." #: src/conf.py:730 msgid "" "This may be a bug. If you think it is,\n" " please file a bug report at\n" " ." msgstr "" "Ceci semble être un bug. Si vous pensez que c'en est un, veillez à " "envoyer un rapport de bug sur ." #: src/conf.py:733 msgid "" "Determines what message the bot sends when it thinks you've\n" " encountered a bug that the developers don't know about." msgstr "" "Détermine quel message le bot envoie quand il pense que l'on a rencontré " "un bug que les développeurs ne connaissent pas." #: src/conf.py:740 msgid "" "A floating point number of seconds to throttle\n" " snarfed URLs, in order to prevent loops between two bots snarfing the " "same\n" " URLs and having the snarfed URL in the output of the snarf message." msgstr "" "Un nombre à virgule flottante correspondant au temps (en secondes) à " "attendre avant de 'snarfer' la même URL, dans le but d'éviter que deux " "bots entrent ainsi en boucle infinie." #: src/conf.py:745 msgid "" "Determines the number of seconds\n" " between running the upkeep function that flushes (commits) open " "databases,\n" " collects garbage, and records some useful statistics at the debugging\n" " level." msgstr "" "Détermine le nombre de secondes entre deux exécutions de la fonction " "upkeep, qui rafraichit les bases de données ouvertes, vide le cache, et " "enregistre des statistiques utiles pour le débogage." #: src/conf.py:751 msgid "" "Determines whether the bot will periodically\n" " flush data and configuration files to disk. Generally, the only time\n" " you'll want to set this to False is when you want to modify those\n" " configuration files by hand and don't want the bot to flush its current\n" " version over your modifications. Do note that if you change this to " "False\n" " inside the bot, your changes won't be flushed. To make this change\n" " permanent, you must edit the registry yourself." msgstr "" "Détermine si le bot rafraichira périodiquement les fichiers de données et " "de configuration sur le disque. Générallement, on ne défini ceci à False " "que lorsque l'on veut modifier ces fichiers de configuration à la main, " "et que l'on ne veut pas que le bot supprime les modifications en écrivant " "par-dessus. Notez que si vous définissez ceci à False, les changements " "apportés aux fichiers n'auront pas pour autant une influence sur le bot. " "Si vous voulez rendre ce changement définitif, vous devez éditer le " "registre vous-même." #: src/conf.py:777 msgid "" "Determines what characters are valid for quoting\n" " arguments to commands in order to prevent them from being tokenized.\n" " " msgstr "" "Détermine quels caractères sont valides pour citer des arguments à des " "commandes, pour leur éviter d'être tokénisés." #: src/conf.py:784 msgid "" "Determines whether the bot will allow nested\n" " commands, which rule. You definitely should keep this on." msgstr "" "Détermine si le bot autorisera les commandes commandes imbriquées sur ce " "bot, ce qui est une fonctionnalité génial. Vous devriez vraiment garder " "ceci actif." #: src/conf.py:787 msgid "" "Determines what the maximum number of\n" " nested commands will be; users will receive an error if they attempt\n" " commands more nested than this." msgstr "" "Détermine le nombre maximum de commandes imbriquées ; les utilisateurs " "recevront une erreur si ils ou elles tentent d'en mettre plus." #: src/conf.py:796 msgid "" "Supybot allows you to specify what brackets\n" " are used for your nested commands. Valid sets of brackets include\n" " [], <>, {}, and (). [] has strong historical motivation, but <> or\n" " () might be slightly superior because they cannot occur in a nick.\n" " If this string is empty, nested commands will not be allowed in this\n" " channel." msgstr "" "Supybot vous permet de spécifier quels crochets sont utilisés pour vos " "commandes imbriquées. Sont disponibles : [], <>, {}, et (). [] est très " "largement le plus utilisé, car leur utilisation est la plus simple pour " "des claviers qwerty. <> et () tirent leur avantage du fait qu'ils ne " "peuvent apparaître dans un nick. Si cette chaîne est vide, les commandes " "imbriquées ne seront pas autorisées sur ce canal." #: src/conf.py:803 msgid "" "Supybot allows nested commands. Enabling this\n" " option will allow nested commands with a syntax similar to UNIX pipes, " "for\n" " example: 'bot: foo | bar'." msgstr "" "Supybot permet les commandes imbriquées. Activer cette option permettra " "aux commandes imbriquées d'utiliser une syntaxe similaire aux 'pipes' " "UNIX, par exemple : 'bot: foo | bar'." #: src/conf.py:808 msgid "" "Determines what commands have default\n" " plugins set, and which plugins are set to be the default for each of " "those\n" " commands." msgstr "" "Détermine, lorsque l'on appelle une commande appartenant à plusieurs " "plugins, lequel sera choisi par défaut pour chacune de ces commandes." #: src/conf.py:814 msgid "" "Determines what plugins automatically get precedence over all\n" " other plugins when selecting a default plugin for a command. By\n" " default, this includes the standard loaded plugins. You probably\n" " shouldn't change this if you don't know what you're doing; if you " "do\n" " know what you're doing, then also know that this set is\n" " case-sensitive." msgstr "" "Détermine quels plugins sont prioritaires sur tous les autres lorsque " "l'on recherche le plugin par défaut d'une commande. Par défaut, cela " "inclu les plugins standards chargés. Vous ne devriez probablement pas " "changer ceci, à moins de savoir ce que vous faites ; si vous savez ce " "que vous faites, sachez que les noms de plugins sont sensibles à la " "classe." #: src/conf.py:824 msgid "" "Allows this bot's owner user to use commands\n" " that grants them shell access. This config variable exists in case you " "want\n" " to prevent MITM from the IRC network itself (vulnerable IRCd or IRCops)\n" " from gaining shell access to the bot's server by impersonating the " "owner.\n" " Setting this to False also disables plugins and commands that can be\n" " used to indirectly gain shell access." msgstr "" "Autorise les propriétaires du bot à utiliser des commandes qui leur donne un " "accès shell. Cette variable de configuration existe au cas où vous voulez " "empêcher des attaques MITM du réseau IRC lui-même (IRCd or IRCops " "vulnérables) de permettre un accès shell au serveur du bot en se faisant " "passer pour les propriétaires. Définir ceci à False désactive aussi les " "plugins et commandes qui peuvent être utilisées pour indirectement gagner un " "accès shell." #: src/conf.py:839 msgid "" "Determines the interval used for\n" " the history storage." msgstr "Détermine l’intervalle utilisé pour le stockage de l’historique." #: src/conf.py:842 msgid "" "Determines whether the bot will defend itself\n" " against command-flooding." msgstr "" "Détermine si le bot se défendra contre les attaques par flood de " "commandes." #: src/conf.py:845 msgid "" "Determines how many commands users are\n" " allowed per minute. If a user sends more than this many commands in " "any\n" " 60 second period, they will be ignored for\n" " supybot.abuse.flood.command.punishment seconds." msgstr "" "Détermine le nombre maximum de commandes qu'une personne peut envoyer en " "une minute. Si une personne dépasse cette limite, elle sera ignoré " "supybot.abuse.flood.command.punishment secondes." #: src/conf.py:850 msgid "" "Determines how many seconds the bot\n" " will ignore users who flood it with commands." msgstr "" "Détermine combien de secondes le bot ignorera les personnes qui le " "floodent de commandes." #: src/conf.py:853 msgid "" "Determines whether the bot will notify people\n" " that they're being ignored for command flooding." msgstr "" "Détermine si le bot notifiera les personnes lorsqu'elles se font ignorer " "pour flood de commande." #: src/conf.py:857 msgid "" "Determines whether the bot will defend itself\n" " against invalid command-flooding." msgstr "" "Détermine si le bot se défendra contre le flood de commandes invalides." #: src/conf.py:860 msgid "" "Determines how many invalid commands users\n" " are allowed per minute. If a user sends more than this many invalid\n" " commands in any 60 second period, they will be ignored for\n" " supybot.abuse.flood.command.invalid.punishment seconds. Typically, " "this\n" " value is lower than supybot.abuse.flood.command.maximum, since it's far\n" " less likely (and far more annoying) for users to flood with invalid\n" " commands than for them to flood with valid commands." msgstr "" "Détermine le nombre maximum de commandes invalies qu'une personne peut " "envoyer en une minute. Si une personne dépasse cette limite, elle sera " "ignoré supybot.abuse.flood.command.invalid.punishment secondes. " "Typiquement, cette valeur est plus petite que value is lower than " "supybot.abuse.flood.command.maximum, car il est plus probable que les " "personnes floodent avec des commandes invalides qu'avec des commandes " "valides." #: src/conf.py:868 msgid "" "Determines how many seconds the bot\n" " will ignore users who flood it with invalid commands. Typically, this\n" " value is higher than supybot.abuse.flood.command.punishment, since it's " "far\n" " less likely (and far more annoying) for users to flood with invalid\n" " commands than for them to flood with valid commands." msgstr "" "Détermine combien de secondes le bot ignorera les personnes qui le " "floodent de commandes invalides. Typiquement,cette valeur est plus " "grande que value is lower than supybot.abuse.flood.command.punishment, " "car il est plus probable que les personnes floodent avec des commandes " "invalides qu'avec des commandes valides." #: src/conf.py:874 msgid "" "Determines whether the bot will notify people\n" " that they're being ignored for invalid command flooding." msgstr "" "Détermine si le bot notifiera les personnes lorsqu'elles se font ignorer " "pour flood de commande." #: src/conf.py:883 msgid "" "Determines the default length of time a\n" " driver should block waiting for input." msgstr "" "Détermine la durée par défaut durant laquelle un moteur réseau peut être " "bloquant, en attente de données entrantes." #: src/conf.py:891 msgid "" "Determines what driver module the \n" " bot will use. The default is Socket which is simple and stable \n" " and supports SSL. Twisted doesn't work if the IRC server which \n" " you are connecting to has IPv6 (most of them do)." msgstr "" "Détermine quel module « pilote » le bot utilise pour se connecter à IRC. Par " "défaut il s'agit de Socket, qui est simple et stable, et supporte SSL. " "Twisted ne fonctionne pas sur le serveur IRC auquel vous vous connectez " "supporte IPv6 (ce qui est le cas de la plupart)." #: src/conf.py:897 msgid "" "Determines the maximum time the bot will\n" " wait before attempting to reconnect to an IRC server. The bot may, of\n" " course, reconnect earlier if possible." msgstr "" "Détermine le temps maximum durant lequel le bot attendra avant de se " "reconnecter à un serveur IRC. Le bot peut, bien sûr, se reconnecter plus " "tôt, si c'est possible." #: src/conf.py:949 msgid "" "Determines what directory configuration data is\n" " put into." msgstr "Détermine dans quel répertoire les données de configuration sont." #: src/conf.py:952 msgid "Determines what directory data is put into." msgstr "Détermine dans quel répertoire les données sont." #: src/conf.py:954 msgid "" "Determines what directory backup data is put\n" " into. Set it to /dev/null to disable backup (it is a special value,\n" " so it also works on Windows and systems without /dev/null)." msgstr "" "Détermine dans quel dossier les sauvegardes seront effectuées. Définissez " "cette variable à /dev/null pour désactiver la sauvegarde (c'est une valeur " "spéciale, donc elle marchera également sur Windows et les systèmes qui n'ont " "pas de /dev/null)." #: src/conf.py:961 msgid "" "Determines what directory temporary files\n" " are put into." msgstr "Détermine dans quel répertoire les fichiers temporaires sont." #: src/conf.py:964 msgid "" "Determines what directory files of the\n" " web server (templates, custom images, ...) are put into." msgstr "" "Détermine dans quel répertoire seront les fichiers du serveur web " "(templates, images personnalisées, ...)." #: src/conf.py:977 msgid "" "Determines what directories\n" " the bot will look for plugins in. Accepts a comma-separated list of\n" " strings.\n" " This means that to add another directory, you can nest the former value " "and\n" " add a new one. E.g. you can say: bot: 'config supybot.directories." "plugins\n" " [config supybot.directories.plugins], newPluginDirectory'." msgstr "" "Détermine dans quel répertoire le bot recherchera les plugins. Accepte " "une liste de chaînes séparées par des virgules. Ce qui signifie que si " "vous voulez ajouter un autre répertoire, vous pouvez imbriquer la forme " "actuelle la nouvelle. Par exemple, vous pouvez dire : 'config supybot." "directories.plugins [config supybot.directories.plugins], " "nouveauRepertoireDePlugins'." #: src/conf.py:985 msgid "" "Determines what plugins will\n" " be loaded." msgstr "Détermine quels plugins seront chargés." #: src/conf.py:988 msgid "" "Determines whether the bot will always load\n" " important plugins (Admin, Channel, Config, Misc, Owner, and User)\n" " regardless of what their configured state is. Generally, if these " "plugins\n" " are configured not to load, you didn't do it on purpose, and you still\n" " want them to load. Users who don't want to load these plugins are " "smart\n" " enough to change the value of this variable appropriately :)" msgstr "" "Détermine si le bot chargera toujours les plugins importants (Admin, " "Channel, Config, Misc, Owner, et User), peu importe l'état de la " "configuration. Généralement, si ces plugins sont configurés pour ne pas " "être chargés, vous ne pourrez plus les charger, et finalement, vous " "voudrez le faire. Les personnes qui ne souhaitent pas charger ces plugins " "sont suffisement intelligents pour changer la valeur de cette variable de " "façon appropriée :)" #: src/conf.py:1016 msgid "" "Determines what databases are available for use. If this\n" " value is not configured (that is, if its value is empty) then sane " "defaults\n" " will be provided." msgstr "" "Détermine quelles bases de données sont disponibles. si cette valeur " "n'est pas configurée (c'est à dire si sa valeur est vide), alors une " "manière par défaut saine sera utilisée." #: src/conf.py:1022 msgid "" "Determines what filename will be used\n" " for the users database. This file will go into the directory specified " "by\n" " the supybot.directories.conf variable." msgstr "" "Détermine quel nom de fichier sera utilisé pour la base de données des " "utilisateurs. Ce fichiers ira dans le répertoire spécifié par la variable " "supybot.directories.conf." #: src/conf.py:1026 msgid "" "Determines how long it takes identification to\n" " time out. If the value is less than or equal to zero, identification " "never\n" " times out." msgstr "" "Détermine combien de temps au maximum une personne peut rester identifié. " "Si cette valeur est inférieure ou égale à zéro, l'identification " "n'expirera jamais." #: src/conf.py:1030 msgid "" "Determines whether the bot will allow users to\n" " unregister their users. This can wreak havoc with already-existing\n" " databases, so by default we don't allow it. Enable this at your own " "risk.\n" " (Do also note that this does not prevent the owner of the bot from " "using\n" " the unregister command.)\n" " " msgstr "" "Détermine si le bot autorisera les utilisateurs à se désenregistrer. Ceci " "peut provoquer des dégâts avec des bases de données déjà existantes, " "donc, nous ne l'autorisons pas par défaut. Activez ceci à vos risques et " "périls. (Notez également que ceci n'empêche pas le propriétaire du bot de " "se désenregistrer.)" #: src/conf.py:1039 msgid "" "Determines what filename will be used\n" " for the ignores database. This file will go into the directory " "specified\n" " by the supybot.directories.conf variable." msgstr "" "Détermine quel nom de fichier sera utilisé pour stocker la base de " "données d'ignorance. Ce fichiers ira dans le répertoire spécifié par la " "variable supybot.directories.conf." #: src/conf.py:1045 msgid "" "Determines what filename will be used\n" " for the channels database. This file will go into the directory " "specified\n" " by the supybot.directories.conf variable." msgstr "" "Détermine quel nom de fichier sera utilisé pour stocker la base de " "données de canaux. Ce fichiers ira dans le répertoire spécifié par la " "variable supybot.directories.conf." #: src/conf.py:1077 msgid "" "Determines whether the bot will require user\n" " registration to use 'add' commands in database-based Supybot\n" " plugins." msgstr "" "Détermine si le bot requiert que les utilisateurs/trices soient enregistrées " "pour utiliser les commandes 'add' des plugins Supybot basés sur des bases de " "données." #: src/conf.py:1081 msgid "" "Determines whether database-based plugins that\n" " can be channel-specific will be so. This can be overridden by " "individual\n" " channels. Do note that the bot needs to be restarted immediately after\n" " changing this variable or your db plugins may not work for your " "channel;\n" " also note that you may wish to set\n" " supybot.databases.plugins.channelSpecific.link appropriately if you " "wish\n" " to share a certain channel's databases globally." msgstr "" "Détermine si des plugins basés sur une base de données peuvent être " "spécifiques à un canal. Ceci peut être surchargé pour chaque canal. Notez " "que le bot doit être redémarré immédiatement après avoir changé cette " "variable, ou vos plugins basés sur une base de données pourraient ne pas " "marcher pour votre canal ; notez également qui vous pouvez vouloir " "définir supybot.databases.plugins.channelSpecific.link de manière " "appropriée si vous voulez partager les bases de données de certains " "canaux." #: src/conf.py:1089 msgid "" "Determines what channel global\n" " (non-channel-specific) databases will be considered a part of. This is\n" " helpful if you've been running channel-specific for awhile and want to " "turn\n" " the databases for your primary channel into global databases. If\n" " supybot.databases.plugins.channelSpecific.link.allow prevents linking, " "the\n" " current channel will be used. Do note that the bot needs to be " "restarted\n" " immediately after changing this variable or your db plugins may not " "work\n" " for your channel." msgstr "" "Détermine quelles bases de données de canaux seront considérées comme " "faisant partie de la base de données globale. Ceci est utile si vous " "utilisez une base de données spécifique à un canal depuis longtemps, et " "que vous voulez faire passer les bases de données de votre canal " "principal en bases de données globales. Si supybot.databases.plugins." "channelSpecific.link.allow empêche la liaison, le canal courant sera " "utilisé. Notez que le bot doit être redémarré immédiatement après avoir " "changé cette variable ou vos plugins basés sur une base de données " "pourraient ne pas fonctionner." #: src/conf.py:1098 msgid "" "Determines whether another channel's global\n" " (non-channel-specific) databases will be allowed to link to this " "channel's\n" " databases. Do note that the bot needs to be restarted immediately " "after\n" " changing this variable or your db plugins may not work for your " "channel.\n" " " msgstr "" "Détermine si les bases de données globales d'un autre canal peuvent êtres " "liées à celles de ce canal. Notez que le bot doit être redémarré " "immédiatement après avoir changé cette variable ou vos plugins basés sur " "une base de données pourraient ne pas marcher pour votre canal." #: src/conf.py:1116 msgid "" "Determines\n" " whether CDB databases will be allowed as a database implementation." msgstr "" "Détermine si les bases de données CDB seront autorisées comme " "implémentation de base de données." #: src/conf.py:1119 msgid "" "Determines how often CDB databases will have\n" " their modifications flushed to disk. When the number of modified " "records\n" " is greater than this fraction of the total number of records, the " "database\n" " will be entirely flushed to disk." msgstr "" "Détermine la fréquence de sauvegarde des bases de données CDB sur le " "disque. Lorsque le nombre d'enregistrements modifiés est plus grand que " "le nombre d'enregistrements non modifiés, la base de données sera " "entièrement enregistrée sur le disque." #: src/conf.py:1211 msgid "" "Determines what will be used as the\n" " default banmask style." msgstr "Détermine le style de masque de bannissement utilisé par défaut." #: src/conf.py:1215 msgid "" "Determines whether the bot will strictly\n" " follow the RFC; currently this only affects what strings are\n" " considered to be nicks. If you're using a server or a network that\n" " requires you to message a nick such as services@this.network.server\n" " then you you should set this to False." msgstr "" "Détermine si le bot suivra strictement la RFC ; actuellement, cela " "n'affecte que les chaînes considérées comme des nicks. Si vous utilisez " "un serveur ou un réseau qui requiert que vous envoyiez un message à un " "nick tel queservices@this.network.server, vous devriez défini ceci à " "False." #: src/conf.py:1222 msgid "" "Determines what certificate file (if any) the bot\n" " will use connect with SSL sockets by default." msgstr "" "Détermine quel fichier de certificat (s’il y en a un) le bot utilisera par " "défaut pour se connecter par SSL." #: src/conf.py:1226 msgid "" "Determines what user modes the bot will request\n" " from the server when it first connects. Many people might choose +i; " "some\n" " networks allow +x, which indicates to the auth services on those " "networks\n" " that you should be given a fake host." msgstr "" "Détermine quels modes d'utilisateur le bot demandera au serveur lorsqu'il " "s'y connecte. Certaines personnes voudront le +i ; certains réseaux " "autorisent +x, ce qui indique aux services du réseau que l'on veut " "'cloaker' (masquer) notre masque d'hôte." #: src/conf.py:1232 msgid "" "Determines what vhost the bot will bind to before\n" " connecting a server (IRC, HTTP, ...) via IPv4." msgstr "" "Détermine quelle vhost le bot bindera avant de se connecter à un serveur " "(IRC, HTTP, ...) par IPv4." #: src/conf.py:1236 msgid "" "Determines what vhost the bot will bind to before\n" " connecting a server (IRC, HTTP, ...) via IPv6." msgstr "" "Détermine quelle vhost le bot bindera avant de se connecter à un serveur " "(IRC, HTTP, ...) par IPv6." #: src/conf.py:1240 msgid "" "Determines how many old messages the bot will\n" " keep around in its history. Changing this variable will not take " "effect\n" " on a network until it is reconnected." msgstr "" "Détermine combien de vieux messages le bot gardera dans son historique. " "Changer cette variable ne prend effet qu'après pour un réseau qu'après une " "reconnexion." #: src/conf.py:1245 msgid "" "A floating point number of seconds to throttle\n" " queued messages -- that is, messages will not be sent faster than once " "per\n" " throttleTime seconds." msgstr "" "Un nombre à virgule flottante, correspondant à la mise en attente des " "messages à envoyé ; c'est à dire que les messages ne seront pas envoyé " "plus vite que 1 par throttleTime secondes." #: src/conf.py:1250 msgid "" "Determines whether the bot will send PINGs to\n" " the server it's connected to in order to keep the connection alive and\n" " discover earlier when it breaks. Really, this option only exists for\n" " debugging purposes: you always should make it True unless you're " "testing\n" " some strange server issues." msgstr "" "Détermine si le bot enverra des PINGs au serveurs auxquels il est " "connecté dans le but de garder la connexion active et détecter plus tôt " "lorsqu'elle est rompue. En fait, cette option existe surtout pour le " "débogage : vous devriez toujours laisser cette option à True excepté si " "il vous arrive des trucs bizarres avec le serveur." #: src/conf.py:1257 msgid "" "Determines the number of seconds between sending\n" " pings to the server, if pings are being sent to the server." msgstr "" "Détermine le nombre de secondes entre deux envois de PING au serveur, si " "les pings sont envoyés au serveur." #: src/conf.py:1262 msgid "" "Determines whether the bot will refuse\n" " duplicated messages to be queued for delivery to the server. This is a\n" " safety mechanism put in place to prevent plugins from sending the same\n" " message multiple times; most of the time it doesn't matter, unless " "you're\n" " doing certain kinds of plugin hacking." msgstr "" "Détermine si le bot refusera de dupliquer les messages à mettre dans la " "queue d'envoi un serveur. C'est un mécanisme de sécurité mis en place " "pour éviter que les plugins envoient le même message plusieurs fois ; la " "plupart du temps, vous n'avez pas à vous en préoccuper, à moins que vous " "ne bidouilliez des plugins." #: src/conf.py:1270 msgid "" "Determines how many seconds must elapse between\n" " JOINs sent to the server." msgstr "" "Détermine combien de secondes doivent s'écouler entre deux envois de JOIN " "au serveur." #: src/conf.py:1278 msgid "" "Determines how many bytes the bot will\n" " 'peek' at when looking through a URL for a doctype or title or " "something\n" " similar. It'll give up after it reads this many bytes, even if it " "hasn't\n" " found what it was looking for." msgstr "" "Détermine combien d'octets le bot 'regarder' lorsqu'il cherchera le " "doctype, le title, ou autre chose dans une URL. Après avoir lu ces " "octets, le bot abandonnera sa recherche." #: src/conf.py:1302 msgid "" "Determines what HTTP proxy all HTTP requests should go\n" " through. The value should be of the form 'host:port'." msgstr "" "Détermine par quel proxy toutes les requêtes HTTP devraient passer. Cette " "valeur doit être de la forme 'host:port'." #: src/conf.py:1311 msgid "" "Determines whether server certificates\n" " will be verified, which checks whether the server certificate is signed\n" " by a known certificate authority, and aborts the connection if it is not." msgstr "" "Détermine si les certificats serveurs sont vérifiés, ce qui vérifie que le " "certificat est signé par une autorité de certification reconnue, et termine " "la connexion si ce n'est pas le cas." #: src/conf.py:1336 msgid "" "If true, uses IPV6_V6ONLY to disable\n" " forwaring of IPv4 traffic to IPv6 sockets. On *nix, has the same\n" " effect as setting kernel variable net.ipv6.bindv6only to 1." msgstr "" "Si vrai, utilise IPV6_V6ONLY pour désactive le transfère du traffic IPv4 à " "des sockets IPv6. Sur *nix, cela a le même effet que de définir l'option " "noyau net.ipv6.bindv6only à 1" #: src/conf.py:1340 msgid "" "Space-separated list of IPv4 hosts the HTTP server\n" " will bind." msgstr "" "Liste d'adresses IPv4 séparées par des espaces que le serveur HTTP va « " "binder »." #: src/conf.py:1343 msgid "" "Space-separated list of IPv6 hosts the HTTP server will\n" " bind." msgstr "" "Liste d'adresses IPv6 séparées par des espaces que le serveur HTTP va « " "binder »." #: src/conf.py:1346 msgid "" "Determines what port the HTTP server will\n" " bind." msgstr "Détermine à quel port le serveur HTTP va s'attacher." #: src/conf.py:1349 msgid "" "Determines whether the server will stay\n" " alive if no plugin is using it. This also means that the server will\n" " start even if it is not used." msgstr "" "Détermine si le serveur restera lancé si aucun plugin ne l'utilise. Cela " "signifie également que le serveur va démarrer même si il n'est pas utilisé." #: src/conf.py:1353 msgid "" "Determines the path of the file served as\n" " favicon to browsers." msgstr "Détermine le chemin du fichier servi comme favicon aux navigateurs." #: src/conf.py:1361 msgid "" "Determines whether the bot will ignore\n" " unidentified users by default. Of course, that'll make it\n" " particularly hard for those users to register or identify with the bot\n" " without adding their hostmasks, but that's your problem to solve." msgstr "" "Détermine si le bot ignorera par défaut les personnes qui ne sont pas " "enregistrées. Bien sûr, cela rendra très difficile pour les utilisateurs/" "trices de s'enregistrer ou de s'identifier, mais c'est votre problème." #: src/conf.py:1368 msgid "" "A string that is the external IP of the bot. If this is the\n" " empty string, the bot will attempt to find out its IP dynamically " "(though\n" " sometimes that doesn't work, hence this variable). This variable is not " "used\n" " by Limnoria and its built-in plugins: see supybot.protocols.irc.vhost /\n" " supybot.protocols.irc.vhost6 to set the IRC bind host, and\n" " supybot.servers.http.hosts4 / supybot.servers.http.hosts6 to set the " "HTTP\n" " server bind host." msgstr "" "Une chaine qui est l'adresse IP externe du bot. Si c'est la chaine vide, le " "bot essayera de détermine son adresse IP dynamiquement (ce qui ne marche pas " "toujours, d'où cette variable). Cette variable n'est pas utilisée par " "Limnoria ou ses plugins par défaut : voyez supybot.protocols.irc.vhost / " "supybot.protocols.irc.vhost6 pour définir le « bind host » pour IRC, et " "supybot.servers.http.hosts4 / supybot.servers.http.hosts6 pour HTTP." #: src/conf.py:1387 msgid "" "Determines what the default timeout for socket\n" " objects will be. This means that *all* sockets will timeout when this " "many\n" " seconds has gone by (unless otherwise modified by the author of the " "code\n" " that uses the sockets)." msgstr "" "Détermine quel timeout est utilisé par défaut pour les sockets. Ceci " "signifie que *toutes* les sockets expireront au bout de cette durée (à " "moins que l'auteur du code n'ait changé cette valeur)." #: src/conf.py:1393 msgid "" "Determines what file the bot should write its PID\n" " (Process ID) to, so you can kill it more easily. If it's left unset (as " "is\n" " the default) then no PID file will be written. A restart is required " "for\n" " changes to this variable to take effect." msgstr "" "Détermine dans quel fichier le bot écrit son PID (Process ID), ce qui " "vous permet de le killer plus facilement. Si cette valeur est laissée " "vide, le PID ne sera écrit dans aucun fichier. Un redémarrage est requis " "pour que cette variable prenne effet." #: src/conf.py:1403 msgid "" "Determines whether the bot will automatically\n" " thread all commands." msgstr "Détermine si le bot threadera automatiquement toutes les commandes." #: src/conf.py:1406 msgid "" "Determines whether the bot will automatically\n" " flush all flushers *very* often. Useful for debugging when you don't " "know\n" " what's breaking or when, but think that it might be logged." msgstr "" "Détermine si le bot videra automatiquement le cache *très* souvent. Utile " "pour déboguer lorsque vous ne savez pas ce qui ne marche pas, mais que " "vous pensez que ça peut être loggué." #: src/httpserver.py:62 msgid "Supybot Web server index" msgstr "Index du serveur Web de Supybot" #: src/httpserver.py:67 msgid "Here is a list of the plugins that have a Web interface:" msgstr "Voici une liste des plugins qui ont une interface Web :" #: src/httpserver.py:295 msgid "" "\n" " This is a default response of the Supybot HTTP server. If you see this\n" " message, it probably means you are developing a plugin, and you have\n" " neither overriden this message or defined an handler for this query." msgstr "" "\n" " Ceci est la réponse par défaut du serveur HTTP de Supybot. Si vous " "voyez ce message, cela signifie probablement que vous êtes en train de " "développer un plugin, et que vous n'avez ni outrepassé ce message ni " "défini un gestionnaire pour cette requête." #: src/httpserver.py:336 msgid "" "\n" " I am a pretty clever IRC bot, but I suck at serving Web pages, " "particulary\n" " if I don't know what to serve.\n" " What I'm saying is you just triggered a 404 Not Found, and I am not\n" " trained to help you in such a case." msgstr "" "\n" " Je suis un robot IRC très intelligent, mais je suis nul dès qu'il s'agit " "de servir des pages Web, en particulier lorsque je ne sais pas quoi " "servir. Ce que j'essaye de dire, c'est que vous venez tout juste de " "déclencher une erreur 404 Not Found, and je ne suis pas entraîné à vous " "aider dans un tel cas." #: src/httpserver.py:356 msgid "Request not handled." msgstr "Requête non gérée." #: src/httpserver.py:360 msgid "No plugins available." msgstr "Aucun plugin disponible." #: src/httpserver.py:378 src/httpserver.py:396 msgid "Request not handled" msgstr "Requête non gérée." #: src/httpserver.py:421 msgid "No favicon set." msgstr "Pas de favicon définie." #: src/ircutils.py:459 msgid "is an op on %L" msgstr "est op sur %L" #: src/ircutils.py:461 msgid "is a halfop on %L" msgstr "est halfop sur %L" #: src/ircutils.py:463 msgid "is voiced on %L" msgstr "est voice sur %L" #: src/ircutils.py:466 msgid "is also on %L" msgstr "est aussi sur %L" #: src/ircutils.py:468 msgid "is on %L" msgstr "est sur %L" #: src/ircutils.py:471 msgid "isn't on any publicly visible channels" msgstr "n'est sur aucun canal visible" #: src/ircutils.py:479 src/ircutils.py:480 src/ircutils.py:486 msgid "" msgstr "" #: src/ircutils.py:488 msgid " %s is away: %s." msgstr "%s est absent/e : %s." #: src/ircutils.py:493 msgid " identified" msgstr " identifié" #: src/ircutils.py:499 msgid "%s (%s) has been%s on server %s since %s (idle for %s). %s %s.%s" msgstr "" "%s (%s) est% sur le serveur %s depuis %s (inactif/ive pendant %s). %s %s.%s" #: src/ircutils.py:503 msgid "%s (%s) has been%s on server %s and disconnected on %s." msgstr "%s (%s) est% sur le serveur %s et s'est déconnect(e) à %s" #: src/questions.py:60 msgid "Sorry, that response was not an option." msgstr "Désolé, cette réponse n'est pas l'une des options." #: src/questions.py:109 msgid "Sorry, you must enter a value." msgstr "Désolé, vous devez entrer une valeur." #: src/questions.py:129 msgid "Enter password: " msgstr "Entrez un mot de passe : " #: src/questions.py:131 msgid "Re-enter password: " msgstr "Entrez à nouveau le mot de passe : " #: src/questions.py:144 msgid "Passwords don't match." msgstr "Les mots de passe ne correspondent pas." #: src/registry.py:218 #, fuzzy msgid "%r is not a valid entry in %r" msgstr "%s n'est pas un(e) %s valide." #: src/registry.py:516 msgid "Value must be either True or False (or On or Off), not %r." msgstr "La valeur doit être True ou False (ou On ou Off), pas %r." #: src/registry.py:533 msgid "Value must be an integer, not %r." msgstr "La valeur doit être un entrier, pas %r." #: src/registry.py:543 msgid "Value must be a non-negative integer, not %r." msgstr "La valeur doit être un entier non négatif, pas %r." #: src/registry.py:552 msgid "Value must be positive (non-zero) integer, not %r." msgstr "La valeur doit être un entier positif (non nul), pas %r." #: src/registry.py:561 msgid "Value must be a floating-point number, not %r." msgstr "La valeur doit être un nombre à virgule flottante, pas %r." #: src/registry.py:577 msgid "Value must be a floating-point number greater than zero, not %r." msgstr "" "La valeur doit être un nombre à virgule flottante plus grand que zéro, pas " "%r." #: src/registry.py:588 msgid "Value must be a floating point number in the range [0, 1], not %r." msgstr "" "La valeur doit être un nombre à virgule flottante compris entre 0 et 1, pas " "%r." #: src/registry.py:603 msgid "Value should be a valid Python string, not %r." msgstr "La valeur doit être une chaîne Python valide, pas %r." #: src/registry.py:637 msgid "Valid values include %L." msgstr "Les valeurs valides sont %L." #: src/registry.py:639 msgid "Valid values include %L, not %%r." msgstr "Les valeurs valides sont %L, pas %%r." #: src/registry.py:714 msgid "Value must be a valid regular expression, not %r." msgstr "La valeur doit être une expression rationnelle valide, pas %r." #: src/utils/gen.py:112 msgid "year" msgstr "année" #: src/utils/gen.py:115 msgid "week" msgstr "semaine" #: src/utils/gen.py:118 msgid "day" msgstr "jour" #: src/utils/gen.py:121 msgid "hour" msgstr "heure" #: src/utils/gen.py:125 msgid "minute" msgstr "minute" #: src/utils/gen.py:128 msgid "second" msgstr "seconde" #: src/utils/gen.py:137 msgid "%s ago" msgstr "il y a %s" #: src/utils/str.py:355 msgid "and" msgstr "et" #~ msgid "" #~ "Determines what driver module the bot\n" #~ " will use. Socket, a simple driver based on timeout sockets, is used " #~ "by\n" #~ " default because it's simple and stable. Twisted is very stable and " #~ "simple,\n" #~ " and if you've got Twisted installed, is probably your best bet." #~ msgstr "" #~ "Détermine quel moteur réseau le bot utilisera. Socket, est un simple " #~ "moteur basé sur des sockets bloquantes à timeout ; c'est celui utilisé " #~ "par défaut. Twisted est très stable et simple, et, si vous avez " #~ "Twisted installé, c'est probablement le meilleur choix." #~ msgid "" #~ "A string that is the external IP of the bot. If this is the\n" #~ " empty string, the bot will attempt to find out its IP dynamically " #~ "(though\n" #~ " sometimes that doesn't work, hence this variable)." #~ msgstr "" #~ "Une chaîne qui est une IP externe du bot. Si elle est vide, le bot " #~ "recherchera cette IP dynamiquement (ce qui, parfois, ne marche pas ; " #~ "dans ce cas, utilisez cette variable)." #~ msgid "" #~ "Determines the content of the robots.txt file,\n" #~ " served on the server to search engine." #~ msgstr "" #~ "Détermine quel sera le contenu du fichier robots.txt, servi par le " #~ "serveur aux moteurs de recherche." #~ msgid "" #~ "Determines what directory backup data is put\n" #~ " into." #~ msgstr "Détermine dans quel répertoire les sauvegardes des données sont." limnoria-2020.03.17/locales/fr.py0000644000175000017500000000724513634634532015732 0ustar valval00000000000000# -*- encoding: utf8 -*- ### # Copyright (c) 2010, Valentin Lorentz # 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. ### """ Supybot utility functions localization in French. """ def pluralize(s): """Returns the plural of s. """ lowered = s.lower() if lowered.endswith('ou') and \ lowered in ['bijou', 'caillou', 'chou', 'genou', 'hibou', 'joujou', 'pou']: return s + 'x' elif lowered.endswith('al') and \ lowered not in ['bal', 'carnaval', 'chacal', 'festival', 'récital', 'régal', 'cal', 'étal', 'aval', 'caracal', 'val', 'choral', 'corral', 'galgal', 'gayal']: return s[0:-2] + 'aux' elif lowered.endswith('ail') and \ lowered not in ['bail', 'corail', 'émail', 'soupirail', 'travail', 'ventail', 'vitrail', 'aspirail', 'fermail']: return s[0:-3] + 'aux' elif lowered.endswith('eau'): return s + 'x' elif lowered == 'pare-feu': return s elif lowered.endswith('eu') and \ lowered not in ['bleu', 'pneu', 'émeu', 'enfeu']: # Note: when 'lieu' is a fish, it has a 's' ; else, it has a 'x' return s + 'x' else: return s + 's' def depluralize(s): """Returns the singular of s.""" lowered = s.lower() if lowered.endswith('aux') and \ lowered in ['baux', 'coraux', 'émaux', 'soupiraux', 'travaux', 'ventaux', 'vitraux', 'aspiraux', 'fermaux']: return s[0:-3] + 'ail' elif lowered.endswith('aux'): return s[0:-3] + 'al' else: return s[0:-1] def ordinal(i): """Returns i + the ordinal indicator for the number. Example: ordinal(3) => '3ème' """ i = int(i) if i == 1: return '1er' else: return '%sème' % i def be(i): """Returns the form of the verb 'être' based on the number i.""" # Note: this function is used only for the third person if i == 1: return 'est' else: return 'sont' def has(i): """Returns the form of the verb 'avoir' based on the number i.""" # Note: this function is used only for the third person if i == 1: return 'a' else: return 'ont' limnoria-2020.03.17/locales/it.po0000644000175000017500000016106013634634532015721 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Supybot-fr\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2014-07-05 00:13+0200\n" "Last-Translator: skizzhg \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/callbacks.py:184 msgid "Error: " msgstr "Errore: " #: src/callbacks.py:198 msgid "Error: I tried to send you an empty message." msgstr "Errore: ho cercato di inviarti un messaggio vuoto." #: src/callbacks.py:292 msgid "Missing \"%s\". You may want to quote your arguments with double quotes in order to prevent extra brackets from being evaluated as nested commands." msgstr "\"%s\" mancante. Dovresti racchiudere gli argomenti tra virgolette per evitare che le parentesi vengano interpretate come comandi nidificati." #: src/callbacks.py:322 msgid "\"|\" with nothing preceding. I obviously can't do a pipe with nothing before the |." msgstr "\"|\" con niente che la precede. Ovviamente non posso usare una pipe senza nulla prima di |." #: src/callbacks.py:330 msgid "Spurious \"%s\". You may want to quote your arguments with double quotes in order to prevent extra brackets from being evaluated as nested commands." msgstr "\"%s\" eccedente. Dovresti racchiudere gli argomenti tra virgolette per evitare che le parentesi vengano interpretate come comandi nidificati." #: src/callbacks.py:339 msgid "\"|\" with nothing following. I obviously can't do a pipe with nothing after the |." msgstr "\"|\" con niente che la segue. Ovviamente non posso usare una pipe senza nulla dopo di |." #: src/callbacks.py:524 msgid "%s is not a valid %s." msgstr "%s non è un %s valido." #: src/callbacks.py:526 msgid "That's not a valid %s." msgstr "Questo non è un %s valido." #: src/callbacks.py:604 msgid "You've attempted more nesting than is currently allowed on this bot." msgstr "Hai tentato di nidificare più di quanto sia attualmente consentito." #: src/callbacks.py:773 msgid "The command %q is available in the %L plugins. Please specify the plugin whose command you wish to call by using its name as a command before %q." msgstr "Il comando %q è disponibile nei plugin %L. Specifica in quale plugin si trova quello che desideri usare aggiungendo il suo nome prima di %q." #: src/callbacks.py:869 src/callbacks.py:882 msgid "(XX more messages)" msgstr "(XX altri messaggi)" #: src/callbacks.py:914 msgid "more message" msgstr "altro messaggio" #: src/callbacks.py:916 msgid "more messages" msgstr "altri messaggi" #: src/callbacks.py:1033 msgid "" "Determines what commands are currently disabled. Such\n" " commands will not appear in command lists, etc. They will appear not even\n" " to exist." msgstr "" "Determina quali comandi sono attualmente disabilitati. Questi non appariranno\n" " nell'elenco dei comandi, ecc.; sarà come se non esistessero." #: src/callbacks.py:1231 msgid "Invalid arguments for %s." msgstr "Argomenti non validi per %s." #: src/callbacks.py:1257 msgid "The %q command has no help." msgstr "Il comando %q non ha un help." #: src/commands.py:228 msgid "integer" msgstr "intero" #: src/commands.py:239 msgid "non-integer value" msgstr "valore non intero" #: src/commands.py:250 msgid "floating point number" msgstr "numero in virgola mobile" #: src/commands.py:259 msgid "positive integer" msgstr "intero positivo" #: src/commands.py:263 msgid "non-negative integer" msgstr "intero non negativo" #: src/commands.py:266 msgid "index" msgstr "indice" #: src/commands.py:291 msgid "number of seconds" msgstr "numero di secondi" #: src/commands.py:298 msgid "boolean" msgstr "booleano" #: src/commands.py:312 src/commands.py:320 src/commands.py:328 #: src/commands.py:336 msgid "do that" msgstr "fare ciò" #: src/commands.py:316 src/commands.py:324 src/commands.py:332 #: src/commands.py:340 msgid "I'm not even in %s." msgstr "Non sono in %s." #: src/commands.py:318 msgid "I need to be voiced to %s." msgstr "Devo avere il voice per %s." #: src/commands.py:326 msgid "I need to be halfopped to %s." msgstr "Devo essere halfop per %s." #: src/commands.py:334 msgid "I need to be opped to %s." msgstr "Devo essere op per %s." #: src/commands.py:344 msgid "I need to be at least halfopped to %s." msgstr "Devo almeno essere halfop per %s." #: src/commands.py:362 msgid "nick or hostmask" msgstr "nick o hostmask" #: src/commands.py:414 src/commands.py:417 msgid "regular expression" msgstr "espressione regolare" #: src/commands.py:428 msgid "That nick is too long for this server." msgstr "Il nick è troppo lungo per questo server." #: src/commands.py:477 src/commands.py:496 msgid "I'm not in %s." msgstr "Non sono in %s." #: src/commands.py:481 msgid "This command may only be given in a channel that I am in." msgstr "Questo comando può essere dato solo in un canale in cui sono presente." #: src/commands.py:494 msgid "You must be in %s." msgstr "Devo essere in %s." #: src/commands.py:498 msgid "channel" msgstr "canale" #: src/commands.py:505 msgid "%s is not in %s." msgstr "%s non è in %s." #: src/commands.py:539 msgid "You must not give the empty string as an argument." msgstr "Non puoi darmi una stringa vuota come argomento." #: src/commands.py:556 msgid "This message must be sent in a channel." msgstr "Questo messaggio va inviato in canale." #: src/commands.py:602 msgid "http url" msgstr "URL HTTP" #: src/commands.py:609 msgid "command name" msgstr "nome comando" #: src/commands.py:617 msgid "ip" msgstr "IP" #: src/commands.py:623 msgid "letter" msgstr "lettera" #: src/commands.py:655 msgid "plugin" msgstr "plugin" #: src/commands.py:663 msgid "irc color" msgstr "colore IRC" #: src/conf.py:104 msgid "" "Determines whether this plugin is loaded\n" " by default." msgstr "Determina se questo plugin sia caricato in modo predefinito." #: src/conf.py:108 msgid "" "Determines whether this plugin is\n" " publicly visible." msgstr "Determina se questo plugin sia visibile pubblicamente." #: src/conf.py:202 msgid "Determines the bot's default nick." msgstr "Determina il nick predefinito del bot." #: src/conf.py:205 msgid "" "Determines what alternative\n" " nicks will be used if the primary nick (supybot.nick) isn't available. A\n" " %s in this nick is replaced by the value of supybot.nick when used. If no\n" " alternates are given, or if all are used, the supybot.nick will be perturbed\n" " appropriately until an unused nick is found." msgstr "" "Determina quali nick alternativi utilizzare se il principale (supybot.nick)\n" " non è disponibile; un %s in questo nick è sostituito dal valore di supybot.nick.\n" " Se non è fornita alcuna alternativa, o sono tutti in uso, supybot.nick sarà\n" " modificato in modo appropriato finché ne trova uno non utilizzato." #: src/conf.py:212 msgid "" "Determines the bot's ident string, if the server\n" " doesn't provide one by default." msgstr "Determina l'ident (ciò che precede @ nella hostmask) nel caso in cui il server non ne fornisca una." #: src/conf.py:223 msgid "" "Determines the user the bot sends to the server.\n" " A standard user using the current version of the bot will be generated if\n" " this is left empty." msgstr "" "Determina il nome utente inviato al server dal bot. Se lasciato vuoto, ne verrà\n" " generato uno standard utilizzando la versione del bot." #: src/conf.py:231 msgid "Determines what networks the bot will connect to." msgstr "Determina su quali reti si connetterà il bot." #: src/conf.py:273 msgid "" "Determines what password will be used on %s. Yes, we know that\n" " technically passwords are server-specific and not network-specific,\n" " but this is the best we can do right now." msgstr "" "Determina quale password utilizzare su %s. Sì, sappiamo che tecnicamente le password\n" " sono specifiche per il server e non per la rete, ma per ora è il meglio che possiamo fare." #: src/conf.py:277 msgid "" "Determines what servers the bot will connect to for %s. Each will\n" " be tried in order, wrapping back to the first when the cycle is\n" " completed." msgstr "" "Determina a quali server si connetterà il bot per %s. Verranno provati in\n" " ordine tornando al primo quando il ciclo è stato completato." #: src/conf.py:281 msgid "Determines what channels the bot will join only on %s." msgstr "Determina in quali canali di %s entrerà il bot." #: src/conf.py:284 msgid "" "Determines whether the bot will attempt to connect with SSL\n" " sockets to %s." msgstr "Determina se il bot tenterà di connettersi a %s tramite un socket SSL." #: src/conf.py:287 msgid "" "Determines what key (if any) will be used to join the\n" " channel." msgstr "Determina quale password (eventuale) verrà usata per entrare in canale." #: src/conf.py:289 msgid "" "Determines\n" " what nick the bot will use on this network. If empty, defaults to\n" " supybot.nick." msgstr "" "Determina che nick userà il bot su questa rete. Se lasciato vuoto assegna il valore di supybot.nick." #: src/conf.py:294 msgid "" "Determines what SASL username will be used on %s. This should\n" " be the bot's account name. Due to the way SASL works, you can't use\n" " any grouped nick." msgstr "" "Determina quale nome utente SASL utilizzare su %s; dovrebbe essere il nome usato per\n" " l'account del bot. A causa del funzionamento di SASL, non è possibile usare\n" " un nick raggruppato." #: src/conf.py:298 msgid "Determines what SASL password will be used on %s." msgstr "Determina quale password per SASL sarà usata su %s." #: src/conf.py:318 msgid "" "Determines how timestamps\n" " printed for human reading should be formatted. Refer to the Python\n" " documentation for the time module to see valid formatting characters for\n" " time formats." msgstr "" "Determina come formattare i timestamp affinché possano essere comprensibli.\n" " Per sapere quali sono i formati validi fai riferimento alla documentazione\n" " di Python per il modulo time." #: src/conf.py:332 msgid "" "Determines whether elapsed times will be given\n" " as \"1 day, 2 hours, 3 minutes, and 15 seconds\" or as \"1d 2h 3m 15s\"." msgstr "" "Determina se il tempo trascorso sarà espresso nella forma \"1 giorno, 2 ore,\n" " 3 minuti e 15 secondi\" o \"1d 2h 3m 15s\"." #: src/conf.py:342 msgid "" "Determines the absolute maximum length of\n" " the bot's reply -- no reply will be passed through the bot with a length\n" " greater than this." msgstr "" "Determina la lunghezza massima delle risposte; il bot non invierà alcuna risposta che supera questo valore." #: src/conf.py:347 msgid "" "Determines whether the bot will break up long\n" " messages into chunks and allow users to use the 'more' command to get the\n" " remaining chunks." msgstr "" "Determina se il bot separerà i messaggi lunghi in più parti permettendo\n" " agli utenti di utilizzare il comando \"more\" per ottenere il resto." #: src/conf.py:352 msgid "" "Determines what the maximum number of\n" " chunks (for use with the 'more' command) will be." msgstr "Determina il numero massimo di parti (da utilizzare con il comando \"more\")." #: src/conf.py:356 msgid "" "Determines how long individual chunks\n" " will be. If set to 0, uses our super-tweaked,\n" " get-the-most-out-of-an-individual-message default." msgstr "" "Determina la lunghezza di ogni singolo pezzo. Se impostata a 0 permette\n" " di usare il nostro super algoritmo per ottenere il più possibile da ogni messaggio." #: src/conf.py:361 msgid "" "Determines how many mores will be sent\n" " instantly (i.e., without the use of the more command, immediately when\n" " they are formed). Defaults to 1, which means that a more command will be\n" " required for all but the first chunk." msgstr "" "Determina quanti pezzi saranno inviati immediatamente (ovvero senza utilizzare il comando\n" " \"more\"). Impostato a 1 in modo predefinito, per cui viene ne viene visualizzato uno solo." #: src/conf.py:367 msgid "" "Determines whether the bot will send\n" " multi-message replies in a single message or in multiple messages. For\n" " safety purposes (so the bot is less likely to flood) it will normally send\n" " everything in a single message, using mores if necessary." msgstr "" "Determina se il bot invierà le risposte multiple in uno o più messaggi. Per\n" " ragioni di sicurezza (il bot è meno suscettibile a flood) mostrerà tutto\n" " in un singolo messaggio e utilizzando \"more\" se necessario." #: src/conf.py:373 msgid "" "Determines whether the bot will reply with an\n" " error message when it is addressed but not given a valid command. If this\n" " value is False, the bot will remain silent, as long as no other plugins\n" " override the normal behavior." msgstr "" "Determines se il bot risponderà con un messaggio di errore quando è chiamato\n" " ma non si fornisce un comando valido. Se impostato a False rimarrà silenzioso,\n" " a meno che altri plugin modifichino questo comportamento." #: src/conf.py:380 msgid "" "Determines whether error messages that result\n" " from bugs in the bot will show a detailed error message (the uncaught\n" " exception) or a generic error message." msgstr "" "Determina se i messaggi di errore causati da bug del bot saranno mostrati\n" " in modo dettagliato (eccezione non gestita) o con un messaggio di errore generico." #: src/conf.py:384 msgid "" "Determines whether the bot will send error\n" " messages to users in private. You might want to do this in order to keep\n" " channel traffic to minimum. This can be used in combination with\n" " supybot.reply.error.withNotice." msgstr "" "Determina se il bot invierà i messaggi di errore in privato; utile per ridurre il\n" " traffico del canale. Può essere usato in combinazione con supybot.reply.error.withNotice." #: src/conf.py:389 msgid "" "Determines whether the bot will send error\n" " messages to users via NOTICE instead of PRIVMSG. You might want to do this\n" " so users can ignore NOTICEs from the bot and not have to see error\n" " messages; or you might want to use it in combination with\n" " supybot.reply.errorInPrivate so private errors don't open a query window\n" " in most IRC clients." msgstr "" "Determina se il bot invierà i messaggi di errore tramite NOTICE anziché PRIVMSG.\n" " Si può volerlo impostare in modo che gli utenti possano ignorare i NOTICE\n" " provenienti dal bot oppure usarlo in combinazione con supybot.reply.errorInPrivate\n" " affinché gli errori non causino l'apertura di una finestra privata nel client IRC." #: src/conf.py:396 msgid "" "Determines whether the bot will send an error\n" " message to users who attempt to call a command for which they do not have\n" " the necessary capability. You may wish to make this True if you don't want\n" " users to understand the underlying security system preventing them from\n" " running certain commands." msgstr "" "Determina se il bot invierà un messaggio di errore agli utenti che cercano di usare\n" " un comando per cui non hanno le capacità necessarie. Si può volerlo impostare a\n" " True per evitare che vengano a conoscenza del sistema di sicurezza sottostante\n" " che impedisce loro di eseguire certi comandi." #: src/conf.py:403 msgid "" "Determines whether the bot will reply\n" " privately when replying in a channel, rather than replying to the whole\n" " channel." msgstr "Determina se il bot risponderà in privato piuttosto che in canale." #: src/conf.py:408 msgid "" "Determines whether the bot will reply with a\n" " notice when replying in a channel, rather than replying with a privmsg as\n" " normal." msgstr "Determina se il bot risponderà con un notice piuttosto che in privato." #: src/conf.py:414 msgid "" "Determines whether the bot will reply with a\n" " notice when it is sending a private message, in order not to open a /query\n" " window in clients. This can be overridden by individual users via the user\n" " configuration variable reply.withNoticeWhenPrivate." msgstr "" "Determina se il bot risponderà con un notice quando gli si invia un messaggio privato\n" " per evitare di aprire una finestra nel client (una /query). Può essere modificato\n" " per singolo utente tramite la variabile di configurazione reply.withNoticeWhenPrivate." #: src/conf.py:420 msgid "" "Determines whether the bot will always prefix\n" " theuser's nick to its reply to that user's command." msgstr "" "Determina se il bot userà sempre il nick dell'utente come prefisso della risposta al comando richiesto." #: src/conf.py:424 msgid "" "Determines whether the bot should attempt to\n" " reply to all messages even if they don't address it (either via its nick\n" " or a prefix character). If you set this to True, you almost certainly want\n" " to set supybot.reply.whenNotCommand to False." msgstr "" "Determina se il bot tenterà di rispondere a tutti i messaggi anche se non richiamato\n" " (tramite il nick o il prefisso). Se impostato a True, si vorrà certamente\n" " impostare supybot.reply.whenNotCommand a False." #: src/conf.py:430 msgid "" "Determines whether the bot will allow you to\n" " send channel-related commands outside of that channel. Sometimes people\n" " find it confusing if a channel-related command (like Filter.outfilter)\n" " changes the behavior of the channel but was sent outside the channel\n" " itself." msgstr "" "Determina se il bot permetterà di inviare comandi relativi al canale al di\n" " fuori dello stesso. Talvolta gli utenti trovano disorientante che un\n" " comando (come Filter.outfilter) modifichi il comportamento del canale\n" " ma è stato inviato fuori da questo." #: src/conf.py:437 msgid "" "Determines whether the bot will unidentify\n" " someone when that person changes their nick. Setting this to True\n" " will cause the bot to track such changes. It defaults to False for a\n" " little greater security." msgstr "" "Determina se il bot de-identificherà un utente quando questo cambia nick.\n" " Impostandolo a True, il bot terrà traccia di questi cambiamenti; è\n" " su False in modo predefinito per migliorare leggermente la sicurezza." #: src/conf.py:443 msgid "" "Determines whether the bot will always join a\n" " channel when it's invited. If this value is False, the bot will only join\n" " a channel if the user inviting it has the 'admin' capability (or if it's\n" " explicitly told to join the channel using the Admin.join command)." msgstr "" "Determina se il bot entrerà sempre in un canale in cui è stato invitato. Se\n" " impostato a False entrerà solo se l'utente che lo invita ha la capacità \"admin\"\n" " (o se gli è stato esplicitamente detto di farlo tramite il comando Admin.join)." #: src/conf.py:449 msgid "" "Supybot normally replies with the full help\n" " whenever a user misuses a command. If this value is set to True, the bot\n" " will only reply with the syntax of the command (the first line of the\n" " help) rather than the full help." msgstr "" "Quando un utente commette un errore inviando un comando, Supybot risponde\n" " solitamente con l'help completo. Se questo valore è impostato a True,\n" " il bot risponderà solo con la sintassi del comando (la prima riga dell'help)." #: src/conf.py:463 msgid "" "Determines what prefix characters the bot will\n" " reply to. A prefix character is a single character that the bot will use\n" " to determine what messages are addressed to it; when there are no prefix\n" " characters set, it just uses its nick. Each character in this string is\n" " interpreted individually; you can have multiple prefix chars\n" " simultaneously, and if any one of them is used as a prefix the bot will\n" " assume it is being addressed." msgstr "" "Definisce il prefisso con il quale risponderà il bot. Un prefisso è un carattere\n" " che serve a determinare quali messaggi siano indirizzati ad esso; quando non\n" " c'è un prefisso viene utilizzato il suo nick. Ogni carattere è interpretato\n" " individualmente ed è possibile impostarne più di uno." #: src/conf.py:472 msgid "" "Determines what strings the\n" " bot will reply to when they are at the beginning of the message. Whereas\n" " prefix.chars can only be one character (although there can be many of\n" " them), this variable is a space-separated list of strings, so you can\n" " set something like '@@ ??' and the bot will reply when a message is\n" " prefixed by either @@ or ??." msgstr "" "Determina a quali stringhe risponderà il bot quando queste sono all'inizio del\n" " messaggio. Mentre con \"prefix.chars\" può essere solo un carattere (sebbene\n" " possano essercene diversi) questa variabile è una lista separata da spazi,\n" " per cui è possibile ad esempio impostare \"@@ ??\" e far sì che il bot\n" " risponderà sia ad un messaggio prefissato da \"@@\" sia da \"??\"." #: src/conf.py:479 msgid "" "Determines whether the bot will reply when\n" " people address it by its nick, rather than with a prefix character." msgstr "" "Determina se il bot risponderà quando gli utenti lo richiamano\n" " tramite il nick piuttosto che con un prefisso." #: src/conf.py:482 msgid "" "Determines whether the bot will reply when\n" " people address it by its nick at the end of the message, rather than at\n" " the beginning." msgstr "" "Determina se il bot risponderà quando gli utenti lo richiamano\n" " tramite il nick alla fine del messaggio piuttosto che all'inizio." #: src/conf.py:486 msgid "" "Determines what extra nicks\n" " the bot will always respond to when addressed by, even if its current nick\n" " is something else." msgstr "" "Determina a quali ulteriori nick risponderà il bot, anche se quello attuale è diverso." #: src/conf.py:496 msgid "The operation succeeded." msgstr "L'operazione è riuscita." #: src/conf.py:497 msgid "" "Determines what message the bot replies with when a command succeeded.\n" " If this configuration variable is empty, no success message will be\n" " sent." msgstr "" "Determina con quale messagggio risponderà il bot quando un comando è stato\n" " effettuato con successo. Se questa variabile è vuota non verrà inviato alcun messaggio." #: src/conf.py:502 msgid "" "An error has occurred and has been logged.\n" " Please contact this bot's administrator for more information." msgstr "" "Si è verificato un errore ed è stato registrato. Per ulteriori informazioni\n" " contattare l'amministratore del bot." #: src/conf.py:503 msgid "" "\n" " Determines what error message the bot gives when it wants to be\n" " ambiguous." msgstr "" "\n" " Determina quale messagggio di errore riporterà il bot quando non vuole essere preciso." #: src/conf.py:508 msgid "" "An error has occurred and has been logged.\n" " Check the logs for more informations." msgstr "" "Si è verificato un errore ed è stato registrato. Per ulteriori informazioni controllare i log." #: src/conf.py:509 msgid "" "Determines what error\n" " message the bot gives to the owner when it wants to be ambiguous." msgstr "Determina quale messagggio di errore riporterà il bot all'owner quando non vuole essere preciso." #: src/conf.py:513 msgid "" "Your hostmask doesn't match or your password\n" " is wrong." msgstr "La tua hostmask non corrisponde o la password è errata." #: src/conf.py:514 msgid "" "Determines what message the bot replies with when\n" " someone tries to use a command that requires being identified or having a\n" " password and neither credential is correct." msgstr "" "Determina con quale messagggio risponderà il bot quando si prova a utilizzare un comando\n" " per cui bisogna essere identificati, o ha una password, e le credenziali non sono corrette." #: src/conf.py:520 msgid "" "I can't find %s in my user\n" " database. If you didn't give a user name, then I might not know what your\n" " user is, and you'll need to identify before this command might work." msgstr "" "Non trovo %s nel database degli utenti. Se non fornisci un nome utente non posso\n" " sapere quale sia e dovrai identificarti prima di utilizzare questo comando." #: src/conf.py:523 msgid "" "Determines what error message the bot replies with when someone tries\n" " to accessing some information on a user the bot doesn't know about." msgstr "" "Determina con quale messagggio di errore risponderà il bot quando qualcuno\n" " prova ad accedere a informazioni riguardanti un utente di cui il bot non sa nulla." #: src/conf.py:527 msgid "" "You must be registered to use this command.\n" " If you are already registered, you must either identify (using the identify\n" " command) or add a hostmask matching your current hostmask (using the\n" " \"hostmask add\" command)." msgstr "" "Per utilizzare questo comando devi essere registrato. Se lo sei già devi identificarti\n" " (tramite il comando \"identify\") o aggiungere una hostmask che corrisponda a quella\n" " attuale (tramite il comando \"hostmask add\")." #: src/conf.py:530 msgid "" "Determines what error message the bot\n" " replies with when someone tries to do something that requires them to be\n" " registered but they're not currently recognized." msgstr "" "Determina con quale messagggio risponderà il bot quando si prova a fare qualcosa\n" " per cui bisogna essere registrati ma non si è attualmente riconosciuti." #: src/conf.py:535 msgid "" "You don't have the %s capability. If you\n" " think that you should have this capability, be sure that you are identified\n" " before trying again. The 'whoami' command can tell you if you're\n" " identified." msgstr "" "Non hai la capacità %s; se pensi di averla assicurati di essere identificato prima\n" " di riprovare. Il comando \"whoami\" ti dirà se lo sei." #: src/conf.py:538 msgid "" "Determines what error message is given when the bot\n" " is telling someone they aren't cool enough to use the command they tried to\n" " use." msgstr "" "Determina quale messaggio di errore fornire quando il bot dice a qualcuno\n" " di non avere accesso ad un certo comando." #: src/conf.py:543 msgid "" "You're missing some capability you need.\n" " This could be because you actually possess the anti-capability for the\n" " capability that's required of you, or because the channel provides that\n" " anti-capability by default, or because the global capabilities include\n" " that anti-capability. Or, it could be because the channel or\n" " supybot.capabilities.default is set to False, meaning that no commands are\n" " allowed unless explicitly in your capabilities. Either way, you can't do\n" " what you want to do." msgstr "" "Ti manca qualche capacità necessaria. Questo potrebbe essere dovuto al fatto che\n" " possiedi una anti-capacità per quella richiesta o perché il canale fornisce\n" " quella anti-capacità in modo predefinito o, ancora, perché le capacità globali\n" " includono tale anti-capacità. Oppure supybot.capabilities.default è impostata a\n" " False, per cui non è permesso l'uso di alcun comando a meno che espressamente\n" " specificato nelle tue capacità (da un amministratore). In ogni caso non ti è\n" " permesso fare ciò che intendevi." #: src/conf.py:551 msgid "" "Determines what generic error message is given when the bot is telling\n" " someone that they aren't cool enough to use the command they tried to use,\n" " and the author of the code calling errorNoCapability didn't provide an\n" " explicit capability for whatever reason." msgstr "" "Determina quale messaggio di errore generico fornire quando il bot dice a qualcuno\n" " di non avere accesso ad un certo comando e l'autore del codice che richiama\n" " errorNoCapability non ha fornito un'informazione esplicita sul motivo." #: src/conf.py:557 msgid "" "That operation cannot be done in a\n" " channel." msgstr "L'operazione non può essere eseguita in un canale." #: src/conf.py:558 msgid "" "Determines what error messages the bot sends to people\n" " who try to do things in a channel that really should be done in\n" " private." msgstr "" "Determina quale messaggio di errore invierà il bot a chi tenta di fare qualcosa\n" " in canale e che dovrebbe invece fare in privato." #: src/conf.py:563 msgid "" "This may be a bug. If you think it is,\n" " please file a bug report at\n" " ." msgstr "" "Questo sembra essere un bug. Se pensi lo sia, invia una segnalazione all'indirizzo\n" " ." #: src/conf.py:566 msgid "" "Determines what message the bot sends when it thinks you've\n" " encountered a bug that the developers don't know about." msgstr "" "Determina quale messaggio invierà il bot quando pensa tu abbia trovato\n" " un bug di cui gli sviluppatori non sono a conoscenza." #: src/conf.py:573 msgid "" "A floating point number of seconds to throttle\n" " snarfed URLs, in order to prevent loops between two bots snarfing the same\n" " URLs and having the snarfed URL in the output of the snarf message." msgstr "" "Un numero in virgola mobile corrispondente ai secondi di attesa prima di\n" " intercettare lo stesso URL, in modo da evitare che due bot entrino in\n" " un loop infinito intercettandosi a vicenda." #: src/conf.py:578 msgid "" "Determines the number of seconds\n" " between running the upkeep function that flushes (commits) open databases,\n" " collects garbage, and records some useful statistics at the debugging\n" " level." msgstr "" "Determina il numero di secondi tra ogni operazione di mantenimento (flush) che\n" " aggiorna i database, svuota la cache e registra le statistiche utili per il debug." #: src/conf.py:584 msgid "" "Determines whether the bot will periodically\n" " flush data and configuration files to disk. Generally, the only time\n" " you'll want to set this to False is when you want to modify those\n" " configuration files by hand and don't want the bot to flush its current\n" " version over your modifications. Do note that if you change this to False\n" " inside the bot, your changes won't be flushed. To make this change\n" " permanent, you must edit the registry yourself." msgstr "" "Determina se il bot salverà periodicamente su disco i dati e le configurazioni.\n" " In genere l'unica occasione per impostarlo a False è quando si vuole modificare\n" " i file manualmente ed evitare che il bot salvi invece i dati che ha in memoria;\n" " se imposti il valore a False tramite il bot i cambiamenti non verranno salvati.\n" " Se vuoi che questa modifica sia permanente devi modificare il registro a mano." #: src/conf.py:609 msgid "" "Determines what characters are valid for quoting\n" " arguments to commands in order to prevent them from being tokenized.\n" " " msgstr "" "Determina quali caratteri siano validi per citare gli argomenti dei comandi\n" ", ed evitare che vengano tokenizzati.\n" " " #: src/conf.py:616 msgid "" "Determines whether the bot will allow nested\n" " commands, which rule. You definitely should keep this on." msgstr "" "Determina se il bot permetterà comandi nidificati. È un'ottima\n" " funzionalità, devi assolutamente tenerla attiva." #: src/conf.py:619 msgid "" "Determines what the maximum number of\n" " nested commands will be; users will receive an error if they attempt\n" " commands more nested than this." msgstr "" "Determina il numero massimo di comandi nidificati. Se gli utenti tentano\n" " di utilizzarne di più, riceveranno un errore." #: src/conf.py:627 msgid "" "Supybot allows you to specify what brackets are\n" " used for your nested commands. Valid sets of brackets include [], <>, and\n" " {} (). [] has strong historical motivation, as well as being the brackets\n" " that don't require shift. <> or () might be slightly superior because they\n" " cannot occur in a nick. If this string is empty, nested commands will\n" " not be allowed in this channel." msgstr "" "Supybot permette di specificare quale tipo di parentesi utilizzare per i comandi\n" " nidificati; sono disponibili: [], <>, {} e (). [] hanno forti motivazioni storiche\n" " in quanto non necessitano l'uso di shift. <> o () sono leggermente migliori perché\n" " non utilizzabili in un nick. Se questa stringa è vuota i comandi nidificati non\n" " saranno permessi." #: src/conf.py:634 msgid "" "Supybot allows nested commands. Enabling this\n" " option will allow nested commands with a syntax similar to UNIX pipes, for\n" " example: 'bot: foo | bar'." msgstr "" "Supybot permette i comandi nidificati. Abilitare questa opzione ne permetterà l'uso\n" " con una sintassi simile alle pipe UNIX, ad esempio: 'bot: foo | bar'." #: src/conf.py:639 msgid "" "Determines what commands have default\n" " plugins set, and which plugins are set to be the default for each of those\n" " commands." msgstr "" "Determina, quando un comando è presente in più di un plugin, quale sarà utilizzato\n" " in modo predefinito per ciascuno di questi comandi." #: src/conf.py:645 msgid "" "Determines what plugins automatically get precedence over all\n" " other plugins when selecting a default plugin for a command. By\n" " default, this includes the standard loaded plugins. You probably\n" " shouldn't change this if you don't know what you're doing; if you do\n" " know what you're doing, then also know that this set is\n" " case-sensitive." msgstr "" "Determina quali plugin abbiano la precedenza su tutti gli altri quando se ne\n" " seleziona uno predefinito per un certo comando; di default questo include\n" " i plugin standard caricati. A meno che tu non sappia cosa fare, è probabile\n" " tu non voglia modificare questo comportamento; in caso contrario sappi anche\n" " che i nomi sono case sensitive." #: src/conf.py:660 msgid "" "Determines whether the bot will defend itself\n" " against command-flooding." msgstr "Determina se il bot si difenderà dall'uso di troppi comandi alla volta (flood)." #: src/conf.py:663 msgid "" "Determines how many commands users are\n" " allowed per minute. If a user sends more than this many commands in any\n" " 60 second period, they will be ignored for\n" " supybot.abuse.flood.command.punishment seconds." msgstr "" "Determina quanti comandi al minuto permettere agli utenti. Se qualcuno supera\n" " questo valore verrà ignorato per il numero di secondi definito nella variabile\n" " supybot.abuse.flood.command.punishment." #: src/conf.py:668 msgid "" "Determines how many seconds the bot\n" " will ignore users who flood it with commands." msgstr "Determina per quanti secondi il bot ignorerà un utente che abusa dei comandi (flood)." #: src/conf.py:672 msgid "" "Determines whether the bot will defend itself\n" " against invalid command-flooding." msgstr "Determina se il bot si difenderà dall'uso di troppi comandi non validi alla volta (flood)." #: src/conf.py:675 msgid "" "Determines how many invalid commands users\n" " are allowed per minute. If a user sends more than this many invalid\n" " commands in any 60 second period, they will be ignored for\n" " supybot.abuse.flood.command.invalid.punishment seconds. Typically, this\n" " value is lower than supybot.abuse.flood.command.maximum, since it's far\n" " less likely (and far more annoying) for users to flood with invalid\n" " commands than for them to flood with valid commands." msgstr "" "Determina quanti comandi non validi al minuto permettere agli utenti. Se qualcuno supera\n" " questo valore verrà ignorato per il numero di secondi definito nella variabile\n" " supybot.abuse.flood.command.invalid.punishment. Solitamente questo valore è inferiore\n" " a quello di supybot.abuse.flood.command.maximum, in quanto è meno probabile che un utente\n" " abusi di comandi non validi piuttosto che di quelli validi." #: src/conf.py:683 msgid "" "Determines how many seconds the bot\n" " will ignore users who flood it with invalid commands. Typically, this\n" " value is higher than supybot.abuse.flood.command.punishment, since it's far\n" " less likely (and far more annoying) for users to flood with invalid\n" " commands than for them to flood with valid commands." msgstr "" "Determina per quanti secondi il bot ignorerà un utente che abusa di comandi non validi (flood).\n" " Solitamente questo valore è superiore a quello di supybot.abuse.flood.command.punishment,\n" " in quanto è meno probabile che un utente abusi di comandi non validi piuttosto che di quelli validi." #: src/conf.py:689 msgid "" "Determines whether the bot will notify people\n" " that they're being ignored for invalid command flooding." msgstr "Determina se il bot avvertirà l'utente che è stato ignorato per abuso di comandi non validi (flood)." #: src/conf.py:698 msgid "" "Determines the default length of time a\n" " driver should block waiting for input." msgstr "" "Determina la quantità di tempo predefinita durante il quale un driver\n" " di rete debba bloccarsi in attesa dei dati in entrata." #: src/conf.py:705 msgid "" "Determines what driver module the bot\n" " will use. Socket, a simple driver based on timeout sockets, is used by\n" " default because it's simple and stable. Twisted is very stable and simple,\n" " and if you've got Twisted installed, is probably your best bet." msgstr "" "Determina quale modulo per il driver di rete utilizzerà il bot. Socket, un driver\n" " semplice basato su timeout del socket e usato in modo predefinito per la sua\n" " stabilità. Twisted è molto stabile e semplice, se lo hai installato è\n" " probabilmente la scelta migliore." #: src/conf.py:711 msgid "" "Determines the maximum time the bot will\n" " wait before attempting to reconnect to an IRC server. The bot may, of\n" " course, reconnect earlier if possible." msgstr "" "Determina il tempo massimo che il bot attenderà prima di riconnettersi al server\n" " IRC. Se ne avrà la possibilità potrà comunque connettersi prima." #: src/conf.py:760 msgid "" "Determines what directory configuration data is\n" " put into." msgstr "Determina in quale directory collocare i dati di configurazione." #: src/conf.py:763 msgid "Determines what directory data is put into." msgstr "Determina in quale directory collocare i dati." #: src/conf.py:765 msgid "" "Determines what directory backup data is put\n" " into. Set it to /dev/null to disable backup (it is a special value,\n" " so it also works on Windows and systems without /dev/null)." msgstr "" "Determina in quale directory collocare i dati di backup. Impostare il valore\n" " a /dev/null per disabilitarlo (è un valore speciale che funziona anche su\n" " Windows e sistemi privi di questo device)." #: src/conf.py:769 msgid "" "Determines what directory temporary files\n" " are put into." msgstr "Determina in quale directory collocare i file temporanei." #: src/conf.py:776 msgid "" "Determines what directories\n" " the bot will look for plugins in. Accepts a comma-separated list of\n" " strings.\n" " This means that to add another directory, you can nest the former value and\n" " add a new one. E.g. you can say: bot: 'config supybot.directories.plugins\n" " [config supybot.directories.plugins], newPluginDirectory'." msgstr "" "Determina in quali directory il bot cercherà i plugin. Accetta un elenco di valori\n" " separati da virgola; ciò significa che se si vuole aggiungere un'altra directory\n" " è possibile nidificare il valore precedente e inserirne uno nuovo. Esempio:\n" " \"config supybot.directories.plugins [config supybot.directories.plugins], nuovaDirectory\"." #: src/conf.py:784 msgid "" "Determines what plugins will\n" " be loaded." msgstr "Determina quali plugin saranno caricati." #: src/conf.py:787 msgid "" "Determines whether the bot will always load\n" " important plugins (Admin, Channel, Config, Misc, Owner, and User)\n" " regardless of what their configured state is. Generally, if these plugins\n" " are configured not to load, you didn't do it on purpose, and you still\n" " want them to load. Users who don't want to load these plugins are smart\n" " enough to change the value of this variable appropriately :)" msgstr "" "Determina se il bot caricherà sempre i plugin importanti (Admin, Channel, Config,\n" " Misc, Owner e User) a prescindere da come siano configurati. In genere se questi\n" " sono impostati per non essere caricati non è stato fatto di proposito e vorrai\n" " comunque caricarli. Chi non vuole che questo avvenga sarà sufficientemente abile\n" " nel modificare il valore di questa variabile in modo appropriato :)" #: src/conf.py:814 msgid "" "Determines what databases are available for use. If this\n" " value is not configured (that is, if its value is empty) then sane defaults\n" " will be provided." msgstr "" "Determina quali database rendere disponibili. Se non configurato (ovvero il\n" " valore è vuoto) verrà utilizzato uno predefinito." #: src/conf.py:820 msgid "" "Determines what filename will be used\n" " for the users database. This file will go into the directory specified by\n" " the supybot.directories.conf variable." msgstr "" "Determina il nome del file utilizzato per il database degli utenti che finirà\n" " nella directory specificata dalla variabile supybot.directories.conf." #: src/conf.py:824 msgid "" "Determines how long it takes identification to\n" " time out. If the value is less than or equal to zero, identification never\n" " times out." msgstr "" "Determina per quanto tempo rimarrà identificato un utente. Se il valore è\n" " inferiore o uguale a zero l'identificazione non scadrà mai." #: src/conf.py:828 msgid "" "Determines whether the bot will allow users to\n" " unregister their users. This can wreak havoc with already-existing\n" " databases, so by default we don't allow it. Enable this at your own risk.\n" " (Do also note that this does not prevent the owner of the bot from using\n" " the unregister command.)\n" " " msgstr "" "Determina se il bot permetterà agli utenti di de-registrarsi. Ciò può causare\n" " danni ai database esistenti per cui non è permesso in modo predefinito;\n" " abilitalo a tuo rischio. Nota che questo non impedisce al proprietario del\n" " bot (owner) di utilizzare il comando \"unregister\".\n" " " #: src/conf.py:837 msgid "" "Determines what filename will be used\n" " for the ignores database. This file will go into the directory specified\n" " by the supybot.directories.conf variable." msgstr "" "Determina il nome del file utilizzato per il database degli ignore che finirà\n" " nella directory specificata dalla variabile supybot.directories.conf." #: src/conf.py:843 msgid "" "Determines what filename will be used\n" " for the channels database. This file will go into the directory specified\n" " by the supybot.directories.conf variable." msgstr "Determina il nome del file utilizzato per il database dei canali che finirà\n" " nella directory specificata dalla variabile supybot.directories.conf." #: src/conf.py:873 msgid "" "Determines whether database-based plugins that\n" " can be channel-specific will be so. This can be overridden by individual\n" " channels. Do note that the bot needs to be restarted immediately after\n" " changing this variable or your db plugins may not work for your channel;\n" " also note that you may wish to set\n" " supybot.databases.plugins.channelSpecific.link appropriately if you wish\n" " to share a certain channel's databases globally." msgstr "" "Determina se i plugin che utilizzano un database possano essere specifici per\n" " un certo canale; ciò è sostituibile per ogni canale. Nota che il bot va\n" " riavviato subito dopo la modifica di questa variabile altrimenti tali plugin\n" " potrebbero non funzionare; se desideri condividere il database di un certo\n" " canale a livello globale è inoltre possibile impostare la variabile\n" " supybot.databases.plugins.channelSpecific.link in modo appropriato." #: src/conf.py:881 msgid "" "Determines what channel global\n" " (non-channel-specific) databases will be considered a part of. This is\n" " helpful if you've been running channel-specific for awhile and want to turn\n" " the databases for your primary channel into global databases. If\n" " supybot.databases.plugins.channelSpecific.link.allow prevents linking, the\n" " current channel will be used. Do note that the bot needs to be restarted\n" " immediately after changing this variable or your db plugins may not work\n" " for your channel." msgstr "" "Determina quali database specifici per un certo canale saranno considerati parte\n" " di uno globale. Utile se ne sono stati utilizzati di specifici per un certo\n" " periodo e li si vuole convertire. Se la variabile supybot.databases.plugins.channelSpecific.link.allow\n" " impedisce il collegamento verrà utilizzato il canale attuale. Nota che il bot va\n" " riavviato subito dopo la modifica di questa variabile altrimenti tali plugin\n" " potrebbero non funzionare." #: src/conf.py:890 msgid "" "Determines whether another channel's global\n" " (non-channel-specific) databases will be allowed to link to this channel's\n" " databases. Do note that the bot needs to be restarted immediately after\n" " changing this variable or your db plugins may not work for your channel.\n" " " msgstr "" "Determina se i database globali di un altro canale possano essere collegati a\n" " quelli di questo canale. Nota che il bot va riavviato subito dopo la modifica\n" " di questa variabile altrimenti tali plugin potrebbero non funzionare.\n" " " #: src/conf.py:907 msgid "" "Determines\n" " whether CDB databases will be allowed as a database implementation." msgstr "Determina se i database CDB sono permessi come implementazione di un database." #: src/conf.py:910 msgid "" "Determines how often CDB databases will have\n" " their modifications flushed to disk. When the number of modified records\n" " is greater than this fraction of the total number of records, the database\n" " will be entirely flushed to disk." msgstr "" "Determina la frequenza di salvataggio su disco delle modifiche dei databases CDB.\n" " Quando il numero di dati modificati è più grande di quelli non modificati il\n" " database verrà interamente scritto sul disco." #: src/conf.py:998 msgid "" "Determines what will be used as the\n" " default banmask style." msgstr "Determina il tipo di banmask utilizzata in modo predefinito." #: src/conf.py:1002 msgid "" "Determines whether the bot will strictly follow\n" " the RFC; currently this only affects what strings are considered to be\n" " nicks. If you're using a server or a network that requires you to message\n" " a nick such as services@this.network.server then you you should set this to\n" " False." msgstr "" "Determina se il bot seguirà rigorosamente la RFC; attualmente riguarda solo quali\n" " stringhe siano considerate dei nick. Se utilizzi un server o una rete che richiede\n" " di inviare un messaggio a un nick nella forma \"services@this.network.server\", è\n" " necessario impostare questo valore a False." #: src/conf.py:1009 msgid "" "Determines what user modes the bot will request\n" " from the server when it first connects. Many people might choose +i; some\n" " networks allow +x, which indicates to the auth services on those networks\n" " that you should be given a fake host." msgstr "" "Determina quali modalità utente (mode) il bot chiederà al server quando si connette.\n" " Molti utenti vorranno +i mentre per altre reti è +x, indica ai servizi che si\n" " desidera avere l'hostmask camuffata (vhost) o parzialmente crittata." #: src/conf.py:1015 msgid "" "Determines what vhost the bot will bind to before\n" " connecting to the IRC server." msgstr "" "Determina quale vhost associare al bot prima di connettersi al server IRC." #: src/conf.py:1019 msgid "" "Determines how many old messages the bot will\n" " keep around in its history. Changing this variable will not take effect\n" " until the bot is restarted." msgstr "" "Determina quanti messaggi vecchi terrà il bot nella cronologia. La modifica\n" " di questa variabile non avrà effetto finché non si riavvia il bot." #: src/conf.py:1024 msgid "" "A floating point number of seconds to throttle\n" " queued messages -- that is, messages will not be sent faster than once per\n" " throttleTime seconds." msgstr "" "Un numero in virgola mobile corrispondente ai secondi di attesa prima di\n" " inviare i messaggi in coda; vale a dire che verranno inviati una volta\n" " ogni numero di secondi specificati." #: src/conf.py:1029 msgid "" "Determines whether the bot will send PINGs to\n" " the server it's connected to in order to keep the connection alive and\n" " discover earlier when it breaks. Really, this option only exists for\n" " debugging purposes: you always should make it True unless you're testing\n" " some strange server issues." msgstr "" "Determina se il bot invierà PING al server sul quale è connesso per mantenere\n" " attiva la connessione e diagnosticare in tempo se c'è un'interruzione.\n" " In realtà questa opzione esiste solo per scopi di debug, a meno che non\n" " si incontrino problemi strani con il server, andrebbe sempre tenuta a True." #: src/conf.py:1036 msgid "" "Determines the number of seconds between sending\n" " pings to the server, if pings are being sent to the server." msgstr "Determina il numero di secondi tra ogni invio di PING al server, se questo avviene." #: src/conf.py:1041 msgid "" "Determines whether the bot will refuse\n" " duplicated messages to be queued for delivery to the server. This is a\n" " safety mechanism put in place to prevent plugins from sending the same\n" " message multiple times; most of the time it doesn't matter, unless you're\n" " doing certain kinds of plugin hacking." msgstr "" "Determina se il bot rifiuterà di accodare messaggi doppi da spedire al server.\n" " Questo è un meccanismo di sicurezza per evitare che i plugin inviino più volte\n" " lo stesso messaggio; il più delle volte non ha importanza, tranne che non si\n" " facciano delle modifiche ai plugin." #: src/conf.py:1049 msgid "" "Determines how many seconds must elapse between\n" " JOINs sent to the server." msgstr "Determina quanti secondi debbano trascorrere tra un invio di JOIN e l'altro." #: src/conf.py:1057 msgid "" "Determines how many bytes the bot will\n" " 'peek' at when looking through a URL for a doctype or title or something\n" " similar. It'll give up after it reads this many bytes, even if it hasn't\n" " found what it was looking for." msgstr "" "Determina il numero di byte entro i quali il bot \"sbircierà\" cercando il doctype,\n" " il title o altri tag HTML simili in un URL. Abbandonerà la ricerca dopo aver\n" " letto questa quantità di byte, anche se non trova quel che stava cercando." #: src/conf.py:1063 msgid "" "Determines what proxy all HTTP requests should go\n" " through. The value should be of the form 'host:port'." msgstr "" "Determina tramite quale proxy debbano passare le richieste HTTP.\n" " Il valore deve essere nella forma \"host:porta\"." #: src/conf.py:1083 msgid "Determines what host the HTTP server will bind." msgstr "Determina a quale host si collegherà il server HTTP." #: src/conf.py:1085 msgid "" "Determines what port the HTTP server will\n" " bind." msgstr "Determina a quale porta si collegherà il server HTTP." #: src/conf.py:1088 msgid "" "Determines whether the server will stay\n" " alive if no plugin is using it. This also means that the server will\n" " start even if it is not used." msgstr "" "Definisce se il server rimarrà in esecuzione se nessun plugin lo utilizza.\n" " Ciò significa che verrà avviato anche se non utilizzato." #: src/conf.py:1092 msgid "" "Determines the content of the robots.txt file,\n" " served on the server to search engine." msgstr "" "Determina il contenuto del file robots.txt, fornito dal server al motore di ricerca." #: src/conf.py:1100 msgid "" "Determines whether the bot will ignore\n" " unregistered users by default. Of course, that'll make it particularly\n" " hard for those users to register or identify with the bot, but that's your\n" " problem to solve." msgstr "" "Determina se il bot ignorerà gli utenti non registrati in modo predefinito.\n" " Naturalmente renderà difficoltoso registrarsi o identificarsi, ma questo è un tuo problema." #: src/conf.py:1107 msgid "" "A string that is the external IP of the bot. If this is the\n" " empty string, the bot will attempt to find out its IP dynamically (though\n" " sometimes that doesn't work, hence this variable)." msgstr "" "Una stringa equivalente all'IP esterno del bot. Se lasciata vuota, il bot tenterà\n" " di risolvere il suo IP dinamicamente (sebbene a volte non funzioni, ecco quindi\n" " lo scopo di questa variabile)." #: src/conf.py:1121 msgid "" "Determines what the default timeout for socket\n" " objects will be. This means that *all* sockets will timeout when this many\n" " seconds has gone by (unless otherwise modified by the author of the code\n" " that uses the sockets)." msgstr "" "Determina il timeout predefinito per i socket. Ciò significa che *tutti* i socket\n" " scadranno dopo questo periodo (a meno che l'utore del codice ne abbia modificato il valore)." #: src/conf.py:1127 msgid "" "Determines what file the bot should write its PID\n" " (Process ID) to, so you can kill it more easily. If it's left unset (as is\n" " the default) then no PID file will be written. A restart is required for\n" " changes to this variable to take effect." msgstr "" "Determina in quale file il bot salverà il suo PID (Process ID) in modo che si possa\n" " terminare più facilmente. Se non impostata, il PID non verrà scritto in nessun\n" " file. Perché le modifiche a questa variabile abbiano effetto è necessario un riavvio." #: src/conf.py:1137 msgid "" "Determines whether the bot will automatically\n" " thread all commands." msgstr "Determina se il bot userà i thread per tutti i comandi." #: src/conf.py:1140 msgid "" "Determines whether the bot will automatically\n" " flush all flushers *very* often. Useful for debugging when you don't know\n" " what's breaking or when, but think that it might be logged." msgstr "" "Determina se il bot eseguirà tutte le operazioni di mantenimento (flush) *molto*\n" " spesso. Utile per fare il debug quando non si sa cosa non funziona o quando\n" " si ritiene necessario tenerlo sotto controllo." #: src/httpserver.py:131 msgid "" "\n" " This is a default response of the Supybot HTTP server. If you see this\n" " message, it probably means you are developing a plugin, and you have\n" " neither overriden this message or defined an handler for this query." msgstr "" "\n" " Questa è la risposta predefinita del server HTTP di Supybot. Se stai leggendo\n" " questo messaggio significa probabilmente che stai sviluppando un plugin e non\n" " hai sovrascritto questo messaggio o definito un gestore per questa query." #: src/httpserver.py:152 msgid "" "\n" " I am a pretty clever IRC bot, but I suck at serving Web pages, particulary\n" " if I don't know what to serve.\n" " What I'm saying is you just triggered a 404 Not Found, and I am not\n" " trained to help you in such a case." msgstr "" "\n" " Sono un bot IRC piuttosto intelligente ma terribile quando è ora di servire pagine\n" " web, in particolare se non cosa servire. Quel che sto cercando di dirti è che hai\n" " appena causato un \"404 Not Found\" e non sono addestrato per aiutarti in questa circostanza." #: src/httpserver.py:169 msgid "Request not handled." msgstr "Richiesta non gestita." #: src/httpserver.py:173 msgid "Supybot Web server index" msgstr "Indice del server web di Supybot" #: src/httpserver.py:176 msgid "Here is a list of the plugins that have a Web interface:" msgstr "Ecco un elenco dei plugin che hanno un'interfaccia web:" #: src/httpserver.py:185 msgid "No plugins available." msgstr "Nessun plugin disponibile." #: src/httpserver.py:199 msgid "Request not handled" msgstr "Richiesta non gestita" #: src/questions.py:60 msgid "Sorry, that response was not an option." msgstr "Spiacente, questa risposta non è un'opzione." #: src/questions.py:106 msgid "Sorry, you must enter a value." msgstr "Spiacente, devi inserire un valore." #: src/questions.py:126 msgid "Enter password: " msgstr "Inserire la password: " #: src/questions.py:128 msgid "Re-enter password: " msgstr "Inserire nuovamente la password: " #: src/questions.py:141 msgid "Passwords don't match." msgstr "Le password non corrispondono." limnoria-2020.03.17/locales/messages.pot0000644000175000017500000011714213634634532017302 0ustar valval00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: 2019-11-24 12:42+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" #: src/callbacks.py:189 msgid "Error: " msgstr "" #: src/callbacks.py:205 msgid "Error: I tried to send you an empty message." msgstr "" #: src/callbacks.py:325 msgid "Missing \"%s\". You may want to quote your arguments with double quotes in order to prevent extra brackets from being evaluated as nested commands." msgstr "" #: src/callbacks.py:354 msgid "\"|\" with nothing preceding. I obviously can't do a pipe with nothing before the |." msgstr "" #: src/callbacks.py:362 msgid "Spurious \"%s\". You may want to quote your arguments with double quotes in order to prevent extra brackets from being evaluated as nested commands." msgstr "" #: src/callbacks.py:371 msgid "\"|\" with nothing following. I obviously can't do a pipe with nothing after the |." msgstr "" #: src/callbacks.py:570 msgid "%s is not a valid %s." msgstr "" #: src/callbacks.py:572 msgid "That's not a valid %s." msgstr "" #: src/callbacks.py:652 msgid "You've attempted more nesting than is currently allowed on this bot." msgstr "" #: src/callbacks.py:833 msgid "The command %q is available in the %L plugins. Please specify the plugin whose command you wish to call by using its name as a command before %q." msgstr "" #: src/callbacks.py:955 msgid "(XX more messages)" msgstr "" #: src/callbacks.py:978 msgid "more message" msgstr "" #: src/callbacks.py:980 msgid "more messages" msgstr "" #: src/callbacks.py:1122 msgid "" "Determines what commands are currently disabled. Such\n" " commands will not appear in command lists, etc. They will appear not even\n" " to exist." msgstr "" #: src/callbacks.py:1344 msgid "Invalid arguments for %s." msgstr "" #: src/callbacks.py:1370 msgid "The %q command has no help." msgstr "" #: src/commands.py:273 msgid "integer" msgstr "" #: src/commands.py:284 msgid "non-integer value" msgstr "" #: src/commands.py:295 msgid "floating point number" msgstr "" #: src/commands.py:304 msgid "positive integer" msgstr "" #: src/commands.py:308 msgid "non-negative integer" msgstr "" #: src/commands.py:311 msgid "index" msgstr "" #: src/commands.py:336 msgid "number of seconds" msgstr "" #: src/commands.py:343 msgid "boolean" msgstr "" #: src/commands.py:357 src/commands.py:364 src/commands.py:373 #: src/commands.py:380 src/commands.py:389 msgid "do that" msgstr "" #: src/commands.py:360 src/commands.py:367 src/commands.py:376 #: src/commands.py:383 src/commands.py:392 msgid "I'm not even in %s." msgstr "" #: src/commands.py:362 msgid "I need to be voiced to %s." msgstr "" #: src/commands.py:370 msgid "I need to be at least voiced to %s." msgstr "" #: src/commands.py:378 msgid "I need to be halfopped to %s." msgstr "" #: src/commands.py:386 msgid "I need to be at least halfopped to %s." msgstr "" #: src/commands.py:394 msgid "I need to be opped to %s." msgstr "" #: src/commands.py:400 src/commands.py:561 msgid "channel" msgstr "" #: src/commands.py:413 msgid "nick or hostmask" msgstr "" #: src/commands.py:467 msgid "regular expression" msgstr "" #: src/commands.py:478 src/commands.py:482 msgid "nick" msgstr "" #: src/commands.py:479 msgid "That nick is too long for this server." msgstr "" #: src/commands.py:490 msgid "I haven't seen %s." msgstr "" #: src/commands.py:540 src/commands.py:559 msgid "I'm not in %s." msgstr "" #: src/commands.py:544 msgid "This command may only be given in a channel that I am in." msgstr "" #: src/commands.py:557 msgid "You must be in %s." msgstr "" #: src/commands.py:568 msgid "%s is not in %s." msgstr "" #: src/commands.py:616 msgid "You must not give the empty string as an argument." msgstr "" #: src/commands.py:624 msgid "You must not give a string containing spaces as an argument." msgstr "" #: src/commands.py:634 msgid "This message must be sent in a channel." msgstr "" #: src/commands.py:666 msgid "url" msgstr "" #: src/commands.py:672 msgid "email" msgstr "" #: src/commands.py:680 msgid "http url" msgstr "" #: src/commands.py:687 msgid "command name" msgstr "" #: src/commands.py:695 msgid "ip" msgstr "" #: src/commands.py:701 msgid "letter" msgstr "" #: src/commands.py:733 msgid "plugin" msgstr "" #: src/commands.py:741 msgid "irc color" msgstr "" #: src/conf.py:128 msgid "" "Determines whether this plugin is loaded\n" " by default." msgstr "" #: src/conf.py:132 msgid "" "Determines whether this plugin is\n" " publicly visible." msgstr "" #: src/conf.py:233 msgid "Determines the bot's default nick." msgstr "" #: src/conf.py:236 msgid "" "Determines what alternative\n" " nicks will be used if the primary nick (supybot.nick) isn't available. A\n" " %s in this nick is replaced by the value of supybot.nick when used. If no\n" " alternates are given, or if all are used, the supybot.nick will be perturbed\n" " appropriately until an unused nick is found." msgstr "" #: src/conf.py:243 msgid "" "Determines the bot's ident string, if the server\n" " doesn't provide one by default." msgstr "" #: src/conf.py:260 msgid "" "Determines the real name which the bot sends to\n" " the server. A standard real name using the current version of the bot\n" " will be generated if this is left empty." msgstr "" #: src/conf.py:269 msgid "Determines what networks the bot will connect to." msgstr "" #: src/conf.py:368 msgid "" "Determines what password will be used on %s. Yes, we know that\n" " technically passwords are server-specific and not network-specific,\n" " but this is the best we can do right now." msgstr "" #: src/conf.py:372 msgid "" "Space-separated list of servers the bot will connect to for %s.\n" " Each will be tried in order, wrapping back to the first when the cycle\n" " is completed." msgstr "" #: src/conf.py:376 msgid "Space-separated list of channels the bot will join only on %s." msgstr "" #: src/conf.py:380 msgid "" "Determines whether the bot will attempt to connect with SSL\n" " sockets to %s." msgstr "" #: src/conf.py:383 msgid "" "Space-separated list\n" " of fingerprints of trusted certificates for this network.\n" " Supported hash algorithms are: %L.\n" " If non-empty, Certification Authority signatures will not be used to\n" " verify certificates." msgstr "" #: src/conf.py:389 msgid "" "A certificate that is trusted to verify\n" " certificates of this network (aka. Certificate Authority)." msgstr "" #: src/conf.py:392 msgid "Deprecated config value, keep it to False." msgstr "" #: src/conf.py:395 msgid "" "Determines what certificate file (if any) the bot will use to\n" " connect with SSL sockets to %s." msgstr "" #: src/conf.py:398 msgid "" "Determines what key (if any) will be used to join the\n" " channel." msgstr "" #: src/conf.py:400 msgid "" "Determines\n" " what nick the bot will use on this network. If empty, defaults to\n" " supybot.nick." msgstr "" #: src/conf.py:403 msgid "" "Determines\n" " the bot's ident string, if the server doesn't provide one by default.\n" " If empty, defaults to supybot.ident." msgstr "" #: src/conf.py:406 msgid "" "Determines\n" " the real name which the bot sends to the server. If empty, defaults to\n" " supybot.user" msgstr "" #: src/conf.py:410 msgid "" "Determines what user modes the bot will request\n" " from the server when it first connects. If empty, defaults to\n" " supybot.protocols.irc.umodes" msgstr "" #: src/conf.py:415 msgid "" "Determines what SASL username will be used on %s. This should\n" " be the bot's account name." msgstr "" #: src/conf.py:419 msgid "Determines what SASL password will be used on %s." msgstr "" #: src/conf.py:422 msgid "" "Determines what SASL ECDSA key (if any) will be used on %s.\n" " The public key must be registered with NickServ for SASL\n" " ECDSA-NIST256P-CHALLENGE to work." msgstr "" #: src/conf.py:426 msgid "" "Determines\n" " what SASL mechanisms will be tried and in which order." msgstr "" #: src/conf.py:429 msgid "" "Determines whether the bot will abort the connection if the\n" " none of the enabled SASL mechanism succeeded." msgstr "" #: src/conf.py:432 msgid "" "If not empty, determines the hostname of the socks proxy that\n" " will be used to connect to this network." msgstr "" #: src/conf.py:452 msgid "Determines how urls should be formatted." msgstr "" #: src/conf.py:460 msgid "" "Determines how timestamps\n" " printed for human reading should be formatted. Refer to the Python\n" " documentation for the time module to see valid formatting characters for\n" " time formats." msgstr "" #: src/conf.py:475 msgid "" "Determines whether elapsed times will be given\n" " as \"1 day, 2 hours, 3 minutes, and 15 seconds\" or as \"1d 2h 3m 15s\"." msgstr "" #: src/conf.py:486 msgid "" "Maximum number of items in a list\n" " before the end is replaced with 'and others'. Set to 0 to always\n" " show the entire list." msgstr "" #: src/conf.py:503 msgid "other" msgstr "" #: src/conf.py:508 msgid "" "Determines the absolute maximum length of\n" " the bot's reply -- no reply will be passed through the bot with a length\n" " greater than this." msgstr "" #: src/conf.py:513 msgid "" "Determines whether the bot will break up long\n" " messages into chunks and allow users to use the 'more' command to get the\n" " remaining chunks." msgstr "" #: src/conf.py:518 msgid "" "Determines what the maximum number of\n" " chunks (for use with the 'more' command) will be." msgstr "" #: src/conf.py:522 msgid "" "Determines how long individual chunks\n" " will be. If set to 0, uses our super-tweaked,\n" " get-the-most-out-of-an-individual-message default." msgstr "" #: src/conf.py:527 msgid "" "Determines how many mores will be sent\n" " instantly (i.e., without the use of the more command, immediately when\n" " they are formed). Defaults to 1, which means that a more command will be\n" " required for all but the first chunk." msgstr "" #: src/conf.py:533 msgid "" "Determines whether the bot will send\n" " multi-message replies in a single message. This defaults to True \n" " in order to prevent the bot from flooding. If this is set to False\n" " the bot will send multi-message replies on multiple lines." msgstr "" #: src/conf.py:539 msgid "" "Determines whether the bot will reply with an\n" " error message when it is addressed but not given a valid command. If this\n" " value is False, the bot will remain silent, as long as no other plugins\n" " override the normal behavior." msgstr "" #: src/conf.py:546 msgid "" "Determines whether error messages that result\n" " from bugs in the bot will show a detailed error message (the uncaught\n" " exception) or a generic error message." msgstr "" #: src/conf.py:550 msgid "" "Determines whether the bot will send error\n" " messages to users in private. You might want to do this in order to keep\n" " channel traffic to minimum. This can be used in combination with\n" " supybot.reply.error.withNotice." msgstr "" #: src/conf.py:555 msgid "" "Determines whether the bot will send error\n" " messages to users via NOTICE instead of PRIVMSG. You might want to do this\n" " so users can ignore NOTICEs from the bot and not have to see error\n" " messages; or you might want to use it in combination with\n" " supybot.reply.errorInPrivate so private errors don't open a query window\n" " in most IRC clients." msgstr "" #: src/conf.py:562 msgid "" "Determines whether the bot will *not* provide\n" " details in the error\n" " message to users who attempt to call a command for which they do not have\n" " the necessary capability. You may wish to make this True if you don't want\n" " users to understand the underlying security system preventing them from\n" " running certain commands." msgstr "" #: src/conf.py:570 msgid "" "Determines whether the bot will reply\n" " privately when replying in a channel, rather than replying to the whole\n" " channel." msgstr "" #: src/conf.py:575 msgid "" "Determines whether the bot will reply with a\n" " notice when replying in a channel, rather than replying with a privmsg as\n" " normal." msgstr "" #: src/conf.py:581 msgid "" "Determines whether the bot will reply with a\n" " notice when it is sending a private message, in order not to open a /query\n" " window in clients." msgstr "" #: src/conf.py:586 msgid "" "Determines whether the bot will always prefix\n" " the user's nick to its reply to that user's command." msgstr "" #: src/conf.py:590 msgid "" "Determines whether the bot should attempt to\n" " reply to all messages even if they don't address it (either via its nick\n" " or a prefix character). If you set this to True, you almost certainly want\n" " to set supybot.reply.whenNotCommand to False." msgstr "" #: src/conf.py:596 msgid "" "Determines whether the bot will allow you to\n" " send channel-related commands outside of that channel. Sometimes people\n" " find it confusing if a channel-related command (like Filter.outfilter)\n" " changes the behavior of the channel but was sent outside the channel\n" " itself." msgstr "" #: src/conf.py:603 msgid "" "Determines whether the bot will unidentify\n" " someone when that person changes their nick. Setting this to True\n" " will cause the bot to track such changes. It defaults to False for a\n" " little greater security." msgstr "" #: src/conf.py:609 msgid "" "Determines whether the bot will always join a\n" " channel when it's invited. If this value is False, the bot will only join\n" " a channel if the user inviting it has the 'admin' capability (or if it's\n" " explicitly told to join the channel using the Admin.join command)." msgstr "" #: src/conf.py:615 msgid "" "Supybot normally replies with the full help\n" " whenever a user misuses a command. If this value is set to True, the bot\n" " will only reply with the syntax of the command (the first line of the\n" " help) rather than the full help." msgstr "" #: src/conf.py:630 msgid "" "Determines what prefix characters the bot will\n" " reply to. A prefix character is a single character that the bot will use\n" " to determine what messages are addressed to it; when there are no prefix\n" " characters set, it just uses its nick. Each character in this string is\n" " interpreted individually; you can have multiple prefix chars\n" " simultaneously, and if any one of them is used as a prefix the bot will\n" " assume it is being addressed." msgstr "" #: src/conf.py:639 msgid "" "Determines what strings the\n" " bot will reply to when they are at the beginning of the message. Whereas\n" " prefix.chars can only be one character (although there can be many of\n" " them), this variable is a space-separated list of strings, so you can\n" " set something like '@@ ??' and the bot will reply when a message is\n" " prefixed by either @@ or ??." msgstr "" #: src/conf.py:646 msgid "" "Determines whether the bot will reply when\n" " people address it by its nick, rather than with a prefix character." msgstr "" #: src/conf.py:649 msgid "" "Determines whether the bot will reply when\n" " people address it by its nick at the end of the message, rather than at\n" " the beginning." msgstr "" #: src/conf.py:653 msgid "" "Determines what extra nicks\n" " the bot will always respond to when addressed by, even if its current nick\n" " is something else." msgstr "" #: src/conf.py:663 msgid "The operation succeeded." msgstr "" #: src/conf.py:664 msgid "" "Determines what message the bot replies with when a command succeeded.\n" " If this configuration variable is empty, no success message will be\n" " sent." msgstr "" #: src/conf.py:669 msgid "" "An error has occurred and has been logged.\n" " Please contact this bot's administrator for more information." msgstr "" #: src/conf.py:670 msgid "" "\n" " Determines what error message the bot gives when it wants to be\n" " ambiguous." msgstr "" #: src/conf.py:675 msgid "" "An error has occurred and has been logged.\n" " Check the logs for more information." msgstr "" #: src/conf.py:676 msgid "" "Determines what error\n" " message the bot gives to the owner when it wants to be ambiguous." msgstr "" #: src/conf.py:680 msgid "" "Your hostmask doesn't match or your password\n" " is wrong." msgstr "" #: src/conf.py:681 msgid "" "Determines what message the bot replies with when\n" " someone tries to use a command that requires being identified or having a\n" " password and neither credential is correct." msgstr "" #: src/conf.py:687 msgid "" "I can't find %s in my user\n" " database. If you didn't give a user name, then I might not know what your\n" " user is, and you'll need to identify before this command might work." msgstr "" #: src/conf.py:690 msgid "" "Determines what error message the bot replies with when someone tries\n" " to accessing some information on a user the bot doesn't know about." msgstr "" #: src/conf.py:694 msgid "" "You must be registered to use this command.\n" " If you are already registered, you must either identify (using the identify\n" " command) or add a hostmask matching your current hostmask (using the\n" " \"hostmask add\" command)." msgstr "" #: src/conf.py:697 msgid "" "Determines what error message the bot\n" " replies with when someone tries to do something that requires them to be\n" " registered but they're not currently recognized." msgstr "" #: src/conf.py:702 msgid "" "You don't have the %s capability. If you\n" " think that you should have this capability, be sure that you are identified\n" " before trying again. The 'whoami' command can tell you if you're\n" " identified." msgstr "" #: src/conf.py:705 msgid "" "Determines what error message is given when the bot\n" " is telling someone they aren't cool enough to use the command they tried to\n" " use." msgstr "" #: src/conf.py:710 msgid "" "You're missing some capability you need.\n" " This could be because you actually possess the anti-capability for the\n" " capability that's required of you, or because the channel provides that\n" " anti-capability by default, or because the global capabilities include\n" " that anti-capability. Or, it could be because the channel or\n" " supybot.capabilities.default is set to False, meaning that no commands are\n" " allowed unless explicitly in your capabilities. Either way, you can't do\n" " what you want to do." msgstr "" #: src/conf.py:718 msgid "" "Determines what generic error message is given when the bot is telling\n" " someone that they aren't cool enough to use the command they tried to use,\n" " and the author of the code calling errorNoCapability didn't provide an\n" " explicit capability for whatever reason." msgstr "" #: src/conf.py:724 msgid "" "That operation cannot be done in a\n" " channel." msgstr "" #: src/conf.py:725 msgid "" "Determines what error messages the bot sends to people\n" " who try to do things in a channel that really should be done in\n" " private." msgstr "" #: src/conf.py:730 msgid "" "This may be a bug. If you think it is,\n" " please file a bug report at\n" " ." msgstr "" #: src/conf.py:733 msgid "" "Determines what message the bot sends when it thinks you've\n" " encountered a bug that the developers don't know about." msgstr "" #: src/conf.py:740 msgid "" "A floating point number of seconds to throttle\n" " snarfed URLs, in order to prevent loops between two bots snarfing the same\n" " URLs and having the snarfed URL in the output of the snarf message." msgstr "" #: src/conf.py:745 msgid "" "Determines the number of seconds\n" " between running the upkeep function that flushes (commits) open databases,\n" " collects garbage, and records some useful statistics at the debugging\n" " level." msgstr "" #: src/conf.py:751 msgid "" "Determines whether the bot will periodically\n" " flush data and configuration files to disk. Generally, the only time\n" " you'll want to set this to False is when you want to modify those\n" " configuration files by hand and don't want the bot to flush its current\n" " version over your modifications. Do note that if you change this to False\n" " inside the bot, your changes won't be flushed. To make this change\n" " permanent, you must edit the registry yourself." msgstr "" #: src/conf.py:777 msgid "" "Determines what characters are valid for quoting\n" " arguments to commands in order to prevent them from being tokenized.\n" " " msgstr "" #: src/conf.py:784 msgid "" "Determines whether the bot will allow nested\n" " commands, which rule. You definitely should keep this on." msgstr "" #: src/conf.py:787 msgid "" "Determines what the maximum number of\n" " nested commands will be; users will receive an error if they attempt\n" " commands more nested than this." msgstr "" #: src/conf.py:796 msgid "" "Supybot allows you to specify what brackets\n" " are used for your nested commands. Valid sets of brackets include\n" " [], <>, {}, and (). [] has strong historical motivation, but <> or\n" " () might be slightly superior because they cannot occur in a nick.\n" " If this string is empty, nested commands will not be allowed in this\n" " channel." msgstr "" #: src/conf.py:803 msgid "" "Supybot allows nested commands. Enabling this\n" " option will allow nested commands with a syntax similar to UNIX pipes, for\n" " example: 'bot: foo | bar'." msgstr "" #: src/conf.py:808 msgid "" "Determines what commands have default\n" " plugins set, and which plugins are set to be the default for each of those\n" " commands." msgstr "" #: src/conf.py:814 msgid "" "Determines what plugins automatically get precedence over all\n" " other plugins when selecting a default plugin for a command. By\n" " default, this includes the standard loaded plugins. You probably\n" " shouldn't change this if you don't know what you're doing; if you do\n" " know what you're doing, then also know that this set is\n" " case-sensitive." msgstr "" #: src/conf.py:824 msgid "" "Allows this bot's owner user to use commands\n" " that grants them shell access. This config variable exists in case you want\n" " to prevent MITM from the IRC network itself (vulnerable IRCd or IRCops)\n" " from gaining shell access to the bot's server by impersonating the owner.\n" " Setting this to False also disables plugins and commands that can be\n" " used to indirectly gain shell access." msgstr "" #: src/conf.py:839 msgid "" "Determines the interval used for\n" " the history storage." msgstr "" #: src/conf.py:842 msgid "" "Determines whether the bot will defend itself\n" " against command-flooding." msgstr "" #: src/conf.py:845 msgid "" "Determines how many commands users are\n" " allowed per minute. If a user sends more than this many commands in any\n" " 60 second period, they will be ignored for\n" " supybot.abuse.flood.command.punishment seconds." msgstr "" #: src/conf.py:850 msgid "" "Determines how many seconds the bot\n" " will ignore users who flood it with commands." msgstr "" #: src/conf.py:853 msgid "" "Determines whether the bot will notify people\n" " that they're being ignored for command flooding." msgstr "" #: src/conf.py:857 msgid "" "Determines whether the bot will defend itself\n" " against invalid command-flooding." msgstr "" #: src/conf.py:860 msgid "" "Determines how many invalid commands users\n" " are allowed per minute. If a user sends more than this many invalid\n" " commands in any 60 second period, they will be ignored for\n" " supybot.abuse.flood.command.invalid.punishment seconds. Typically, this\n" " value is lower than supybot.abuse.flood.command.maximum, since it's far\n" " less likely (and far more annoying) for users to flood with invalid\n" " commands than for them to flood with valid commands." msgstr "" #: src/conf.py:868 msgid "" "Determines how many seconds the bot\n" " will ignore users who flood it with invalid commands. Typically, this\n" " value is higher than supybot.abuse.flood.command.punishment, since it's far\n" " less likely (and far more annoying) for users to flood with invalid\n" " commands than for them to flood with valid commands." msgstr "" #: src/conf.py:874 msgid "" "Determines whether the bot will notify people\n" " that they're being ignored for invalid command flooding." msgstr "" #: src/conf.py:883 msgid "" "Determines the default length of time a\n" " driver should block waiting for input." msgstr "" #: src/conf.py:891 msgid "" "Determines what driver module the \n" " bot will use. The default is Socket which is simple and stable \n" " and supports SSL. Twisted doesn't work if the IRC server which \n" " you are connecting to has IPv6 (most of them do)." msgstr "" #: src/conf.py:897 msgid "" "Determines the maximum time the bot will\n" " wait before attempting to reconnect to an IRC server. The bot may, of\n" " course, reconnect earlier if possible." msgstr "" #: src/conf.py:949 msgid "" "Determines what directory configuration data is\n" " put into." msgstr "" #: src/conf.py:952 msgid "Determines what directory data is put into." msgstr "" #: src/conf.py:954 msgid "" "Determines what directory backup data is put\n" " into. Set it to /dev/null to disable backup (it is a special value,\n" " so it also works on Windows and systems without /dev/null)." msgstr "" #: src/conf.py:961 msgid "" "Determines what directory temporary files\n" " are put into." msgstr "" #: src/conf.py:964 msgid "" "Determines what directory files of the\n" " web server (templates, custom images, ...) are put into." msgstr "" #: src/conf.py:977 msgid "" "Determines what directories\n" " the bot will look for plugins in. Accepts a comma-separated list of\n" " strings.\n" " This means that to add another directory, you can nest the former value and\n" " add a new one. E.g. you can say: bot: 'config supybot.directories.plugins\n" " [config supybot.directories.plugins], newPluginDirectory'." msgstr "" #: src/conf.py:985 msgid "" "Determines what plugins will\n" " be loaded." msgstr "" #: src/conf.py:988 msgid "" "Determines whether the bot will always load\n" " important plugins (Admin, Channel, Config, Misc, Owner, and User)\n" " regardless of what their configured state is. Generally, if these plugins\n" " are configured not to load, you didn't do it on purpose, and you still\n" " want them to load. Users who don't want to load these plugins are smart\n" " enough to change the value of this variable appropriately :)" msgstr "" #: src/conf.py:1016 msgid "" "Determines what databases are available for use. If this\n" " value is not configured (that is, if its value is empty) then sane defaults\n" " will be provided." msgstr "" #: src/conf.py:1022 msgid "" "Determines what filename will be used\n" " for the users database. This file will go into the directory specified by\n" " the supybot.directories.conf variable." msgstr "" #: src/conf.py:1026 msgid "" "Determines how long it takes identification to\n" " time out. If the value is less than or equal to zero, identification never\n" " times out." msgstr "" #: src/conf.py:1030 msgid "" "Determines whether the bot will allow users to\n" " unregister their users. This can wreak havoc with already-existing\n" " databases, so by default we don't allow it. Enable this at your own risk.\n" " (Do also note that this does not prevent the owner of the bot from using\n" " the unregister command.)\n" " " msgstr "" #: src/conf.py:1039 msgid "" "Determines what filename will be used\n" " for the ignores database. This file will go into the directory specified\n" " by the supybot.directories.conf variable." msgstr "" #: src/conf.py:1045 msgid "" "Determines what filename will be used\n" " for the channels database. This file will go into the directory specified\n" " by the supybot.directories.conf variable." msgstr "" #: src/conf.py:1077 msgid "" "Determines whether the bot will require user\n" " registration to use 'add' commands in database-based Supybot\n" " plugins." msgstr "" #: src/conf.py:1081 msgid "" "Determines whether database-based plugins that\n" " can be channel-specific will be so. This can be overridden by individual\n" " channels. Do note that the bot needs to be restarted immediately after\n" " changing this variable or your db plugins may not work for your channel;\n" " also note that you may wish to set\n" " supybot.databases.plugins.channelSpecific.link appropriately if you wish\n" " to share a certain channel's databases globally." msgstr "" #: src/conf.py:1089 msgid "" "Determines what channel global\n" " (non-channel-specific) databases will be considered a part of. This is\n" " helpful if you've been running channel-specific for awhile and want to turn\n" " the databases for your primary channel into global databases. If\n" " supybot.databases.plugins.channelSpecific.link.allow prevents linking, the\n" " current channel will be used. Do note that the bot needs to be restarted\n" " immediately after changing this variable or your db plugins may not work\n" " for your channel." msgstr "" #: src/conf.py:1098 msgid "" "Determines whether another channel's global\n" " (non-channel-specific) databases will be allowed to link to this channel's\n" " databases. Do note that the bot needs to be restarted immediately after\n" " changing this variable or your db plugins may not work for your channel.\n" " " msgstr "" #: src/conf.py:1116 msgid "" "Determines\n" " whether CDB databases will be allowed as a database implementation." msgstr "" #: src/conf.py:1119 msgid "" "Determines how often CDB databases will have\n" " their modifications flushed to disk. When the number of modified records\n" " is greater than this fraction of the total number of records, the database\n" " will be entirely flushed to disk." msgstr "" #: src/conf.py:1211 msgid "" "Determines what will be used as the\n" " default banmask style." msgstr "" #: src/conf.py:1215 msgid "" "Determines whether the bot will strictly\n" " follow the RFC; currently this only affects what strings are\n" " considered to be nicks. If you're using a server or a network that\n" " requires you to message a nick such as services@this.network.server\n" " then you you should set this to False." msgstr "" #: src/conf.py:1222 msgid "" "Determines what certificate file (if any) the bot\n" " will use connect with SSL sockets by default." msgstr "" #: src/conf.py:1226 msgid "" "Determines what user modes the bot will request\n" " from the server when it first connects. Many people might choose +i; some\n" " networks allow +x, which indicates to the auth services on those networks\n" " that you should be given a fake host." msgstr "" #: src/conf.py:1232 msgid "" "Determines what vhost the bot will bind to before\n" " connecting a server (IRC, HTTP, ...) via IPv4." msgstr "" #: src/conf.py:1236 msgid "" "Determines what vhost the bot will bind to before\n" " connecting a server (IRC, HTTP, ...) via IPv6." msgstr "" #: src/conf.py:1240 msgid "" "Determines how many old messages the bot will\n" " keep around in its history. Changing this variable will not take effect\n" " on a network until it is reconnected." msgstr "" #: src/conf.py:1245 msgid "" "A floating point number of seconds to throttle\n" " queued messages -- that is, messages will not be sent faster than once per\n" " throttleTime seconds." msgstr "" #: src/conf.py:1250 msgid "" "Determines whether the bot will send PINGs to\n" " the server it's connected to in order to keep the connection alive and\n" " discover earlier when it breaks. Really, this option only exists for\n" " debugging purposes: you always should make it True unless you're testing\n" " some strange server issues." msgstr "" #: src/conf.py:1257 msgid "" "Determines the number of seconds between sending\n" " pings to the server, if pings are being sent to the server." msgstr "" #: src/conf.py:1262 msgid "" "Determines whether the bot will refuse\n" " duplicated messages to be queued for delivery to the server. This is a\n" " safety mechanism put in place to prevent plugins from sending the same\n" " message multiple times; most of the time it doesn't matter, unless you're\n" " doing certain kinds of plugin hacking." msgstr "" #: src/conf.py:1270 msgid "" "Determines how many seconds must elapse between\n" " JOINs sent to the server." msgstr "" #: src/conf.py:1278 msgid "" "Determines how many bytes the bot will\n" " 'peek' at when looking through a URL for a doctype or title or something\n" " similar. It'll give up after it reads this many bytes, even if it hasn't\n" " found what it was looking for." msgstr "" #: src/conf.py:1302 msgid "" "Determines what HTTP proxy all HTTP requests should go\n" " through. The value should be of the form 'host:port'." msgstr "" #: src/conf.py:1311 msgid "" "Determines whether server certificates\n" " will be verified, which checks whether the server certificate is signed\n" " by a known certificate authority, and aborts the connection if it is not." msgstr "" #: src/conf.py:1336 msgid "" "If true, uses IPV6_V6ONLY to disable\n" " forwaring of IPv4 traffic to IPv6 sockets. On *nix, has the same\n" " effect as setting kernel variable net.ipv6.bindv6only to 1." msgstr "" #: src/conf.py:1340 msgid "" "Space-separated list of IPv4 hosts the HTTP server\n" " will bind." msgstr "" #: src/conf.py:1343 msgid "" "Space-separated list of IPv6 hosts the HTTP server will\n" " bind." msgstr "" #: src/conf.py:1346 msgid "" "Determines what port the HTTP server will\n" " bind." msgstr "" #: src/conf.py:1349 msgid "" "Determines whether the server will stay\n" " alive if no plugin is using it. This also means that the server will\n" " start even if it is not used." msgstr "" #: src/conf.py:1353 msgid "" "Determines the path of the file served as\n" " favicon to browsers." msgstr "" #: src/conf.py:1361 msgid "" "Determines whether the bot will ignore\n" " unidentified users by default. Of course, that'll make it\n" " particularly hard for those users to register or identify with the bot\n" " without adding their hostmasks, but that's your problem to solve." msgstr "" #: src/conf.py:1368 msgid "" "A string that is the external IP of the bot. If this is the\n" " empty string, the bot will attempt to find out its IP dynamically (though\n" " sometimes that doesn't work, hence this variable). This variable is not used\n" " by Limnoria and its built-in plugins: see supybot.protocols.irc.vhost /\n" " supybot.protocols.irc.vhost6 to set the IRC bind host, and\n" " supybot.servers.http.hosts4 / supybot.servers.http.hosts6 to set the HTTP\n" " server bind host." msgstr "" #: src/conf.py:1387 msgid "" "Determines what the default timeout for socket\n" " objects will be. This means that *all* sockets will timeout when this many\n" " seconds has gone by (unless otherwise modified by the author of the code\n" " that uses the sockets)." msgstr "" #: src/conf.py:1393 msgid "" "Determines what file the bot should write its PID\n" " (Process ID) to, so you can kill it more easily. If it's left unset (as is\n" " the default) then no PID file will be written. A restart is required for\n" " changes to this variable to take effect." msgstr "" #: src/conf.py:1403 msgid "" "Determines whether the bot will automatically\n" " thread all commands." msgstr "" #: src/conf.py:1406 msgid "" "Determines whether the bot will automatically\n" " flush all flushers *very* often. Useful for debugging when you don't know\n" " what's breaking or when, but think that it might be logged." msgstr "" #: src/httpserver.py:62 msgid "Supybot Web server index" msgstr "" #: src/httpserver.py:67 msgid "Here is a list of the plugins that have a Web interface:" msgstr "" #: src/httpserver.py:295 msgid "" "\n" " This is a default response of the Supybot HTTP server. If you see this\n" " message, it probably means you are developing a plugin, and you have\n" " neither overriden this message or defined an handler for this query." msgstr "" #: src/httpserver.py:336 msgid "" "\n" " I am a pretty clever IRC bot, but I suck at serving Web pages, particulary\n" " if I don't know what to serve.\n" " What I'm saying is you just triggered a 404 Not Found, and I am not\n" " trained to help you in such a case." msgstr "" #: src/httpserver.py:356 msgid "Request not handled." msgstr "" #: src/httpserver.py:360 msgid "No plugins available." msgstr "" #: src/httpserver.py:378 src/httpserver.py:396 msgid "Request not handled" msgstr "" #: src/httpserver.py:421 msgid "No favicon set." msgstr "" #: src/ircutils.py:459 msgid "is an op on %L" msgstr "" #: src/ircutils.py:461 msgid "is a halfop on %L" msgstr "" #: src/ircutils.py:463 msgid "is voiced on %L" msgstr "" #: src/ircutils.py:466 msgid "is also on %L" msgstr "" #: src/ircutils.py:468 msgid "is on %L" msgstr "" #: src/ircutils.py:471 msgid "isn't on any publicly visible channels" msgstr "" #: src/ircutils.py:479 src/ircutils.py:480 src/ircutils.py:486 msgid "" msgstr "" #: src/ircutils.py:488 msgid " %s is away: %s." msgstr "" #: src/ircutils.py:493 msgid " identified" msgstr "" #: src/ircutils.py:499 msgid "%s (%s) has been%s on server %s since %s (idle for %s). %s %s.%s" msgstr "" #: src/ircutils.py:503 msgid "%s (%s) has been%s on server %s and disconnected on %s." msgstr "" #: src/questions.py:60 msgid "Sorry, that response was not an option." msgstr "" #: src/questions.py:109 msgid "Sorry, you must enter a value." msgstr "" #: src/questions.py:129 msgid "Enter password: " msgstr "" #: src/questions.py:131 msgid "Re-enter password: " msgstr "" #: src/questions.py:144 msgid "Passwords don't match." msgstr "" #: src/registry.py:218 msgid "%r is not a valid entry in %r" msgstr "" #: src/registry.py:516 msgid "Value must be either True or False (or On or Off), not %r." msgstr "" #: src/registry.py:533 msgid "Value must be an integer, not %r." msgstr "" #: src/registry.py:543 msgid "Value must be a non-negative integer, not %r." msgstr "" #: src/registry.py:552 msgid "Value must be positive (non-zero) integer, not %r." msgstr "" #: src/registry.py:561 msgid "Value must be a floating-point number, not %r." msgstr "" #: src/registry.py:577 msgid "Value must be a floating-point number greater than zero, not %r." msgstr "" #: src/registry.py:588 msgid "Value must be a floating point number in the range [0, 1], not %r." msgstr "" #: src/registry.py:603 msgid "Value should be a valid Python string, not %r." msgstr "" #: src/registry.py:637 msgid "Valid values include %L." msgstr "" #: src/registry.py:639 msgid "Valid values include %L, not %%r." msgstr "" #: src/registry.py:714 msgid "Value must be a valid regular expression, not %r." msgstr "" #: src/utils/gen.py:112 msgid "year" msgstr "" #: src/utils/gen.py:115 msgid "week" msgstr "" #: src/utils/gen.py:118 msgid "day" msgstr "" #: src/utils/gen.py:121 msgid "hour" msgstr "" #: src/utils/gen.py:125 msgid "minute" msgstr "" #: src/utils/gen.py:128 msgid "second" msgstr "" #: src/utils/gen.py:137 msgid "%s ago" msgstr "" #: src/utils/str.py:355 msgid "and" msgstr "" limnoria-2020.03.17/man/0000755000175000017500000000000013634634547014100 5ustar valval00000000000000limnoria-2020.03.17/man/supybot-adduser.10000644000175000017500000000223713634634532017312 0ustar valval00000000000000.\" Process this file with .\" groff -man -Tascii supybot-adduser.1 .\" .TH SUPYBOT-ADDUSER 1 "APRIL 2005" .SH NAME supybot-adduser \- Adds a user to a Supybot users.conf file .SH SYNOPSIS .B supybot-adduser .RI [ options ] " users.conf .SH DESCRIPTION .B supybot-adduser adds a user to the specified users.conf file. .SH OPTIONS .TP .B \-\^\-version Show version of program. .TP .BR \-h ", " \-\^\-help Show summary of options. .TP .BR \-u " NAME" "\fR,\fP \-\^\-username=" NAME Specifies the username to use for the new user. .TP .BR \-p " PASSWORD" "\fR,\fP \-\^\-password=" PASSWORD Specifies the password to use for the new user. .TP .BR \-c " CAPABILITY" "\fR,\fP \-\^\-capability=" CAPABILITY Capability the user should have; this option may be given multiple times. .SH "SEE ALSO" .IR python (1), .IR supybot (1), .IR supybot-test (1), .IR supybot-botchk (1), .IR supybot-wizard (1), .IR supybot-plugin-doc (1), .IR supybot-plugin-create (1) .SH AUTHOR This manual page was originally written by James McCoy . Permission is granted to copy, distribute and/or modify this document under the terms of the Supybot license, a BSD-style license. limnoria-2020.03.17/man/supybot-botchk.10000644000175000017500000000267413634634532017142 0ustar valval00000000000000.\" Process this file with .\" groff -man -Tascii supybot-botchk.1 .\" .TH SUPYBOT-BOTCHK 1 "APRIL 2005" .SH NAME supybot-botchk \- A script to start Supybot if it's not already running. .SH SYNOPSIS .B supybot-botchk .RI [ options ] .SH DESCRIPTION .B supybot-botchk is a script that will start Supybot if it detects that one is not currently running. This can be useful for scheduling .IR supybot (1) to run via .IR cron (8). .SH OPTIONS .TP .BR \-h ", " \-\^\-help Show summary of options. .TP .BR \-v ", " \-\^\-verbose Use verbose output when running the script. .TP .BI \-\^\-botdir= BOTDIR Determines which directory the bot be started in. .TP .BI \-\^\-pidfile= PIDFILE Specifies the name of the pidfile to look for. This should be relative to the given botdir. .TP .BI \-\^\-supybot= SUPYBOT Specifies the location of .IR supybot (1). If this is not given, it is assumed that .IR supybot (1) is in the user's $PATH. .TP .BI \-\^\-conffile= CONFFILE Specifies the path to the bot's configuration file. This will be used when (re)starting the bot. .SH "SEE ALSO" .IR python (1), .IR supybot (1), .IR supybot-test (1), .IR supybot-wizard (1), .IR supybot-adduser (1), .IR supybot-plugin-doc (1), .IR supybot-plugin-create (1) .SH AUTHOR This manual page was originally written by James McCoy . Permission is granted to copy, distribute and/or modify this document under the terms of the Supybot license, a BSD-style license. limnoria-2020.03.17/man/supybot-plugin-create.10000644000175000017500000000212713634634532020420 0ustar valval00000000000000.\" Process this file with .\" groff -man -Tascii supybot-plugin-create.1 .\" .TH SUPYBOT-PLUGIN-CREATE 1 "APRIL 2005" .SH NAME supybot-plugin-create \- A wizard for creating Supybot plugins .SH SYNOPSIS .B supybot-plugin-create .RI [ options ] .SH DESCRIPTION .B supybot-plugin-create is a wizard that creates a template python source file for a new .IR supybot (1) plugin. .SH OPTIONS .TP .B \-\^\-version Show version of program. .TP .BR \-h ", " \-\^\-help Show summary of options. .TP .BI \-n " NAME" "\fR,\fP \-\^\-name=" NAME Sets the name for the plugin. .TP .BR \-t ", " \-\^\-thread Makes the plugin threaded. .TP .BI \-\^\-real\-name= REALNAME Specify what real name the copyright is assigned to. .SH "SEE ALSO" .IR python (1), .IR supybot (1), .IR supybot-test (1), .IR supybot-botchk (1), .IR supybot-wizard (1), .IR supybot-adduser (1), .IR supybot-plugin-doc (1) .SH AUTHOR This manual page was originally written by James McCoy . Permission is granted to copy, distribute and/or modify this document under the terms of the Supybot license, a BSD-style license. limnoria-2020.03.17/man/supybot-plugin-doc.10000644000175000017500000000252413634634532017723 0ustar valval00000000000000.\" Process this file with .\" groff -man -Tascii supybot-plugin-doc.1 .\" .TH SUPYBOT-PLUGIN-DOC 1 "May 2009" .SH NAME supybot-plugin-doc \- Generates the documentation for a Supybot plugin .SH SYNOPSIS .B supybot-plugin-doc .RI [ options ] .SH DESCRIPTION .B supybot-plugin-doc is used to generate documentation (StructuredText or reStructuredText format) for a .IR supybot (1) plugin. .SH OPTIONS .TP .B \-\^\-version Show version of program. .TP .BR \-h ", " \-\^\-help Show summary of options. .TP .BR \-c ", " \-\^\-clean Clean the various data/conf/log directories after generating the docs. .TP .BR \-o ", " \-\^\-output\-dir= \fIOUTPUTDIR Specifies the directory in which to write the documentation for the plugin. .TP .BR \-f ", " \-\^\-format= \fIFORMAT Specifies which output format to use. Choices are 'rst' or 'stx'. .TP .BI \-\^\-plugins\-dir= PLUGINSDIRS Looks in the given directory for plugins and generates documentation for all of them. .SH "SEE ALSO" .IR python (1), .IR supybot (1), .IR supybot-test (1), .IR supybot-botchk (1), .IR supybot-wizard (1), .IR supybot-adduser (1), .IR supybot-plugin-create (1) .SH AUTHOR This manual page was originally written by James McCoy . Permission is granted to copy, distribute and/or modify this document under the terms of the Supybot license, a BSD-style license. limnoria-2020.03.17/man/supybot-test.10000644000175000017500000000271513634634532016643 0ustar valval00000000000000.\" Process this file with .\" groff -man -Tascii supybot-test.1 .\" .TH SUPYBOT-TEST 1 "OCTOBER 2005" .SH NAME supybot-test \- Runs the test suite for a Supybot plugin .SH SYNOPSIS .B supybot-test .RI [ options ] " plugins .SH DESCRIPTION .B supybot-test Runs the test suite for a Supybot plugin .SH OPTIONS .TP .B \-\^\-version Show version of program. .TP .BR \-h ", " \-\^\-help Show summary of options. .TP .BR \-c ", " \-\^\-clean Cleans the various data/conf/logs directories before running tests. .TP .BR \-t " TIMEOUT" "\fR,\fP \-\^\-timeout=" TIMEOUT Specifies the timeout for tests to return responses. .TP .BR \-v ", " \-\^\-verbose Sets the verbose flag, logging extra information about each test that runs. .TP .BR \-\^\-no\-network Prevents the network-based tests from being run. .TP .BR \-\^\-trace Traces all calls made. Unless you're really in a pinch, you probably shouldn't do this; it results in copious amounts of output. .TP .BR "\fR,\fP \-\^\-plugins\-dir=" PLUGINSDIR Looks in the given directory for plugins and loads the tests for all of them. .SH "SEE ALSO" .IR python (1), .IR supybot (1), .IR supybot-botchk (1), .IR supybot-wizard (1), .IR supybot-adduser (1), .IR supybot-plugin-doc (1), .IR supybot-plugin-create (1) .SH AUTHOR This manual page was originally written by James McCoy . Permission is granted to copy, distribute and/or modify this document under the terms of the Supybot license, a BSD-style license. limnoria-2020.03.17/man/supybot-wizard.10000644000175000017500000000223113634634532017155 0ustar valval00000000000000.\" Process this file with .\" groff -man -Tascii supybot-wizard.1 .\" .TH SUPYBOT-WIZARD 1 "SEPTEMBER 2004" .SH NAME supybot-wizard \- A wizard for creating Supybot configuration files .SH SYNOPSIS .B supybot-wizard .RI [ options ] .SH DESCRIPTION .B supybot-wizard is an in-depth wizard that provides a nice user interface for creating configuration files for .IR supybot (1). .SH OPTIONS .TP .B \-\^\-version Show version of program. .TP .BR \-h ", " \-\^\-help Show summary of options. .TP .B \-\^\-allow\-root Determines whether the wizard will be allowed to run as root. You do not want this. Do not do it. Even if you think you want it, you do not. .TP .B \-\^\-no\-network Determines whether the wizard will be allowed to run without a network connection. .SH "SEE ALSO" .IR python (1), .IR supybot (1), .IR supybot-test (1), .IR supybot-botchk (1), .IR supybot-adduser (1), .IR supybot-plugin-doc (1), .IR supybot-plugin-create (1) .SH AUTHOR This manual page was originally written by James McCoy . Permission is granted to copy, distribute and/or modify this document under the terms of the Supybot license, a BSD-style license. limnoria-2020.03.17/man/supybot.10000644000175000017500000000364613634634532015672 0ustar valval00000000000000.\" Process this file with .\" groff -man -Tascii supybot.1 .\" .TH SUPYBOT 1 "JULY 2009" .SH NAME supybot - A robust and user friendly Python IRC bot .SH SYNOPSIS .B supybot .RI [ options ] " configFile .SH DESCRIPTION .B Supybot is a robust, user-friendly, and programmer-friendly Python IRC bot. It aims to be an adequate replacement for most existing IRC bots. It includes a very flexible and powerful ACL system for controlling access to commands, as well as more than 50 builtin plugins providing around 400 actual commands. .SH OPTIONS .TP .B \-\^\-version Show version of program. .TP .BR \-h ", " \-\^\-help Show summary of options. .TP .BR \-P ", " \-\^\-profile Enable profiling. .TP .BI \-n " NICK" "\fR,\fP \-\^\-nick=" NICK Nick the bot should use. .TP .BI \-u " USER" "\fR,\fP \-\^\-user=" USER Full username the bot should use. .TP .BI \-i " IDENT" "\fR,\fP \-\^\-ident=" IDENT Ident the bot should use. .TP .BR \-d ", " \-\^\-daemon Determines whether the bot will daemonize. This is a no-op on non-POSIX systems. .TP .B \-\^\-allow\-default\-owner Determines whether the bot will allow its defaultCapabilities not to include "\-owner", thus giving all users the owner capability by default. This is dumb, hence we require a command-line option to enable it. .TP .B \-\^\-allow\-root Determines whether the bot will be allowed to run as root. You do not want this. Do not do it. Even if you think you want it, you do not. .TP .B \-\^\-debug Determines whether some extra debugging stuff will be logged by this script. .SH "SEE ALSO" .IR python (1), .IR supybot-test (1), .IR supybot-botchk (1), .IR supybot-wizard (1), .IR supybot-adduser (1), .IR supybot-plugin-doc (1), .IR supybot-plugin-create (1) .SH AUTHOR This manual page was originally written by James McCoy . Permission is granted to copy, distribute and/or modify this document under the terms of the Supybot license, a BSD-style license. limnoria-2020.03.17/plugins/0000755000175000017500000000000013634634547015006 5ustar valval00000000000000limnoria-2020.03.17/plugins/Admin/0000755000175000017500000000000013634634547016036 5ustar valval00000000000000limnoria-2020.03.17/plugins/Admin/__init__.py0000644000175000017500000000451213634634532020143 0ustar valval00000000000000### # Copyright (c) 2004-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ These are commands useful for administrating the bot; they all require their caller to have the 'admin' capability. This plugin is loaded by default. """ import supybot import supybot.world as world __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # 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%%" # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload reload(plugin) # In case we're being reloaded. if world.testing: from . import test Class = plugin.Class configure = config.configure limnoria-2020.03.17/plugins/Admin/config.py0000644000175000017500000000433413634634532017653 0ustar valval00000000000000### # Copyright (c) 2004-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Admin') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Admin', True) Admin = conf.registerPlugin('Admin') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Admin/locales/0000755000175000017500000000000013634634547017460 5ustar valval00000000000000limnoria-2020.03.17/plugins/Admin/locales/de.po0000644000175000017500000001627613634634532020416 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2014-08-17 13:46+CEST\n" "PO-Revision-Date: 2011-10-31 13:37+0100\n" "Last-Translator: Florian Besser \n" "Language-Team: German \n" "Language: de\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: German\n" "X-Poedit-Country: Germany\n" #: plugin.py:54 msgid "Nick/channel temporarily unavailable." msgstr "Nick/Kanal temporär nicht verfügbar." #: plugin.py:72 msgid "Cannot join %s, it's full." msgstr "Kann %s nicht beitreten, der Kanal ist voll." #: plugin.py:80 msgid "Cannot join %s, I was not invited." msgstr "Kann %s nicht beitreten, ich wurde nicht eingeladen." #: plugin.py:88 msgid "Cannot join %s, I am banned." msgstr "Ich kann %s nicht betreten, ich bin gebannt." #: plugin.py:96 msgid "Cannot join %s, my keyword was wrong." msgstr "Ich kann %s nicht beitreten, mein Schlüsselwort ist falsch." #: plugin.py:104 plugin.py:113 msgid "Cannot join %s, I'm not identified with NickServ." msgstr "Ich kann %s nicht betreten, ich bin nicht mit NickServ identifiziert." #: plugin.py:143 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" "Sagt dem Bot dem angegeben Kanal beizutreten. Falls angegeben " "wird, wird dieser benutzt um zu versuchen den Kanal zu betreten." #: plugin.py:156 msgid "I'm already too close to maximum number of channels for this network." msgstr "Ich bin schon zu nah an den maximalen Kanälen für dieses Netzwerk." #: plugin.py:165 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 "" "hat keine Argumenten\n" "\n" "Gibt die Kanäle aus in denen der Bot sich befindet. Dieser Befehl muss " "privat gegeben werden, um das Geheimnis der geheimen Kanale zu wahren." #: plugin.py:175 msgid "I'm not currently in any channels." msgstr "Momentan bin ich in keinen Kanälen." #: plugin.py:181 msgid "My connection is restricted, I can't change nicks." msgstr "Meine Verbindung ist begrenzt, I kann meinen Nick nicht wechseln." #: plugin.py:188 msgid "Someone else is already using that nick." msgstr "Jemand anderes benutzt diesen Nick schon." #: plugin.py:195 #, fuzzy msgid "I can't change nick, I'm currently banned in %s." msgstr "Ich kann meinen Nick nicht ändern, der Server sagte %q." #: plugin.py:203 msgid "I can't change nicks, the server said %q." msgstr "Ich kann meinen Nick nicht ändern, der Server sagte %q." #: plugin.py:217 #, fuzzy 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" "Ändert den Nick des Bots zu . Falls nicht angegeben wird, wird " "der momentane Botnick zurückgegeben." #: plugin.py:234 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" "Sagt dem Bot die Liste von angebenen Kanälen zu verlassen. ist nur " "notwendig, falls der Bot einen anderen Kanal als den Momentanen verlassen " "soll. Falls angegeben wird, wird dies als Verlassensnachricht " "verwendet." #: plugin.py:252 msgid "I'm not in %s." msgstr "Ich bin nicht in %s." #: plugin.py:264 msgid "" " \n" "\n" " Gives the user specified by (or the user to whom " "\n" " currently maps) the specified capability \n" " " msgstr "" " \n" "\n" "Gibt dem angebenen Benutzer (oder dem auf den die " "zutrifft) die angegebene Fähigkeit." #: plugin.py:284 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 "" "Die \"owner\" Fähigkeit kann nicht über den Bot hinzugefügt werden. Benutze " "das supybot-adduser Programm (oder verändere users.conf per Hand) um die " "Besitzer Fähigkeit hinzuzufügen." #: plugin.py:295 msgid "You can't add capabilities you don't have." msgstr "Du kannst keine Fähigkeiten hinzufügen, die du nicht hast." #: plugin.py:300 msgid "" " \n" "\n" " Takes from the user specified by (or the user to whom\n" " currently maps) the specified capability " "\n" " " msgstr "" " \n" "\n" "Nimmt dem Benutzer der durch (oder dem Benutzer auf den momentan " " zeigt) angeben wird die angegeben Fähigkeit ." #: plugin.py:312 msgid "That user doesn't have that capability." msgstr "Der Benutzer hat diese Fähigkeit nicht." #: plugin.py:314 msgid "You can't remove capabilities you don't have." msgstr "Du kannst keine Fähigkeiten entfernen, die du nicht hast." #: plugin.py:322 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" "Es wird eine beständige Ignorierung auf oder auf die Hostmaske " "die momentan mit verbunden wird gesetzt. ist " "optional, das legt fest wann die Ignorierung abläuft;falls dies nicht " "angegeben wird, wird die Ignorierung niemals ablaufen." #: plugin.py:335 msgid "" "\n" "\n" " This will remove the persistent ignore on or the\n" " hostmask currently associated with .\n" " " msgstr "" "\n" "\n" "Wird die beständige Ignorierung, von oder der Hostmaske die " "momentan mit dem verbunden wird, aufheben." #: plugin.py:344 msgid "%s wasn't in the ignores database." msgstr "%s war nicht in der Datenbank für Ignorierungen." #: plugin.py:349 msgid "" "takes no arguments\n" "\n" " Lists the hostmasks that the bot is ignoring.\n" " " msgstr "" "hat keine Argumente\n" "\n" "Listet die Hostmasken auf, die der Bot ignoriert." #: plugin.py:357 msgid "I'm not currently globally ignoring anyone." msgstr "Momentan ignoriere ich niemanden global." #: plugin.py:361 msgid "" "takes no arguments\n" "\n" " Clears the current send queue for this network.\n" " " msgstr "" "hat keine Argumente\n" "\n" "Leert die momentane Sendenwarteschlange für dieses Netzwerk." #~ msgid "That nick is currently banned." #~ msgstr "Dieser Nick ist momentan gebannt." limnoria-2020.03.17/plugins/Admin/locales/es.po0000644000175000017500000001762713634634532020436 0ustar valval00000000000000# Spanish translation for limnoria # Copyright (c) 2015 Limnoria Contributors 2015 # This file is distributed under the same license as the Limnoria package. # Aaron Farias , 2015. # msgid "" msgstr "" "Project-Id-Version: limnoria\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2014-12-24 15:42+0000\n" "PO-Revision-Date: 2015-01-01 16:35+0000\n" "Last-Translator: Aaron Farias \n" "Language-Team: Spanish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-01-01 16:48+0000\n" "X-Generator: Launchpad (build 17286)\n" #: plugin.py:46 #, docstring msgid "" "This plugin provides access to administrative commands, such as\n" " adding capabilities, managing ignore lists, and joining channels.\n" " This is a core Supybot plugin that should not be removed!" msgstr "" "Este plugin proporciona acceso a los comandos de administración, tales como\n" "la adición de capacidades, la gestión ignoran listas y canales de unión.\n" "Se trata de un complemento del núcleo Supybot que no se debe quitar!" #: plugin.py:57 #, docstring msgid "Nick/channel temporarily unavailable." msgstr "Nick/canal disponible temporalmente." #: plugin.py:75 msgid "Cannot join %s, it's full." msgstr "No puede unirse a %s, está lleno." #: plugin.py:83 msgid "Cannot join %s, I was not invited." msgstr "No puede unirse a %s, no me invitaron." #: plugin.py:91 msgid "Cannot join %s, I am banned." msgstr "No pudo unirse a %s, estoy baneado." #: plugin.py:99 msgid "Cannot join %s, my keyword was wrong." msgstr "No puede unirse a %s, mi palabra estaba mal." #: plugin.py:107 plugin.py:116 msgid "Cannot join %s, I'm not identified with NickServ." msgstr "No puede unirse a %s, yo no estoy identificado con NickServ." #: plugin.py:146 #, docstring 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" "Dígale al bot para unirse al canal dado. Si se da, que se utiliza\n" "al intentar entrar al canal.\n" " " #: plugin.py:159 msgid "I'm already too close to maximum number of channels for this network." msgstr "Ya estoy demasiado cerca de número máximo de canales para esta red." #: plugin.py:168 #, docstring 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 "" "no tiene argumentos\n" "\n" "Devuelve los canales del bot está encendido. Debe ser dado en privado, con " "el fin\n" "para proteger el secreto de canales secretos.\n" " " #: plugin.py:178 msgid "I'm not currently in any channels." msgstr "No estoy actualmente en ningún canal." #: plugin.py:184 msgid "My connection is restricted, I can't change nicks." msgstr "Mi conexión es restringida, no puedo cambiar nicks." #: plugin.py:191 msgid "Someone else is already using that nick." msgstr "Alguien más ya está utilizando ese nick." #: plugin.py:198 msgid "I can't change nick, I'm currently banned in %s." msgstr "No puedo cambiar de nick, actualmente Estoy prohibido en %s." #: plugin.py:206 msgid "I can't change nicks, the server said %q." msgstr "No puedo cambiar de nicks, dijo el servidor %q." #: plugin.py:220 #, docstring 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" "Cambia el nick del bot a . Si no se da nick, devuelve el\n" "actual nick del bot.\n" " " #: plugin.py:237 #, docstring 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" "Le dice al bot a parte de la lista de canales que le des. es\n" "sólo es necesario si desea que el bot a desprenderse de un canal que no sea " "el\n" "canal actual. Si se especifica, lo utilizan como parte\n" "mensaje.\n" " " #: plugin.py:255 msgid "I'm not in %s." msgstr "No estoy en %s." #: plugin.py:267 #, docstring msgid "" " \n" "\n" " Gives the user specified by (or the user to whom " "\n" " currently maps) the specified capability \n" " " msgstr "" " \n" "\n" "Le da al usuario especificado por (o el usuario a quien \n" "Actualmente los mapas) la capacidad especificada \n" " " #: plugin.py:287 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 "" "La capacidad de \"propietario\" no se puede añadir en el bot. Utilice el " "programa de usuario Supybot-add (o editar el users.conf archivo usted mismo) " "para añadir una capacidad de propietario." #: plugin.py:298 msgid "You can't add capabilities you don't have." msgstr "No se puede agregar capacidades que usted no tiene." #: plugin.py:303 #, docstring msgid "" " \n" "\n" " Takes from the user specified by (or the user to whom\n" " currently maps) the specified capability " "\n" " " msgstr "" " \n" "\n" "Toma del usuario especificado por (o el usuario a quien\n" " Actualmente los mapas) la capacidad especificada \n" " " #: plugin.py:315 msgid "That user doesn't have that capability." msgstr "Ese usuario no tiene esa capacidad." #: plugin.py:317 msgid "You can't remove capabilities you don't have." msgstr "No se puede quitar capacidades que usted no tiene." #: plugin.py:325 #, docstring 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" "Esto establecerá una persistente ignoran en o la hostmask\n" "actualmente asociados con . es un argumento opcional\n" "especificar cuándo (en \"segundo a partir de ahora\") expirará el ignorar; " "si\n" "no se le da, el ignorar nunca expirará automáticamente.\n" " " #: plugin.py:338 #, docstring msgid "" "\n" "\n" " This will remove the persistent ignore on or the\n" " hostmask currently associated with .\n" " " msgstr "" "\n" "\n" "Esto eliminará la persistente ignoran en o la\n" "hostmask actualmente asociada con .\n" " " #: plugin.py:347 msgid "%s wasn't in the ignores database." msgstr "%S no estaba en la base de datos ignorados." #: plugin.py:352 #, docstring msgid "" "takes no arguments\n" "\n" " Lists the hostmasks that the bot is ignoring.\n" " " msgstr "" "no tiene argumentos\n" "\n" "Enumera los sus hosts que el bot está ignorando.\n" " " #: plugin.py:360 msgid "I'm not currently globally ignoring anyone." msgstr "No estoy actualmente haciendo caso omiso a nivel mundial a nadie." #: plugin.py:364 #, docstring msgid "" "takes no arguments\n" "\n" " Clears the current send queue for this network.\n" " " msgstr "" "no tiene argumentos\n" "\n" "Borra la cola de envío actual de esta red.\n" " " limnoria-2020.03.17/plugins/Admin/locales/fi.po0000644000175000017500000002022613634634532020412 0ustar valval00000000000000# Admin plugin in Limnoria. # Copyright (C) 2011 Limnoria # Mikaela Suomalainen , 2011. # msgid "" msgstr "" "Project-Id-Version: Finnish translation of Admin plugin in Supybot\n" "POT-Creation-Date: 2014-12-20 11:59+EET\n" "PO-Revision-Date: 2014-12-20 12:19+0200\n" "Last-Translator: Mikaela Suomalainen \n" "Language-Team: \n" "Language: fi_FI\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-Generator: Poedit 1.6.10\n" #: plugin.py:46 msgid "" "This plugin provides access to administrative commands, such as\n" " adding capabilities, managing ignore lists, and joining channels.\n" " This is a core Supybot plugin that should not be removed!" msgstr "" "Tämä plugin antaa pääsyn ylläpitäviin komentoihin, kuten valtuuksien " "lisäämiseen,\n" " ignore-listojen hallintaan ja kanaville liittymiseen. Tämä on ydin Supybot-" "plugin, jota ei pitäisi poistaa!s" #: plugin.py:57 msgid "Nick/channel temporarily unavailable." msgstr "Nimimerkki/kanava on väliaikaisesti saavuttamattomissa." #: plugin.py:75 msgid "Cannot join %s, it's full." msgstr "Ei voida liittyä kanavalle %s, se on täynnä." #: plugin.py:83 msgid "Cannot join %s, I was not invited." msgstr "Ei voi liittyä kanavalle %s, minua ei ole kutsuttu." #: plugin.py:91 msgid "Cannot join %s, I am banned." msgstr "Ei voi liittyä kanavalle %s, se on antanut minulle porttikiellon." #: plugin.py:99 msgid "Cannot join %s, my keyword was wrong." msgstr "En voi liittyä kanavalle %s, minun avainsana oli väärä." #: plugin.py:107 plugin.py:116 msgid "Cannot join %s, I'm not identified with NickServ." msgstr "En voi liittyä kanavalle %s, koska en ole tunnistautunut NickServille." #: plugin.py:146 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:159 msgid "I'm already too close to maximum number of channels for this network." msgstr "Minä olen jo liian lähellä kanavien maksimimäärää tässä verkossa." #: plugin.py:168 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ä salaistenkanavien\n" " salaisuuden suojelemiseksi.\n" " " #: plugin.py:178 msgid "I'm not currently in any channels." msgstr "En juuri nyt ole millään kanavalla." #: plugin.py:184 msgid "My connection is restricted, I can't change nicks." msgstr "Minun yhteyteni on rajoitettu. En voi vaihtaa nimimerkkiä." #: plugin.py:191 msgid "Someone else is already using that nick." msgstr "Joku muu käyttää jo tuota nimimerkkiä." #: plugin.py:198 #, fuzzy msgid "I can't change nick, I'm currently banned in %s." msgstr "Minä en voi vaihtaa nimimerkkiä, koska palvelin sanoi %q" #: plugin.py:206 msgid "I can't change nicks, the server said %q." msgstr "Minä en voi vaihtaa nimimerkkiä, koska palvelin sanoi %q" #: plugin.py:220 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 . Mikäli nimimerkkiä ei anneta, " "botin nykyinen\n" " nimimerkki palautetaan.\n" " " #: plugin.py:237 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 jos haluat botin poistuvat muulta, kuin \n" " nykyiseltä kanavalta. Jos on määritetty, sitä käytetään\n" " poistumisviestissä.\n" " " #: plugin.py:255 msgid "I'm not in %s." msgstr "Minä en ole kanavalla %s." #: plugin.py:267 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:287 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 voi lisätä botissa. Käytä supybot-adduser ohjelmaa " "(tai muokkaa users.conf tiedostoa itse) lisätäksesi owner valtuuden." #: plugin.py:298 msgid "You can't add capabilities you don't have." msgstr "Et voi lisätä valtuuksia, joita sinulla ei ole." #: plugin.py:303 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ä " "johon\n" " sopii) määritetyn valtuuden \n" " " #: plugin.py:315 msgid "That user doesn't have that capability." msgstr "Tuolla käyttäjällä ei tuota valtuutta." #: plugin.py:317 msgid "You can't remove capabilities you don't have." msgstr "Sinä et voi poistaa valtuuksia, joita sinulla ei ole." #: plugin.py:325 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 huomiotta jättämisen tai " "hostmaskiin,\n" " joka on tällä hetkellä yhdistetty . on " "vaihtoehtoinen paremetri,\n" " joka määrittää (\"sekuntit\") joiden jälkeen huomiotta jättäminen " "poistetaan; jos\n" " sitä ei ole annettu, huomiotta jättäminen ei vanhene ikinä " "automaattisesti.\n" " " #: plugin.py:338 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 huomiotta jättämisen tai\n" " hostmaskista joka on tällä hetkellä yhdistetty .\n" " " #: plugin.py:347 msgid "%s wasn't in the ignores database." msgstr "%s ei ollut huomiotta jätettävien tietokannassa." #: plugin.py:352 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 huomiotta jättämis listalla.\n" " " #: plugin.py:360 msgid "I'm not currently globally ignoring anyone." msgstr "En tällä hetkellä jätä ketään huomioitta globaalisti." #: plugin.py:364 msgid "" "takes no arguments\n" "\n" " Clears the current send queue for this network.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Tyhjentää nykyisen lähetysjonon tälle verkolle.\n" " " #~ msgid "That nick is currently banned." #~ msgstr "Tuolla nimimerkillä on tällähetkellä porttikielto." limnoria-2020.03.17/plugins/Admin/locales/fr.po0000644000175000017500000001570013634634532020424 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-08-17 13:46+CEST\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: ProgVal \n" "Language: fr_FR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: Ascii\n" "X-Generator: Poedit 1.5.4\n" #: plugin.py:54 msgid "Nick/channel temporarily unavailable." msgstr "Nick/canal temporairement indisponible" #: plugin.py:72 msgid "Cannot join %s, it's full." msgstr "Ne peut joindre %s, il est plein." #: plugin.py:80 msgid "Cannot join %s, I was not invited." msgstr "Ne peut joindre %s, pas invité." #: plugin.py:88 msgid "Cannot join %s, I am banned." msgstr "Ne peut joindre %s, j'y suis banni." #: plugin.py:96 msgid "Cannot join %s, my keyword was wrong." msgstr "Ne peut joindre %s, mon mot de passe est mauvais." #: plugin.py:104 plugin.py:113 msgid "Cannot join %s, I'm not identified with NickServ." msgstr "Ne peut joindre %s, je ne suis pas identifié auprès de NickServ." #: plugin.py:143 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" "Dit au bot de rejoindre le canal donné. Si la est donnée, elle est " "utilisée pour rejoindre le canal." #: plugin.py:156 msgid "I'm already too close to maximum number of channels for this network." msgstr "Je suis déjà sur un nombre de canaux trop grand pour ce réseau." #: plugin.py:165 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 "" "Ne prend pas d'argument \n" "\n" "Retourne les canaux sur lesquels le bot est. Doit être en privé, dans le but " "d'éviter que les canaux secrets ne soient divulgués." #: plugin.py:175 msgid "I'm not currently in any channels." msgstr "Je ne suis actuellement sur aucun canal." #: plugin.py:181 msgid "My connection is restricted, I can't change nicks." msgstr "Ma connexion est restreinte, je ne peux changer de nick." #: plugin.py:188 msgid "Someone else is already using that nick." msgstr "Quelqu'un d'autre utilise déjà ce nick." #: plugin.py:195 #, fuzzy msgid "I can't change nick, I'm currently banned in %s." msgstr "Je ne peux changer de nick, le serveur a dit %q." #: plugin.py:203 msgid "I can't change nicks, the server said %q." msgstr "Je ne peux changer de nick, le serveur a dit %q." #: plugin.py:217 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" "Change le nick du bot à . Si aucun nick n'est donné, retourne le nick " "actuel du bot." #: plugin.py:234 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" "Dit au bot de partir de la liste de canaux que vous avez donnée. " "n'est nécessaire que si vous voulez que le bot parte d'un autre canal que " "l'actuel. Si la est spécifiée, elle est utilisée comme message de " "départ." #: plugin.py:252 msgid "I'm not in %s." msgstr "Je ne suis pas sur %s." #: plugin.py:264 msgid "" " \n" "\n" " Gives the user specified by (or the user to whom " "\n" " currently maps) the specified capability \n" " " msgstr "" " \n" "\n" "Donne la à l'utilisateur spécifié par (ou l'utilisateur à " "qui correspond )." #: plugin.py:284 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 "" "La capacité \"owner\" ne peut être ajoutée via le bot. Utilisez le programme " "supybot-adduser (ou éditez le fichier users.conf vous-même) pour ajouter la " "capacité owner." #: plugin.py:295 msgid "You can't add capabilities you don't have." msgstr "Vous ne pouvez ajouter des capacités que vous n'avez pas." #: plugin.py:300 msgid "" " \n" "\n" " Takes from the user specified by (or the user to whom\n" " currently maps) the specified capability " "\n" " " msgstr "" " \n" "\n" "Retire la à l'utilisateur spécifié par le (ou celui à qui " "correspond le )." #: plugin.py:312 msgid "That user doesn't have that capability." msgstr "Cet utilisateur n'a pas cette capacité." #: plugin.py:314 msgid "You can't remove capabilities you don't have." msgstr "Vous ne pouvez retirer des capacités que vous n'avez pas." #: plugin.py:322 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" "Ajoute un masque d'ignorance persistant sur le , ou sur le " "masque d'hôte de . est un argument optionnel spécifiant " "quand (en \"secondes à partir de maintenant\") l'ignorance expirera ; si " "elle n'est pas donnée, l'ignorance n'expirera jamais." #: plugin.py:335 msgid "" "\n" "\n" " This will remove the persistent ignore on or the\n" " hostmask currently associated with .\n" " " msgstr "" "\n" "\n" "Ceci retirera le masque d'ignorance persistant sur le , ou " "sur le masque d'hôte associé au ." #: plugin.py:344 msgid "%s wasn't in the ignores database." msgstr "%s n'étais pas dans ma base de données d'ignorance." #: plugin.py:349 msgid "" "takes no arguments\n" "\n" " Lists the hostmasks that the bot is ignoring.\n" " " msgstr "" "Ne prend pas d'argument\n" "\n" "Liste les masques d'hôte que le bot ignore." #: plugin.py:357 msgid "I'm not currently globally ignoring anyone." msgstr "Je n'ignore actuellement personne globalement." #: plugin.py:361 msgid "" "takes no arguments\n" "\n" " Clears the current send queue for this network.\n" " " msgstr "" "Ne prend pas d'argument\n" "\n" "Vide la queue en attente pour ce réseau." #~ msgid "That nick is currently banned." #~ msgstr "Ce nick est banni." limnoria-2020.03.17/plugins/Admin/locales/it.po0000644000175000017500000001630313634634532020431 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-08-17 13:46+CEST\n" "PO-Revision-Date: 2012-03-15 20:25+0100\n" "Last-Translator: skizzhg \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugin.py:54 msgid "Nick/channel temporarily unavailable." msgstr "Nick/canale temporaneamente non disponibile." #: plugin.py:72 msgid "Cannot join %s, it's full." msgstr "Non posso entrare in %s, è pieno." #: plugin.py:80 msgid "Cannot join %s, I was not invited." msgstr "Non posso entrare in %s, non sono stato invitato." #: plugin.py:88 msgid "Cannot join %s, I am banned." msgstr "Non posso entrare in %s, sono stato bannato." #: plugin.py:96 msgid "Cannot join %s, my keyword was wrong." msgstr "Non posso entrare in %s, la password era sbagliata." #: plugin.py:104 plugin.py:113 msgid "Cannot join %s, I'm not identified with NickServ." msgstr "Non posso entrare in %s, non sono identificato con NickServ." #: plugin.py:143 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" " Dice al bot di entrare nel canale specificato. Se è " "fornita,\n" " viene usata quando si tenta di entrare nel canale.\n" " " #: plugin.py:156 msgid "I'm already too close to maximum number of channels for this network." msgstr "Sono già troppo vicino al numero massimo di canali per questa rete." #: plugin.py:165 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 "" "non necessita argomenti\n" "\n" " Restituisce i canali dove è presente il bot. Deve essere richiesto " "in\n" " privato per preservare la segretezza dei canali privati.\n" " " #: plugin.py:175 msgid "I'm not currently in any channels." msgstr "Al momento non sono in nessun canale." #: plugin.py:181 msgid "My connection is restricted, I can't change nicks." msgstr "La mia connessione è limitata, non posso cambiare nick." #: plugin.py:188 msgid "Someone else is already using that nick." msgstr "Qualcun altro sta utilizzando questo nick." #: plugin.py:195 #, fuzzy msgid "I can't change nick, I'm currently banned in %s." msgstr "Non posso cambiare nick, il server ha detto %q." #: plugin.py:203 msgid "I can't change nicks, the server said %q." msgstr "Non posso cambiare nick, il server ha detto %q." #: plugin.py:217 #, fuzzy 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" " Cambia il nick del bot in . Se non ne viene fornito uno, " "restituisce\n" " quello attuale.\n" " " #: plugin.py:234 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" " Fornisce al bot l'elenco dei canali da cui uscire. è " "necessario\n" " solo se si vuole far uscire il bot da un canale diverso da quello " "attuale.\n" " Se viene specificato, verrà usato come messaggio di " "uscita.\n" " " #: plugin.py:252 msgid "I'm not in %s." msgstr "Non sono in %s." #: plugin.py:264 msgid "" " \n" "\n" " Gives the user specified by (or the user to whom " "\n" " currently maps) the specified capability \n" " " msgstr "" " \n" "\n" " Dà all'utente specificato da (o quello a cui corrisponde\n" " attualmente) la specificata.\n" " " #: plugin.py:284 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 "" "La capacità \"owner\" non può essere aggiunta al bot. Utilizzare il " "programma supybot-adduser (o modificare il file users.conf) per aggiungerla." #: plugin.py:295 msgid "You can't add capabilities you don't have." msgstr "Non puoi aggiungere capacità che non hai." #: plugin.py:300 msgid "" " \n" "\n" " Takes from the user specified by (or the user to whom\n" " currently maps) the specified capability " "\n" " " msgstr "" " \n" "\n" " Rimuove l'utente specificato da (o quello a cui " "corrisponde\n" " attualmente) la specificata\n" " " #: plugin.py:312 msgid "That user doesn't have that capability." msgstr "Questo utente non ha tale capacità." #: plugin.py:314 msgid "You can't remove capabilities you don't have." msgstr "Non puoi rimuovere capacità che non hai." #: plugin.py:322 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" " Imposta un ignore permanente su o l'hostmask " "attualmente\n" " associata a . è un argomento opzionale per " "specificare\n" " quando (in \"secondi a partire da subito\") scadrà l'ignore; se " "non fornito,\n" " questo non scadrà mai.\n" " " #: plugin.py:335 msgid "" "\n" "\n" " This will remove the persistent ignore on or the\n" " hostmask currently associated with .\n" " " msgstr "" "\n" "\n" " Rimuove l'ignore persistente su o l'attuale hostmask " "associata a .\n" " " #: plugin.py:344 msgid "%s wasn't in the ignores database." msgstr "%s non è nel mio database degli ignorati." #: plugin.py:349 msgid "" "takes no arguments\n" "\n" " Lists the hostmasks that the bot is ignoring.\n" " " msgstr "" "Non necessita argomenti\n" "\n" " Elenca le hostmask che il bot sta ignorando.\n" " " #: plugin.py:357 msgid "I'm not currently globally ignoring anyone." msgstr "Al momento non sto ignorando nessuno." #: plugin.py:361 msgid "" "takes no arguments\n" "\n" " Clears the current send queue for this network.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Pulisce l'attuale coda dei messaggi da inviare (interrompe il flood) " "per questa rete.\n" " " #~ msgid "That nick is currently banned." #~ msgstr "Il nick è attualmente bannato." limnoria-2020.03.17/plugins/Admin/plugin.py0000644000175000017500000003536613634634532017715 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2010, Valentin Lorentz # 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 sys import time import supybot.conf as conf import supybot.ircdb as ircdb import supybot.utils as utils from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.schedule as schedule import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Admin') class Admin(callbacks.Plugin): """This plugin provides access to administrative commands, such as adding capabilities, managing ignore lists, and joining channels. This is a core Supybot plugin that should not be removed!""" def __init__(self, irc): self.__parent = super(Admin, self) self.__parent.__init__(irc) self.joins = {} self.pendingNickChanges = {} @internationalizeDocstring def do437(self, irc, msg): """Nick/channel temporarily unavailable.""" target = msg.args[0] t = time.time() + 30 if irc.isChannel(target): # Let's schedule a rejoin. networkGroup = conf.supybot.networks.get(irc.network) def rejoin(): irc.queueMsg(networkGroup.channels.join(target)) # We don't need to schedule something because we'll get another # 437 when we try to join later. schedule.addEvent(rejoin, t) self.log.info('Scheduling a rejoin to %s at %s; ' 'Channel temporarily unavailable.', target, t) else: irc = self.pendingNickChanges.get(irc, None) if irc is not None: def nick(): irc.queueMsg(ircmsgs.nick(target)) schedule.addEvent(nick, t) self.log.info('Scheduling a nick change to %s at %s; ' 'Nick temporarily unavailable.', target, t) else: self.log.debug('Got 437 without Admin.nick being called.') def do471(self, irc, msg): try: channel = msg.args[1] (irc, msg) = self.joins.pop(channel) irc.error(_('Cannot join %s, it\'s full.') % channel) except KeyError: self.log.debug('Got 471 without Admin.join being called.') def do473(self, irc, msg): try: channel = msg.args[1] (irc, msg) = self.joins.pop(channel) irc.error(_('Cannot join %s, I was not invited.') % channel) except KeyError: self.log.debug('Got 473 without Admin.join being called.') def do474(self, irc, msg): try: channel = msg.args[1] (irc, msg) = self.joins.pop(channel) irc.error(_('Cannot join %s, I am banned.') % channel) except KeyError: self.log.debug('Got 474 without Admin.join being called.') def do475(self, irc, msg): try: channel = msg.args[1] (irc, msg) = self.joins.pop(channel) irc.error(_('Cannot join %s, my keyword was wrong.') % channel) except KeyError: self.log.debug('Got 475 without Admin.join being called.') def do477(self, irc, msg): try: channel = msg.args[1] (irc,msg) = self.joins.pop(channel) irc.error(_('Cannot join %s, I\'m not identified with ' 'NickServ.') % channel) except KeyError: self.log.debug('Got 477 without Admin.join being called.') def do515(self, irc, msg): try: channel = msg.args[1] (irc, msg) = self.joins.pop(channel) irc.error(_('Cannot join %s, I\'m not identified with ' 'NickServ.') % channel) except KeyError: self.log.debug('Got 515 without Admin.join being called.') def doJoin(self, irc, msg): if msg.prefix == irc.prefix: try: del self.joins[msg.args[0]] except KeyError: s = 'Joined a channel without Admin.join being called.' self.log.debug(s) def doInvite(self, irc, msg): channel = msg.args[1] if channel not in irc.state.channels: if conf.supybot.alwaysJoinOnInvite.get(channel)() or \ ircdb.checkCapability(msg.prefix, 'admin'): self.log.info('Invited to %s by %s.', channel, msg.prefix) networkGroup = conf.supybot.networks.get(irc.network) irc.queueMsg(networkGroup.channels.join(channel)) conf.supybot.networks.get(irc.network).channels().add(channel) else: self.log.warning('Invited to %s by %s, but ' 'supybot.alwaysJoinOnInvite was False and ' 'the user lacked the "admin" capability.', channel, msg.prefix) @internationalizeDocstring def join(self, irc, msg, args, channel, key): """ [] Tell the bot to join the given channel. If is given, it is used when attempting to join the channel. """ if not irc.isChannel(channel): irc.errorInvalid(_('channel'), channel, Raise=True) networkGroup = conf.supybot.networks.get(irc.network) networkGroup.channels().add(channel) if key: networkGroup.channels.key.get(channel).setValue(key) maxchannels = irc.state.supported.get('maxchannels', sys.maxsize) if len(irc.state.channels) + 1 > maxchannels: irc.error(_('I\'m already too close to maximum number of ' 'channels for this network.'), Raise=True) irc.queueMsg(networkGroup.channels.join(channel)) irc.noReply() self.joins[channel] = (irc, msg) join = wrap(join, ['validChannel', additional('something')]) @internationalizeDocstring def channels(self, irc, msg, args): """takes no arguments Returns the channels the bot is on. """ L = irc.state.channels.keys() if L: utils.sortBy(ircutils.toLower, L) irc.reply(format('%L', L), private=True) else: irc.reply(_('I\'m not currently in any channels.')) channels = wrap(channels) def do484(self, irc, msg): irc = self.pendingNickChanges.get(irc, None) if irc is not None: irc.error(_('My connection is restricted, I can\'t change nicks.')) else: self.log.debug('Got 484 without Admin.nick being called.') def do433(self, irc, msg): irc = self.pendingNickChanges.get(irc, None) if irc is not None: irc.error(_('Someone else is already using that nick.')) else: self.log.debug('Got 433 without Admin.nick being called.') def do435(self, irc, msg): irc = self.pendingNickChanges.get(irc, None) if irc is not None: irc.error(_('I can\'t change nick, I\'m currently banned in %s.') % msg.args[2]) else: self.log.debug('Got 435 without Admin.nick being called.') def do438(self, irc, msg): irc = self.pendingNickChanges.get(irc, None) if irc is not None: irc.error(format(_('I can\'t change nicks, the server said %q.'), msg.args[2]), private=True) else: self.log.debug('Got 438 without Admin.nick being called.') def doNick(self, irc, msg): if msg.nick == irc.nick or msg.args[0] == irc.nick: try: del self.pendingNickChanges[irc] except KeyError: self.log.debug('Got NICK without Admin.nick being called.') @internationalizeDocstring def nick(self, irc, msg, args, nick, network): """[] [] Changes the bot's nick to . If no nick is given, returns the bot's current nick. """ network = network or irc.network if nick: group = getattr(conf.supybot.networks, network) group.nick.setValue(nick) irc.queueMsg(ircmsgs.nick(nick)) self.pendingNickChanges[irc.getRealIrc()] = irc else: irc.reply(irc.nick) nick = wrap(nick, [additional('nick'), additional('something')]) class capability(callbacks.Commands): @internationalizeDocstring def add(self, irc, msg, args, user, capability): """ Gives the user specified by (or the user to whom currently maps) the specified capability """ # Ok, the concepts that are important with capabilities: # ### 1) No user should be able to elevate their privilege to owner. ### 2) Admin users are *not* superior to #channel.ops, and don't ### have God-like powers over channels. ### 3) We assume that Admin users are two things: non-malicious and ### and greedy for power. So they'll try to elevate their ### privilege to owner, but they won't try to crash the bot for ### no reason. # Thus, the owner capability can't be given in the bot. Admin # users can only give out capabilities they have themselves (which # will depend on supybot.capabilities and its child default) but # generally means they can't mess with channel capabilities. if ircutils.strEqual(capability, 'owner'): irc.error(_('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.')) return if ircdb.isAntiCapability(capability) or \ ircdb.checkCapability(msg.prefix, capability): user.addCapability(capability) ircdb.users.setUser(user) irc.replySuccess() else: irc.error(_('You can\'t add capabilities you don\'t have.')) add = wrap(add, ['otherUser', 'lowered']) @internationalizeDocstring def remove(self, irc, msg, args, user, capability): """ Takes from the user specified by (or the user to whom currently maps) the specified capability """ if ircdb.checkCapability(msg.prefix, capability) or \ ircdb.isAntiCapability(capability): try: user.removeCapability(capability) ircdb.users.setUser(user) irc.replySuccess() except KeyError: irc.error(_('That user doesn\'t have that capability.')) else: s = _('You can\'t remove capabilities you don\'t have.') irc.error(s) remove = wrap(remove, ['otherUser','lowered']) class ignore(callbacks.Commands): @internationalizeDocstring def add(self, irc, msg, args, hostmask, expires): """ [] This will set a persistent ignore on or the hostmask currently associated with . is an optional argument specifying when (in "seconds from now") the ignore will expire; if it isn't given, the ignore will never automatically expire. """ ircdb.ignores.add(hostmask, expires) irc.replySuccess() add = wrap(add, ['hostmask', additional('expiry', 0)]) @internationalizeDocstring def remove(self, irc, msg, args, hostmask): """ This will remove the persistent ignore on or the hostmask currently associated with . """ try: ircdb.ignores.remove(hostmask) irc.replySuccess() except KeyError: irc.error(_('%s wasn\'t in the ignores database.') % hostmask) remove = wrap(remove, ['hostmask']) @internationalizeDocstring def list(self, irc, msg, args): """takes no arguments Lists the hostmasks that the bot is ignoring. """ # XXX Add the expirations. if ircdb.ignores.hostmasks: irc.reply(format('%L', (list(map(repr,ircdb.ignores.hostmasks))))) else: irc.reply(_('I\'m not currently globally ignoring anyone.')) list = wrap(list) def clearq(self, irc, msg, args): """takes no arguments Clears the current send queue for this network. """ irc.queue.reset() irc.replySuccess() clearq = wrap(clearq) def acmd(self, irc, msg, args, commandAndArgs): """ [ ...] Perform (with associated s on all channels on current network.""" for channel in irc.state.channels: msg.args[0] = channel self.Proxy(irc, msg, commandAndArgs) acmd = wrap(acmd, ['admin', many('something')]) Class = Admin # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Admin/test.py0000644000175000017500000001302113634634532017356 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * class AdminTestCase(PluginTestCase): plugins = ('Admin',) def testChannels(self): def getAfterJoinMessages(): m = self.irc.takeMsg() self.assertEqual(m.command, 'MODE') m = self.irc.takeMsg() self.assertEqual(m.command, 'MODE') m = self.irc.takeMsg() self.assertEqual(m.command, 'WHO') self.assertRegexp('channels', 'not.*in any') self.irc.feedMsg(ircmsgs.join('#foo', prefix=self.prefix)) getAfterJoinMessages() self.assertRegexp('channels', '#foo') self.irc.feedMsg(ircmsgs.join('#bar', prefix=self.prefix)) getAfterJoinMessages() self.assertRegexp('channels', '#bar and #foo') self.irc.feedMsg(ircmsgs.join('#Baz', prefix=self.prefix)) getAfterJoinMessages() self.assertRegexp('channels', '#bar, #Baz, and #foo') def testIgnoreAddRemove(self): self.assertNotError('admin ignore add foo!bar@baz') self.assertError('admin ignore add alsdkfjlasd') self.assertNotError('admin ignore remove foo!bar@baz') self.assertError('admin ignore remove foo!bar@baz') def testIgnoreList(self): self.assertNotError('admin ignore list') self.assertNotError('admin ignore add foo!bar@baz') self.assertNotError('admin ignore list') self.assertNotError('admin ignore add foo!bar@baz') self.assertRegexp('admin ignore list', 'foo') def testCapabilityAdd(self): self.assertError('capability add foo bar') u = ircdb.users.newUser() u.name = 'foo' ircdb.users.setUser(u) self.assertNotError('capability add foo bar') self.assertError('addcapability foo baz') self.assert_('bar' in u.capabilities) ircdb.users.delUser(u.id) def testCapabilityRemove(self): self.assertError('capability remove foo bar') u = ircdb.users.newUser() u.name = 'foo' ircdb.users.setUser(u) self.assertNotError('capability add foo bar') self.assert_('bar' in u.capabilities) self.assertError('removecapability foo bar') self.assertNotError('capability remove foo bar') self.assert_(not 'bar' in u.capabilities) ircdb.users.delUser(u.id) def testJoin(self): m = self.getMsg('join #foo') self.assertEqual(m.command, 'JOIN') self.assertEqual(m.args[0], '#foo') m = self.getMsg('join #foo key') self.assertEqual(m.command, 'JOIN') self.assertEqual(m.args[0], '#foo') self.assertEqual(m.args[1], 'key') def testNick(self): original = conf.supybot.nick() try: m = self.getMsg('nick foobar') self.assertEqual(m.command, 'NICK') self.assertEqual(m.args[0], 'foobar') finally: conf.supybot.networks.test.nick.setValue('') def testAddCapabilityOwner(self): self.assertError('admin capability add %s owner' % self.nick) def testJoinOnOwnerInvite(self): self.irc.feedMsg(ircmsgs.invite(conf.supybot.nick(), '#foo', prefix=self.prefix)) m = self.getMsg(' ') self.assertEqual(m.command, 'JOIN') self.assertEqual(m.args[0], '#foo') def testNoJoinOnUnprivilegedInvite(self): try: world.testing = False self.irc.feedMsg(ircmsgs.invite(conf.supybot.nick(), '#foo', prefix='foo!bar@baz')) self.assertResponse('somecommand', 'Error: "somecommand" is not a valid command.') finally: world.testing = True def testNoJoinOnUnprivilegedInvite(self): try: world.testing = False self.irc.feedMsg(ircmsgs.invite(conf.supybot.nick(), '#foo\u0009', prefix='foo!bar@baz')) self.assertResponse('somecommand', 'Error: "somecommand" is not a valid command.') finally: world.testing = True # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Aka/0000755000175000017500000000000013634634547015502 5ustar valval00000000000000limnoria-2020.03.17/plugins/Aka/__init__.py0000644000175000017500000000533713634634532017615 0ustar valval00000000000000### # Copyright (c) 2013, Valentin Lorentz # 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. ### """ This plugin allows the user to create various aliases (known as "Akas", since Alias is the name of another plugin Aka is based on) to other commands or combinations of other commands (via nested commands). """ 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__ = "" __author__ = supybot.authors.progval __maintainer__ = supybot.authors.limnoria_core # 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/Aka/download' from . import config from . import plugin from importlib import reload # In case we're being reloaded. reload(config) reload(plugin) # 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Aka/config.py0000644000175000017500000000563613634634532017325 0ustar valval00000000000000### # Copyright (c) 2013, Valentin Lorentz # 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 try: from supybot.i18n import PluginInternationalization _ = PluginInternationalization('Aka') except: # Placeholder that allows to run the plugin on a bot # without the i18n module _ = 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 themself 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('Aka', True) Aka = conf.registerPlugin('Aka') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Aka, 'someConfigVariableName', # registry.Boolean(False, _("""Help for someConfigVariableName."""))) conf.registerGlobalValue(Aka, 'maximumWordsInName', registry.Integer(5, _("""The maximum number of words allowed in a command name. Setting this to an high value may slow down your bot on long commands."""))) conf.registerGroup(Aka, 'web') conf.registerGlobalValue(Aka.web, 'enable', registry.Boolean(False, _("""Determines whether the Akas will be browsable through the HTTP server."""))) # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Aka/locales/0000755000175000017500000000000013634634547017124 5ustar valval00000000000000limnoria-2020.03.17/plugins/Aka/locales/fi.po0000644000175000017500000001700413634634532020056 0ustar valval00000000000000# Aka plugin for Limnoria # Copyright (C) 2014 Limnoria # Mikaela Suomalainen , 2014. # msgid "" msgstr "" "Project-Id-Version: Aka plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 13:30+EET\n" "PO-Revision-Date: 2014-12-20 13:57+0200\n" "Last-Translator: Mikaela Suomalainen \n" "Language-Team: \n" "Language: fi\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-Generator: Poedit 1.6.10\n" #: config.py:55 msgid "" "The maximum number of words allowed in a\n" " command name. Setting this to an high value may slow down your bot\n" " on long commands." msgstr "" "Komennon nimessä sallittujen merkkien enimmäismäärä.\n" " Korkean arvon asettaminen tähän voi hidastaa bottiasi pitkien komentojen " "kanssa." #: plugin.py:140 plugin.py:264 plugin.py:507 msgid "This Aka already exists." msgstr "Tämä Aka on jo olemassa." #: plugin.py:169 plugin.py:181 plugin.py:195 plugin.py:291 plugin.py:308 #: plugin.py:325 msgid "This Aka does not exist." msgstr "Tätä Akaa ei ole olemassa." #: plugin.py:293 msgid "This Aka is already locked." msgstr "Tämä Aka on jo lukittu." #: plugin.py:310 msgid "This Aka is already unlocked." msgstr "Tämä Aka on jo avattu." #: plugin.py:372 msgid "" "Aka is the improved version of the Alias plugin. It stores akas outside\n" " of the bot.conf, which doesn't have risk of corrupting the bot.conf " "file\n" " (this often happens when there are Unicode issues). Aka also\n" " introduces multi-worded akas." msgstr "" "Aka on paranneltu versio Alias pluginista. Se tallentaa akat bot.conf-" "tiedoston ulkopuolelle, jollla ei ole\n" " riskiä korruptoida bot.conf tiedostoa (joka tapahtuu usein Unicode-" "ongelmien kanssa). Aka\n" " tukee myös useamman akan pituisia akoja." #: plugin.py:481 msgid "You've attempted more nesting than is currently allowed on this bot." msgstr "" "Olet yrittänyt sisällyttää enemmän komentoja, kuin tässä botti sallii juuri " "nyt." #: plugin.py:485 msgid " at least" msgstr "ainakin" #: plugin.py:494 msgid "Locked by %s at %s" msgstr "Lukinnut %s aikaan %s" #: plugin.py:497 msgid "" "\n" "\n" "Alias for %q.%s" msgstr "" "\n" "\n" "Alias komennolle %q.%s" #: plugin.py:498 msgid "argument" msgstr "parametri" #: plugin.py:504 msgid "You can't overwrite commands in this plugin." msgstr "Et voi ylikirjoittaa tämän lisä-osan komentoja." #: plugin.py:509 msgid "This Aka has too many spaces in its name." msgstr "Tämän Akan nimessä on liian monta välilyöntiä." #: plugin.py:514 msgid "Can't mix $* and optional args (@1, etc.)" msgstr "" "$*:ä ja vapaaehtoisia parametrejä (@1, jne.) ei voida sekoittaa keskenään" #: plugin.py:516 msgid "There can be only one $* in an alias." msgstr "Aliaksessa voi olla vain yksi $*." #: plugin.py:523 msgid "This Aka is locked." msgstr "Tämä Aka on lukittu." #: plugin.py:527 #, fuzzy msgid "" "[--channel <#channel>] \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" " arguments that have not replaced $1, $2, etc.\", ie. it will also\n" " include optional arguments.\n" " " msgstr "" "[--channel <#kanava>] \n" "\n" "Määrittää aliaksen , joka suorittaa . \n" " pitäisi olla tavallisessa muodossa \"komento parametri [sisällytettykomento " "parametri]\"\n" " parametreinä aliakselle; ne täytetään ensimmäisenä, toisena, jne.\n" " parametreinä. $1, $2, jne. voidaan käyttää vaadittuina parametreinä. @1, " "@2,\n" " jne. voidaan käyttää vapaaehtoisina parametreinä. $* tarkoittaa " "yksinkertaisesti \"kaikki\n" " jotka eivät ole korvanneet $1, $2, jne.\", esim. se sisältää vapaa-ehtoiset " "parametrit.\n" " " #: plugin.py:541 plugin.py:573 plugin.py:604 plugin.py:636 plugin.py:659 #: plugin.py:682 msgid "%r is not a valid channel." msgstr "%r ei ole kelvollinen kanava." #: plugin.py:559 #, fuzzy msgid "" "[--channel <#channel>] \n" "\n" " Overwrites an existing alias to execute instead. " "The\n" " should be in the standard \"command argument " "[nestedcommand\n" " argument]\" arguments to the alias; they'll be filled with the " "first,\n" " second, etc. arguments. $1, $2, etc. can be used for required\n" " arguments. @1, @2, etc. can be used for optional arguments. $* " "simply\n" " means \"all arguments that have not replaced $1, $2, etc.\", ie. it " "will\n" " also include optional arguments.\n" " " msgstr "" "[--kanava <#kanava>] \n" " Ylikirjoittaa olemassa olevan aliaksen suorittamaan " "sensijaan. \n" " pitäisi olla standardissa \"komento parametri [sisäkkäinen komento\" " "parametreinä aliakselle; ne täytetään\n" " ensimmäisillä, toisilla jne. parametreillä. $1, $2, jne. voidaan käyttää " "vaadittuihin parametreihin. $*\n" " yksinkertaisesti tarkoittaa \"kaikki parametrin, joita ei ole korvattu $1, " "$2 jne.\", esimerkiksi. se sisällyttää\n" " myös kaikki vapaaehtoiset parametrit." #: plugin.py:596 msgid "" "[--channel <#channel>] \n" "\n" " Removes the given alias, if unlocked.\n" " " msgstr "" "[--channel <#kanava>] \n" "\n" " Poistaa annetun aliaksen, ellei se ole lukittu.\n" " " #: plugin.py:618 msgid "" "Check if the user has any of the required capabilities to manage\n" " the regexp database." msgstr "" "Tarkistaa onko käyttäjällä vaadittu valtuus säännöllisten lausekkeiden\n" " tietokannan hallintaan." #: plugin.py:628 msgid "" "[--channel <#channel>] \n" "\n" " Locks an alias so that no one else can change it.\n" " " msgstr "" "[--channel <#kanava>] \n" "\n" " Lukitsee aliaksen estäen muita muokkaamasta sitä.\n" " " #: plugin.py:651 msgid "" "[--channel <#channel>] \n" "\n" " Unlocks an alias so that people can define new aliases over it.\n" " " msgstr "" "[--channel <#kanava>] \n" "\n" " Avaa aliaksen, jotta kaikki voivat määrittää uusia aliaksia sen päälle.\n" " " #: plugin.py:674 msgid "" "[--channel <#channel>] \n" "\n" " This command shows the content of an Aka.\n" " " msgstr "" " <#kanava> \n" "\n" " Tämä komento näyttää Akan sisällön." #: plugin.py:689 msgid "This Aka does not exist." msgstr "Tätä Akaa ei ole olemassakaan." #: plugin.py:694 msgid "" "takes no arguments\n" "\n" " Imports the Alias database into Aka's, and clean the former." msgstr "" "ei ota parametrejä\n" "\n" " Tuo Aliaksen tietokannan Akaan ja tyhjentää aiemman." #: plugin.py:699 msgid "Alias plugin is not loaded." msgstr "Alias lisä-osa ei ole ladattu." #: plugin.py:709 msgid "Error occured when importing the %n: %L" msgstr "Virhe komennon %n tuomisessa: %L" #~ msgid "" #~ "Add the help for 'plugin help Aka' here\n" #~ " This should describe *how* to use this plugin." #~ msgstr "" #~ "Lisää ohje komentoa 'plugin help Aka' varten tähän.\n" #~ " Tämän pitäisi kuvata *kuinka* tätä lisä-osaa käytetään." limnoria-2020.03.17/plugins/Aka/plugin.py0000644000175000017500000011216013634634532017345 0ustar valval00000000000000### # Copyright (c) 2013, Valentin Lorentz # 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 re import os import sys import datetime import supybot.conf as conf import supybot.utils as utils import supybot.ircdb as ircdb from supybot.commands import * import supybot.plugins as plugins import supybot.utils.minisix as minisix import supybot.ircutils as ircutils import supybot.callbacks as callbacks import supybot.httpserver as httpserver from supybot.i18n import PluginInternationalization _ = PluginInternationalization('Aka') try: import sqlite3 except ImportError: sqlite3 = None try: import sqlalchemy import sqlalchemy.ext import sqlalchemy.ext.declarative except ImportError: sqlalchemy = None if not (sqlite3 or sqlalchemy): raise callbacks.Error('You have to install python-sqlite3 or ' 'python-sqlalchemy in order to load this plugin.') available_db = {} class Alias(object): __slots__ = ('name', 'alias', 'locked', 'locked_by', 'locked_at') def __init__(self, name, alias): self.name = name self.alias = alias self.locked = False self.locked_by = None self.locked_at = None def __repr__(self): return "" % (self.name, self.alias) if sqlite3: class SQLiteAkaDB(object): __slots__ = ('engines', 'filename', 'dbs',) def __init__(self, filename): self.engines = ircutils.IrcDict() self.filename = filename.replace('sqlite3', 'sqlalchemy') def close(self): self.dbs.clear() def get_db(self, channel): if channel in self.engines: engine = self.engines[channel] else: filename = plugins.makeChannelFilename(self.filename, channel) exists = os.path.exists(filename) engine = sqlite3.connect(filename, check_same_thread=False) if not exists: cursor = engine.cursor() cursor.execute("""CREATE TABLE aliases ( id INTEGER NOT NULL, name VARCHAR NOT NULL, alias VARCHAR NOT NULL, locked BOOLEAN NOT NULL, locked_by VARCHAR, locked_at DATETIME, PRIMARY KEY (id), UNIQUE (name))""") engine.commit() self.engines[channel] = engine assert engine.execute("select 1").fetchone() == (1,) return engine def has_aka(self, channel, name): name = callbacks.canonicalName(name, preserve_spaces=True) if minisix.PY2 and isinstance(name, str): name = name.decode('utf8') db = self.get_db(channel) return self.get_db(channel).cursor() \ .execute("""SELECT COUNT() as count FROM aliases WHERE name = ?;""", (name,)) \ .fetchone()[0] def get_aka_list(self, channel): cursor = self.get_db(channel).cursor() cursor.execute("""SELECT name FROM aliases;""") list_ = cursor.fetchall() return list_ def get_alias(self, channel, name): name = callbacks.canonicalName(name, preserve_spaces=True) if minisix.PY2 and isinstance(name, str): name = name.decode('utf8') cursor = self.get_db(channel).cursor() cursor.execute("""SELECT alias FROM aliases WHERE name = ?;""", (name,)) r = cursor.fetchone() if r: return r[0] else: return None def add_aka(self, channel, name, alias): name = callbacks.canonicalName(name, preserve_spaces=True) if self.has_aka(channel, name): raise AkaError(_('This Aka already exists.')) if minisix.PY2: if isinstance(name, str): name = name.decode('utf8') if isinstance(alias, str): alias = alias.decode('utf8') db = self.get_db(channel) cursor = db.cursor() cursor.execute("""INSERT INTO aliases VALUES ( NULL, ?, ?, 0, NULL, NULL);""", (name, alias)) db.commit() def remove_aka(self, channel, name): name = callbacks.canonicalName(name, preserve_spaces=True) if minisix.PY2 and isinstance(name, str): name = name.decode('utf8') db = self.get_db(channel) db.cursor().execute('DELETE FROM aliases WHERE name = ?', (name,)) db.commit() def lock_aka(self, channel, name, by): name = callbacks.canonicalName(name, preserve_spaces=True) if minisix.PY2 and isinstance(name, str): name = name.decode('utf8') db = self.get_db(channel) cursor = db.cursor().execute("""UPDATE aliases SET locked=1, locked_at=?, locked_by=? WHERE name = ?""", (datetime.datetime.now(), by, name)) if cursor.rowcount == 0: raise AkaError(_('This Aka does not exist.')) db.commit() def unlock_aka(self, channel, name, by): name = callbacks.canonicalName(name, preserve_spaces=True) if minisix.PY2 and isinstance(name, str): name = name.decode('utf8') db = self.get_db(channel) cursor = db.cursor() cursor.execute("""UPDATE aliases SET locked=0, locked_at=? WHERE name = ?""", (datetime.datetime.now(), name)) if cursor.rowcount == 0: raise AkaError(_('This Aka does not exist.')) db.commit() def get_aka_lock(self, channel, name): name = callbacks.canonicalName(name, preserve_spaces=True) if minisix.PY2 and isinstance(name, str): name = name.decode('utf8') cursor = self.get_db(channel).cursor() cursor.execute("""SELECT locked, locked_by, locked_at FROM aliases WHERE name = ?;""", (name,)) r = cursor.fetchone() if r: return (bool(r[0]), r[1], r[2]) else: raise AkaError(_('This Aka does not exist.')) def get_all(self, channel): cursor = self.get_db(channel).cursor() cursor.execute(""" SELECT id, name, alias, locked, locked_by, locked_at FROM aliases; """) return cursor.fetchall() available_db.update({'sqlite3': SQLiteAkaDB}) elif sqlalchemy: Base = sqlalchemy.ext.declarative.declarative_base() class SQLAlchemyAlias(Alias, Base): __slots__ = () __tablename__ = 'aliases' id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True) name = sqlalchemy.Column(sqlalchemy.String, unique=True, nullable=False) alias = sqlalchemy.Column(sqlalchemy.String, nullable=False) locked = sqlalchemy.Column(sqlalchemy.Boolean, nullable=False) locked_by = sqlalchemy.Column(sqlalchemy.String, nullable=True) locked_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=True) # TODO: Add table for usage statistics class SqlAlchemyAkaDB(object): __slots__ = ('engines', 'filename', 'sqlalchemy', 'dbs') def __init__(self, filename): self.engines = ircutils.IrcDict() self.filename = filename self.sqlalchemy = sqlalchemy def close(self): self.dbs.clear() def get_db(self, channel): if channel in self.engines: engine = self.engines[channel] else: filename = plugins.makeChannelFilename(self.filename, channel) exists = os.path.exists(filename) engine = sqlalchemy.create_engine('sqlite:///' + filename) if not exists: Base.metadata.create_all(engine) self.engines[channel] = engine assert engine.execute("select 1").scalar() == 1 Session = sqlalchemy.orm.sessionmaker() Session.configure(bind=engine) return Session() def has_aka(self, channel, name): name = callbacks.canonicalName(name, preserve_spaces=True) if minisix.PY2 and isinstance(name, str): name = name.decode('utf8') count = self.get_db(channel).query(SQLAlchemyAlias) \ .filter(SQLAlchemyAlias.name == name) \ .count() return bool(count) def get_aka_list(self, channel): list_ = list(self.get_db(channel).query(SQLAlchemyAlias.name)) return list_ def get_alias(self, channel, name): name = callbacks.canonicalName(name, preserve_spaces=True) if minisix.PY2 and isinstance(name, str): name = name.decode('utf8') try: return self.get_db(channel).query(SQLAlchemyAlias.alias) \ .filter(SQLAlchemyAlias.name == name).one()[0] except sqlalchemy.orm.exc.NoResultFound: return None def add_aka(self, channel, name, alias): name = callbacks.canonicalName(name, preserve_spaces=True) if self.has_aka(channel, name): raise AkaError(_('This Aka already exists.')) if minisix.PY2: if isinstance(name, str): name = name.decode('utf8') if isinstance(alias, str): alias = alias.decode('utf8') db = self.get_db(channel) db.add(SQLAlchemyAlias(name, alias)) db.commit() def remove_aka(self, channel, name): name = callbacks.canonicalName(name, preserve_spaces=True) if minisix.PY2 and isinstance(name, str): name = name.decode('utf8') db = self.get_db(channel) db.query(SQLAlchemyAlias).filter(SQLAlchemyAlias.name == name).delete() db.commit() def lock_aka(self, channel, name, by): name = callbacks.canonicalName(name, preserve_spaces=True) if minisix.PY2 and isinstance(name, str): name = name.decode('utf8') db = self.get_db(channel) try: aka = db.query(SQLAlchemyAlias) \ .filter(SQLAlchemyAlias.name == name).one() except sqlalchemy.orm.exc.NoResultFound: raise AkaError(_('This Aka does not exist.')) if aka.locked: raise AkaError(_('This Aka is already locked.')) aka.locked = True aka.locked_by = by aka.locked_at = datetime.datetime.now() db.commit() def unlock_aka(self, channel, name, by): name = callbacks.canonicalName(name, preserve_spaces=True) if minisix.PY2 and isinstance(name, str): name = name.decode('utf8') db = self.get_db(channel) try: aka = db.query(SQLAlchemyAlias) \ .filter(SQLAlchemyAlias.name == name).one() except sqlalchemy.orm.exc.NoResultFound: raise AkaError(_('This Aka does not exist.')) if not aka.locked: raise AkaError(_('This Aka is already unlocked.')) aka.locked = False aka.locked_by = by aka.locked_at = datetime.datetime.now() db.commit() def get_aka_lock(self, channel, name): name = callbacks.canonicalName(name, preserve_spaces=True) if minisix.PY2 and isinstance(name, str): name = name.decode('utf8') try: return self.get_db(channel) \ .query(SQLAlchemyAlias.locked, SQLAlchemyAlias.locked_by, SQLAlchemyAlias.locked_at)\ .filter(SQLAlchemyAlias.name == name).one() except sqlalchemy.orm.exc.NoResultFound: raise AkaError(_('This Aka does not exist.')) def get_all(self, channel): akas = self.get_db(channel).query( SQLAlchemyAlias.id, SQLAlchemyAlias.name, SQLAlchemyAlias.alias, SQLAlchemyAlias.locked, SQLAlchemyAlias.locked_by, SQLAlchemyAlias.locked_at ).all() return map( lambda aka: (aka.name, aka.alias, aka.locked, aka.locked_by, aka.locked_at), akas ) available_db.update({'sqlalchemy': SqlAlchemyAkaDB}) def getArgs(args, required=1, optional=0, wildcard=0): if len(args) < required: raise callbacks.ArgumentError if len(args) < required + optional: ret = list(args) + ([''] * (required + optional - len(args))) elif len(args) >= required + optional: if not wildcard: ret = list(args[:required + optional - 1]) ret.append(' '.join(args[required + optional - 1:])) else: ret = list(args) return ret class AkaError(Exception): pass class RecursiveAlias(AkaError): pass dollarRe = re.compile(r'\$(\d+)') def findBiggestDollar(alias): dollars = dollarRe.findall(alias) dollars = list(map(int, dollars)) dollars.sort() if dollars: return dollars[-1] else: return 0 atRe = re.compile(r'@(\d+)') def findBiggestAt(alias): ats = atRe.findall(alias) ats = list(map(int, ats)) ats.sort() if ats: return ats[-1] else: return 0 if 'sqlite3' in conf.supybot.databases() and 'sqlite3' in available_db: AkaDB = SQLiteAkaDB elif 'sqlalchemy' in conf.supybot.databases() and 'sqlalchemy' in available_db: log.warning('Aka\'s only enabled database engine is SQLAlchemy, which ' 'is deprecated. Please consider adding \'sqlite3\' to ' 'supybot.databases (and/or install sqlite3).') AkaDB = SqlAlchemyAkaDB else: raise plugins.NoSuitableDatabase(['sqlite3', 'sqlalchemy']) class AkaHTTPCallback(httpserver.SupyHTTPServerCallback): name = 'Aka web interface' base_template = '''\ Aka

Aka

%s ''' index_template = base_template % '''\

To view the global Akas either click here or enter 'global' in the form below.

''' list_template = base_template % '''\ %s
Name Alias Locked
''' def doGet(self, handler, path, *args, **kwargs): if path == '/': self.send_response(200) self.send_header('Content-type', 'text/html; charset=utf-8') self.end_headers() self.write(self.index_template) elif path.startswith('/list/'): parts = path.split('/') channel = parts[2] if len(parts) == 3 else 'global' channel = utils.web.urlunquote(channel) self.send_response(200) self.send_header('Content-type', 'text/html; charset=utf-8') self.end_headers() akas = {} for aka in self._plugin._db.get_all('global'): akas[aka[1]] = aka if channel != 'global': for aka in self._plugin._db.get_all(channel): akas[aka[1]] = aka aka_rows = [] for (name, aka) in sorted(akas.items()): (id, name, alias, locked, locked_by, locked_at) = aka locked_column = 'False' if locked: locked_column = format( _('By %s at %s'), locked_by, locked_at.split('.')[0], ) aka_rows.append( format( """ %s %s %s """, utils.web.html_escape(name), utils.web.html_escape(alias), utils.web.html_escape(locked_column), ) ) self.write(format(self.list_template, ''.join(aka_rows))) def doPost(self, handler, path, form, *args, **kwargs): if path == '/' and 'channel' in form: self.send_response(303) self.send_header( 'Location', format('list/%s', utils.web.urlquote(form['channel'].value)) ) self.end_headers() else: self.send_response(400) self.send_header('Content-type', 'text/plain; charset=utf-8') self.end_headers() self.write('Missing field \'channel\'.') class Aka(callbacks.Plugin): """Aka is the improved version of the Alias plugin. It stores akas outside of the bot.conf, which doesn't have risk of corrupting the bot.conf file (this often happens when there are Unicode issues). Aka also introduces multi-worded akas.""" def __init__(self, irc): self.__parent = super(Aka, self) self.__parent.__init__(irc) # "sqlalchemy" is only for backward compatibility filename = conf.supybot.directories.data.dirize('Aka.sqlalchemy.db') self._db = AkaDB(filename) self._http_running = False conf.supybot.plugins.Aka.web.enable.addCallback(self._httpConfCallback) if self.registryValue('web.enable'): self._startHttp() def die(self): if self._http_running: self._stopHttp() def _httpConfCallback(self): if self.registryValue('web.enable'): if not self._http_running: self._startHttp() else: if self._http_running: self._stopHttp() def _startHttp(self): callback = AkaHTTPCallback() callback._plugin = self httpserver.hook('aka', callback) self._http_running = True def _stopHttp(self): httpserver.unhook('aka') self._http_running = False def isCommandMethod(self, name): args = name.split(' ') if '|' in args: return False if len(args) > 1 and \ callbacks.canonicalName(args[0]) != self.canonicalName(): for cb in dynamic.irc.callbacks: # including this plugin if cb.isCommandMethod(' '.join(args[0:-1])): return False if minisix.PY2 and isinstance(name, str): name = name.decode('utf8') channel = dynamic.channel or 'global' return self._db.has_aka(channel, name) or \ self._db.has_aka('global', name) or \ self.__parent.isCommandMethod(name) isCommand = isCommandMethod def listCommands(self): commands = ['add', 'remove', 'lock', 'unlock', 'importaliasdatabase', 'show', 'list', 'set', 'search'] return commands def getCommand(self, args, check_other_plugins=True): canonicalName = callbacks.canonicalName # All the code from here to the 'for' loop is copied from callbacks.py assert args == list(map(canonicalName, args)) first = args[0] for cb in self.cbs: if first == cb.canonicalName(): return cb.getCommand(args[1:]) if first == self.canonicalName() and len(args) > 1: ret = self.getCommand(args[1:], False) if ret: return [first] + ret max_length = self.registryValue('maximumWordsInName') for i in range(1, min(len(args)+1, max_length)): if self.isCommandMethod(callbacks.formatCommand(args[0:i])): return args[0:i] return [] def getCommandMethod(self, command): if len(command) == 1 or command[0] == self.canonicalName(): try: return self.__parent.getCommandMethod(command) except AttributeError: pass name = callbacks.formatCommand(command) channel = dynamic.channel or 'global' original = self._db.get_alias(channel, name) if not original: original = self._db.get_alias('global', name) biggestDollar = findBiggestDollar(original) biggestAt = findBiggestAt(original) wildcard = '$*' in original def f(irc, msg, args): tokens = callbacks.tokenize(original) if biggestDollar or biggestAt: args = getArgs(args, required=biggestDollar, optional=biggestAt, wildcard=wildcard) remaining_len = conf.supybot.reply.maximumLength() for (i, arg) in enumerate(args): if remaining_len < len(arg): arg = arg[0:remaining_len] args[i+1:] = [] break remaining_len -= len(arg) def regexpReplace(m): idx = int(m.group(1)) return args[idx-1] def replace(tokens, replacer): for (i, token) in enumerate(tokens): if isinstance(token, list): replace(token, replacer) else: tokens[i] = replacer(token) replace(tokens, lambda s: dollarRe.sub(regexpReplace, s)) if biggestAt: assert not wildcard args = args[biggestDollar:] replace(tokens, lambda s: atRe.sub(regexpReplace, s)) if wildcard: assert not biggestAt # Gotta remove the things that have already been subbed in. i = biggestDollar args[:] = args[i:] def everythingReplace(tokens): skip = 0 for (i, token) in enumerate(tokens): if skip: skip -= 1 continue if isinstance(token, list): everythingReplace(token) if token == '$*': tokens[i:i+1] = args skip = len(args)-1 # do not make replacements in # tokens we just added elif '$*' in token: tokens[i] = token.replace('$*', ' '.join(args)) everythingReplace(tokens) maxNesting = conf.supybot.commands.nested.maximum() if maxNesting and irc.nested+1 > maxNesting: irc.error(_('You\'ve attempted more nesting than is ' 'currently allowed on this bot.'), Raise=True) self.Proxy(irc, msg, tokens, nested=irc.nested+1) if biggestDollar and (wildcard or biggestAt): flexargs = _(' at least') else: flexargs = '' try: lock = self._db.get_aka_lock(channel, name) except AkaError: lock = self._db.get_aka_lock('global', name) (locked, locked_by, locked_at) = lock if locked: lock = ' ' + _('Locked by %s at %s') % (locked_by, locked_at) else: lock = '' escaped_command = original.replace('\\', '\\\\').replace('"', '\\"') if channel == 'global': doc = format(_('\n\nAlias for %q.%s'), flexargs, (biggestDollar, _('argument')), escaped_command, lock) else: doc = format(_('\n\nAlias for %q.%s'), channel, flexargs, (biggestDollar, _('argument')), escaped_command, lock) f = utils.python.changeFunctionName(f, name, doc) return f def _add_aka(self, channel, name, alias): if self.__parent.isCommandMethod(name): raise AkaError(_('You can\'t overwrite commands in ' 'this plugin.')) if self._db.has_aka(channel, name): raise AkaError(_('This Aka already exists.')) if len(name.split(' ')) > self.registryValue('maximumWordsInName'): raise AkaError(_('This Aka has too many spaces in its name.')) biggestDollar = findBiggestDollar(alias) biggestAt = findBiggestAt(alias) wildcard = '$*' in alias if biggestAt and wildcard: raise AkaError(_('Can\'t mix $* and optional args (@1, etc.)')) self._db.add_aka(channel, name, alias) def _remove_aka(self, channel, name, evenIfLocked=False): if not evenIfLocked: (locked, by, at) = self._db.get_aka_lock(channel, name) if locked: raise AkaError(_('This Aka is locked.')) self._db.remove_aka(channel, name) def add(self, irc, msg, args, optlist, name, alias): """[--channel <#channel>] Defines an alias that executes . The should be in the standard "command argument [nestedcommand argument]" arguments to the alias; they'll be filled with the first, second, etc. arguments. $1, $2, etc. can be used for required arguments. @1, @2, etc. can be used for optional arguments. $* simply means "all arguments that have not replaced $1, $2, etc.", ie. it will also include optional arguments. """ channel = 'global' for (option, arg) in optlist: if option == 'channel': if not irc.isChannel(arg): irc.error(_('%r is not a valid channel.') % arg, Raise=True) channel = arg if ' ' not in alias: # If it's a single word, they probably want $*. alias += ' $*' try: self._add_aka(channel, name, alias) self.log.info('Adding Aka %r for %r (from %s)', name, alias, msg.prefix) irc.replySuccess() except AkaError as e: irc.error(str(e)) add = wrap(add, [getopts({ 'channel': 'somethingWithoutSpaces', }), 'something', 'text']) def set(self, irc, msg, args, optlist, name, alias): """[--channel <#channel>] Overwrites an existing alias to execute instead. The should be in the standard "command argument [nestedcommand argument]" arguments to the alias; they'll be filled with the first, second, etc. arguments. $1, $2, etc. can be used for required arguments. @1, @2, etc. can be used for optional arguments. $* simply means "all arguments that have not replaced $1, $2, etc.", ie. it will also include optional arguments. """ channel = 'global' for (option, arg) in optlist: if option == 'channel': if not irc.isChannel(arg): irc.error(_('%r is not a valid channel.') % arg, Raise=True) channel = arg try: self._remove_aka(channel, name) except AkaError as e: irc.error(str(e), Raise=True) if ' ' not in alias: # If it's a single word, they probably want $*. alias += ' $*' try: self._add_aka(channel, name, alias) self.log.info('Setting Aka %r to %r (from %s)', name, alias, msg.prefix) irc.replySuccess() except AkaError as e: irc.error(str(e)) set = wrap(set, [getopts({ 'channel': 'somethingWithoutSpaces', }), 'something', 'text']) def remove(self, irc, msg, args, optlist, name): """[--channel <#channel>] Removes the given alias, if unlocked. """ channel = 'global' for (option, arg) in optlist: if option == 'channel': if not irc.isChannel(arg): irc.error(_('%r is not a valid channel.') % arg, Raise=True) channel = arg try: self._remove_aka(channel, name) self.log.info('Removing Aka %r (from %s)', name, msg.prefix) irc.replySuccess() except AkaError as e: irc.error(str(e)) remove = wrap(remove, [getopts({ 'channel': 'somethingWithoutSpaces', }), 'something']) def _checkManageCapabilities(self, irc, msg, channel): """Check if the user has any of the required capabilities to manage the regexp database.""" if channel != 'global': capability = ircdb.makeChannelCapability(channel, 'op') else: capability = 'admin' if not ircdb.checkCapability(msg.prefix, capability): irc.errorNoCapability(capability, Raise=True) def lock(self, irc, msg, args, optlist, user, name): """[--channel <#channel>] Locks an alias so that no one else can change it. """ channel = 'global' for (option, arg) in optlist: if option == 'channel': if not irc.isChannel(arg): irc.error(_('%r is not a valid channel.') % arg, Raise=True) channel = arg self._checkManageCapabilities(irc, msg, channel) try: self._db.lock_aka(channel, name, user.name) except AkaError as e: irc.error(str(e)) else: irc.replySuccess() lock = wrap(lock, [getopts({ 'channel': 'somethingWithoutSpaces', }), 'user', 'something']) def unlock(self, irc, msg, args, optlist, user, name): """[--channel <#channel>] Unlocks an alias so that people can define new aliases over it. """ channel = 'global' for (option, arg) in optlist: if option == 'channel': if not irc.isChannel(arg): irc.error(_('%r is not a valid channel.') % arg, Raise=True) channel = arg self._checkManageCapabilities(irc, msg, channel) try: self._db.unlock_aka(channel, name, user.name) except AkaError as e: irc.error(str(e)) else: irc.replySuccess() unlock = wrap(unlock, [getopts({ 'channel': 'somethingWithoutSpaces', }), 'user', 'something']) def show(self, irc, msg, args, optlist, name): """[--channel <#channel>] This command shows the content of an Aka. """ channel = 'global' for (option, arg) in optlist: if option == 'channel': if not irc.isChannel(arg): irc.error(_('%r is not a valid channel.') % arg, Raise=True) channel = arg command = self._db.get_alias(channel, name) if command: irc.reply(command) else: irc.error(_('This Aka does not exist.')) show = wrap(show, [getopts({'channel': 'somethingWithoutSpaces'}), 'text']) def importaliasdatabase(self, irc, msg, args): """takes no arguments Imports the Alias database into Aka's, and clean the former.""" alias_plugin = irc.getCallback('Alias') if alias_plugin is None: irc.error(_('Alias plugin is not loaded.'), Raise=True) errors = {} for (name, (command, locked, func)) in \ list(alias_plugin.aliases.items()): try: self._add_aka('global', name, command) except AkaError as e: errors[name] = e.args[0] else: alias_plugin.removeAlias(name, evenIfLocked=True) if errors: irc.error(format(_('Error occured when importing the %n: %L'), (len(errors), 'following', 'command'), ['%s (%s)' % x for x in errors.items()])) else: irc.replySuccess() importaliasdatabase = wrap(importaliasdatabase, ['owner']) def list(self, irc, msg, args, optlist): """[--channel <#channel>] [--keys] [--unlocked|--locked] Lists all Akas defined for . If is not specified, lists all global Akas. If --keys is given, lists only the Aka names and not their commands.""" channel = 'global' filterlocked = filterunlocked = False for (option, arg) in optlist: if option == 'channel': if not irc.isChannel(arg): irc.error(_('%r is not a valid channel.') % arg, Raise=True) channel = arg if option == 'locked': filterlocked = True if option == 'unlocked': filterunlocked = True aka_list = self._db.get_aka_list(channel) if filterlocked and filterunlocked: irc.error(_('--locked and --unlocked are incompatible options.'), Raise=True) elif filterlocked: aka_list = [aka for aka in aka_list if self._db.get_aka_lock(channel, aka)[0]] elif filterunlocked: aka_list = [aka for aka in aka_list if not self._db.get_aka_lock(channel, aka)[0]] if aka_list: if 'keys' in dict(optlist): # Strange, aka_list is a list of one length tuples s = [k[0] for k in aka_list] oneToOne = True else: aka_values = [self._db.get_alias(channel, aka) for aka in aka_list] s = ('{0}: "{1}"'.format(ircutils.bold(k), v) for (k, v) in zip(aka_list, aka_values)) oneToOne = None irc.replies(s, oneToOne=oneToOne) else: irc.error(_("No Akas found.")) list = wrap(list, [getopts({'channel': 'channel', 'keys': '', 'locked': '', 'unlocked': ''})]) def search(self, irc, msg, args, optlist, query): """[--channel <#channel>] Searches Akas defined for . If is not specified, searches all global Akas.""" query = callbacks.canonicalName(query, preserve_spaces=True) channel = 'global' for (option, arg) in optlist: if option == 'channel': if not irc.isChannel(arg): irc.error(_('%r is not a valid channel.') % arg, Raise=True) channel = arg aka_list = self._db.get_aka_list(channel) aka_list = [callbacks.canonicalName(k[0], preserve_spaces=True) for k in aka_list] matching = [aka for aka in aka_list if query in aka] if matching: irc.replies(matching) else: irc.error(_("No matching Akas were found.")) search = wrap(search, [getopts({'channel': 'channel'}), 'text']) Class = Aka # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Aka/test.py0000644000175000017500000003462013634634532017032 0ustar valval00000000000000# -*- coding: utf8 -*- ### # Copyright (c) 2002-2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * import supybot.conf as conf import supybot.httpserver as httpserver import supybot.plugin as plugin import supybot.registry as registry from supybot.utils.minisix import u from . import plugin as Aka class FunctionsTest(SupyTestCase): def testFindBiggestDollar(self): self.assertEqual(Aka.findBiggestDollar(''), 0) self.assertEqual(Aka.findBiggestDollar('foo'), 0) self.assertEqual(Aka.findBiggestDollar('$0'), 0) self.assertEqual(Aka.findBiggestDollar('$1'), 1) self.assertEqual(Aka.findBiggestDollar('$2'), 2) self.assertEqual(Aka.findBiggestDollar('$2 $10'), 10) self.assertEqual(Aka.findBiggestDollar('$3'), 3) self.assertEqual(Aka.findBiggestDollar('$3 $2 $1'), 3) self.assertEqual(Aka.findBiggestDollar('foo bar $1'), 1) self.assertEqual(Aka.findBiggestDollar('foo $2 $1'), 2) self.assertEqual(Aka.findBiggestDollar('foo $0 $1'), 1) self.assertEqual(Aka.findBiggestDollar('foo $1 $3'), 3) self.assertEqual(Aka.findBiggestDollar('$10 bar $1'), 10) class AkaChannelTestCase(ChannelPluginTestCase): plugins = ('Aka', 'Conditional', 'Filter', 'Math', 'Utilities', 'Format', 'Reply', 'String') def testHistsearch(self): self.assertNotError( r'aka add histsearch "last --from [cif true ' r'\"echo test\" \"echo test\"] ' r'--regexp [concat \"m/$1/\" [re s/g// \"@2\"]]"') self.assertResponse('echo foo', 'foo') self.assertResponse('histsearch .*', '@echo foo') def testDoesNotOverwriteCommands(self): # We don't have dispatcher commands anymore #self.assertError('aka add aka "echo foo bar baz"') self.assertError('aka add add "echo foo bar baz"') self.assertError('aka add remove "echo foo bar baz"') self.assertError('aka add lock "echo foo bar baz"') self.assertError('aka add unlock "echo foo bar baz"') def testAkaHelp(self): self.assertNotError(r'aka add slashdot "foo \"bar\" baz"') self.assertRegexp('help slashdot', r'Alias for "foo \\"bar\\" baz".') self.assertNotError('aka add nonascii echo éé') self.assertRegexp('help nonascii', r'Alias for "echo éé".') self.assertNotError('aka remove slashdot') self.assertNotError('aka add --channel %s slashdot foo' % self.channel) self.assertRegexp('help aka slashdot', "an alias on %s.*Alias for .*foo" % self.channel) self.assertNotError('aka remove --channel %s slashdot' % self.channel) def testShow(self): self.assertNotError('aka add foo bar') self.assertResponse('show foo', 'bar $*') self.assertNotError('aka add "foo bar" baz') self.assertResponse('show "foo bar"', 'baz $*') def testRemove(self): self.assertNotError('aka add foo echo bar') self.assertResponse('foo', 'bar') self.assertNotError('aka remove foo') self.assertError('foo') def testDollars(self): self.assertNotError('aka add rot26 "rot13 [rot13 $1]"') self.assertResponse('rot26 foobar', 'foobar') def testMoreDollars(self): self.assertNotError('aka add rev "echo $3 $2 $1"') self.assertResponse('rev foo bar baz', 'baz bar foo') def testAllArgs(self): self.assertNotError('aka add swap "echo $2 $1 $*"') self.assertResponse('swap 1 2 3 4 5', '2 1 3 4 5') self.assertError('aka add foo "echo $1 @1 $*"') self.assertNotError('aka add moo echo $1 $*') self.assertError('moo') self.assertResponse('moo foo', 'foo') self.assertResponse('moo foo bar', 'foo bar') self.assertNotError('aka add spam "echo [echo $*]"') self.assertResponse('spam egg', 'egg') self.assertResponse('spam egg bacon', 'egg bacon') self.assertNotError('aka add doublespam "echo [echo $* $*]"') self.assertResponse('doublespam egg', 'egg egg') self.assertResponse('doublespam egg bacon', 'egg bacon egg bacon') def testExpansionBomb(self): self.assertNotError('aka add bomb "bomb $* $* $* $* $*"') # if the mitigation doesn't work, this test will eat all memory on the # system. self.assertResponse('bomb foo', "Error: You've attempted more nesting " "than is currently allowed on this bot.") def testChannel(self): self.assertNotError('aka add channel echo $channel') self.assertResponse('aka channel', self.channel) def testAddRemoveAka(self): cb = self.irc.getCallback('Aka') cb._add_aka('global', 'foobar', 'echo sbbone') cb._db.lock_aka('global', 'foobar', 'evil_admin') self.assertResponse('foobar', 'sbbone') self.assertRegexp('aka list', 'foobar') self.assertRaises(Aka.AkaError, cb._remove_aka, 'global', 'foobar') cb._remove_aka('global', 'foobar', evenIfLocked=True) self.assertNotRegexp('aka list', 'foobar') self.assertError('foobar') def testOptionalArgs(self): self.assertNotError('aka add myrepr "repr @1"') self.assertResponse('myrepr foo', '"foo"') self.assertResponse('myrepr ""', '""') def testRequiredAndOptional(self): self.assertNotError('aka add reqopt "echo req=$1, opt=@1"') self.assertResponse('reqopt foo bar', 'req=foo, opt=bar') self.assertResponse('reqopt foo', 'req=foo, opt=') def testNoExtraSpaces(self): self.assertNotError('aka add foo "action takes $1\'s money"') self.assertResponse('foo bar', '\x01ACTION takes bar\'s money\x01') def testNoExtraQuotes(self): self.assertNotError('aka add myre "echo s/$1/$2/g"') self.assertResponse('myre foo bar', 's/foo/bar/g') def testSimpleAkaWithoutArgsImpliesDollarStar(self): self.assertNotError('aka add exo echo') self.assertResponse('exo foo bar baz', 'foo bar baz') def testChannelPriority(self): self.assertNotError('aka add spam "echo foo"') self.assertNotError('aka add --channel %s spam "echo bar"' % self.channel) self.assertResponse('spam', 'bar') self.assertNotError('aka add --channel %s egg "echo baz"' % self.channel) self.assertNotError('aka add egg "echo qux"') self.assertResponse('egg', 'baz') def testComplicatedNames(self): self.assertNotError(u('aka add café "echo coffee"')) self.assertResponse(u('café'), 'coffee') self.assertNotError('aka add "foo bar" "echo spam"') self.assertResponse('foo bar', 'spam') self.assertNotError('aka add "foo" "echo egg"') self.assertResponse('foo', 'egg') # You could expect 'spam' here, but in fact, this is dangerous. # Just imagine this session: # aka add "echo foo" quit # The operation succeeded. # ... # echo foo # * bot has quit self.assertResponse('foo bar', 'egg') def testNoOverride(self): self.assertNotError('aka add "echo foo" "echo bar"') self.assertResponse('echo foo', 'foo') self.assertNotError('aka add foo "echo baz"') self.assertNotError('aka add "foo bar" "echo qux"') self.assertResponse('foo bar', 'baz') def testRecursivity(self): self.assertNotError('aka add fact ' r'"cif [nceq $1 0] \"echo 1\" ' r'\"calc $1 * [fact [calc $1 - 1]]\""') self.assertResponse('fact 4', '24') self.assertRegexp('fact 50', 'more nesting') def testDollarStarNesting(self): self.assertResponse('aka add alias aka $*', 'The operation succeeded.') self.assertResponse('alias add a+ aka add $*', 'The operation succeeded.') self.assertResponse('a+ spam echo egg', 'The operation succeeded.') self.assertResponse('spam', 'egg') def testIgnore(self): self.assertResponse('aka add test ignore', 'The operation succeeded.') self.assertNoResponse('test') class AkaTestCase(PluginTestCase): plugins = ('Aka', 'Alias', 'User', 'Utilities') def testMaximumLength(self): self.assertNotError('aka add "foo bar baz qux quux" "echo test"') self.assertError('aka add "foo bar baz qux quux corge" "echo test"') def testAkaLockedHelp(self): self.assertNotError('register evil_admin foo') self.assertNotError('aka add slashdot foo') self.assertRegexp('help aka slashdot', "a global alias.*Alias for .*foo") self.assertNotRegexp('help aka slashdot', 'Locked by') self.assertNotError('aka lock slashdot') self.assertRegexp('help aka slashdot', 'Locked by evil_admin') self.assertNotError('aka unlock slashdot') self.assertNotRegexp('help aka slashdot', 'Locked by') def testAliasImport(self): self.assertNotError('alias add foo "echo bar"') self.assertNotError(u('alias add baz "echo café"')) self.assertNotError('aka add qux "echo quux"') self.assertResponse('alias foo', 'bar') self.assertResponse('alias baz', 'café') self.assertRegexp('aka foo', 'there is no command named') self.assertResponse('aka qux', 'quux') self.assertNotError('aka importaliasdatabase') self.assertRegexp('alias foo', 'there is no command named') self.assertResponse('aka foo', 'bar') self.assertResponse('aka baz', 'café') self.assertResponse('aka qux', 'quux') self.assertNotError('alias add foo "echo test"') self.assertNotError('alias add spam "echo egg"') self.assertNotError('alias lock spam') self.assertRegexp('aka importaliasdatabase', r'the 1 following command: foo \(This Aka already exists.\)$') self.assertResponse('aka foo', 'bar') self.assertResponse('alias foo', 'test') self.assertRegexp('alias spam', 'there is no command named') self.assertResponse('aka spam', 'egg') def testList(self): self.assertNotError('aka add foo bar') self.assertRegexp('aka list', r'foo.*?bar \$\*') self.assertNotError('aka add "foo bar" baz') self.assertRegexp('aka list', r'foo.*?bar \$\*.*?foo bar.*?baz \$\*') def testListLockedUnlocked(self): self.assertNotError('register tacocat hunter2') self.assertNotError('aka add foo bar') self.assertNotError('aka add abcd echo hi') self.assertNotError('aka lock foo') self.assertRegexp('aka list --locked', 'foo') self.assertNotRegexp('aka list --locked', 'abcd') self.assertNotRegexp('aka list --unlocked', 'foo') self.assertRegexp('aka list --unlocked', 'abcd') # Can't look up both. self.assertError('aka list --locked --unlocked abcd') def testSearch(self): self.assertNotError('aka add foo bar') self.assertNotError('aka add "many words" "much command"') self.assertRegexp('aka search f', 'foo') self.assertError('aka search abcdefghijklmnop') self.assertRegexp('aka search many', 'many words') # This should be case insensitive too. self.assertRegexp('aka search MaNY', 'many words') class AkaWebUITestCase(ChannelHTTPPluginTestCase): plugins = ('Aka',) config = { 'servers.http.keepAlive': True, 'plugins.Aka.web.enable': False, } def setUp(self): super(ChannelHTTPPluginTestCase, self).setUp() httpserver.startServer() def tearDown(self): httpserver.stopServer() super(ChannelHTTPPluginTestCase, self).tearDown() def testToggleWebEnable(self): self.assertHTTPResponse('/aka/', 404) self.assertNotError('config plugins.Aka.web.enable True') self.assertHTTPResponse('/aka/', 200) self.assertNotError('config plugins.Aka.web.enable False') self.assertHTTPResponse('/aka/', 404) def testGlobalPage(self): self.assertNotError('config plugins.Aka.web.enable True') self.assertNotError('aka add foo1 echo 1') self.assertNotError('aka add --channel #foo foo2 echo 2') self.assertNotError('aka add --channel #bar foo3 echo 3') (respCode, body) = self.request('/aka/list/global') self.assertEqual(respCode, 200) self.assertIn(b'foo1', body) self.assertNotIn(b'foo2', body) self.assertNotIn(b'foo3', body) def testChannelPage(self): self.assertNotError('config plugins.Aka.web.enable True') self.assertNotError('aka add foo1 echo 1') self.assertNotError('aka add --channel #foo foo2 echo 2') self.assertNotError('aka add --channel #bar foo3 echo 3') (respCode, body) = self.request('/aka/list/%23foo') self.assertEqual(respCode, 200) self.assertIn(b'foo1', body) self.assertIn(b'foo2', body) self.assertNotIn(b'foo3', body) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Alias/0000755000175000017500000000000013634634547016037 5ustar valval00000000000000limnoria-2020.03.17/plugins/Alias/__init__.py0000644000175000017500000000506313634634532020146 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Allows aliases for other commands. NOTE THAT IT'S RECOMMENDED TO USE Aka PLUGIN INSTEAD! """ 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__ = "" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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! from .plugin import findBiggestDollar, AliasError, escapeAlias, unescapeAlias # for the tests. if world.testing: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Alias/config.py0000644000175000017500000000467713634634532017666 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Alias') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Alias', True) Alias = conf.registerPlugin('Alias') conf.registerGroup(Alias, 'aliases') conf.registerGroup(Alias, 'escapedaliases') conf.registerGlobalValue(Alias, 'validName', registry.String(r'^[^\x00-\x20]+$', _("""Regex which alias names must match in order to be valid"""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Alias/locales/0000755000175000017500000000000013634634547017461 5ustar valval00000000000000limnoria-2020.03.17/plugins/Alias/locales/de.po0000644000175000017500000000530713634634532020410 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2012-02-15 23:03+CET\n" "PO-Revision-Date: 2012-04-27 15:36+0200\n" "Last-Translator: Mikaela Suomalainen \n" "Language-Team: German \n" "Language: de\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: German\n" "X-Poedit-Country: GERMANY\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 "" "Gibt den Kanal aus über den die Nachricht kam oder der Kanal der in den Argumenten gegeben wurde.\n" "\n" "Falls der Kanal in den Argumenten angegeben wurde, werden die Argumente bearbeitet (der Kanal wird entfernt." #: plugin.py:164 msgid " at least" msgstr "mindestens" #: plugin.py:165 msgid "" "\n" "\n" "Alias for %q." msgstr "" "\n" "\n" "Alias für %q." #: plugin.py:166 msgid "argument" msgstr "Argument" #: plugin.py:220 msgid "" "\n" "\n" " Locks an alias so that no one else can change it.\n" " " msgstr "" "\n" "\n" "Versperrt ein Alias, sodass er nicht verändert werden kann." #: plugin.py:229 #: plugin.py:243 msgid "There is no such alias." msgstr "Es gibt keinen Alias mit diesem Namen." #: plugin.py:234 msgid "" "\n" "\n" " Unlocks an alias so that people can define new aliases over it.\n" " " msgstr "" "\n" "\n" "Entsperrt den Alias, sodass andere Personen ihn verändern können." #: plugin.py:254 msgid "That name isn't valid. Try %q instead." msgstr "Dieser Name ist nicht zulässig. Probiere anstatt %q." #: plugin.py:292 #, fuzzy 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" "Definiert einen Alias der ausführt. sollte in der Standardform \"Befehl Argument [verschachtelter Befehl Argument\" angegeben werden." #: plugin.py:315 msgid "" "\n" "\n" " Removes the given alias, if unlocked.\n" " " msgstr "" "\n" "\n" "Entfernt den gegeben Alias, falls er nicht gesperrt ist." limnoria-2020.03.17/plugins/Alias/locales/es.po0000644000175000017500000000761113634634532020427 0ustar valval00000000000000# Spanish translation for limnoria # Copyright (c) 2015 Limnoria contributors 2015 # This file is distributed under the same license as the Limnoria package. # Aaron Farias , 2015. # msgid "" msgstr "" "Project-Id-Version: limnoria\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2014-12-24 15:42+0000\n" "PO-Revision-Date: 2015-01-01 16:43+0000\n" "Last-Translator: Aaron Farias \n" "Language-Team: Spanish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-01-01 17:00+0000\n" "X-Generator: Launchpad (build 17286)\n" #: plugin.py:46 #, docstring 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 "" "Devuelve el msg del canal vino o el canal dado en args.\n" "\n" "Si el canal se le dio en args, args es modificado (el canal está\n" "eliminado).\n" " " #: plugin.py:105 #, docstring msgid "" "Encodes [a-z0-9.]+ into [a-z][a-z0-9].\n" " Format: aa(d)+." msgstr "" "Codifica [a-z0-9.] + En [az] [a-z0-9].\n" "Formato: un a (<índice> d) + ." #: plugin.py:221 msgid " at least" msgstr " al menos" #: plugin.py:223 plugin.py:228 msgid "" "\n" "\n" "Alias for %q." msgstr "" "\n" "\n" "Alias ​​para %q." #: plugin.py:224 plugin.py:229 msgid "argument" msgstr "argumento" #: plugin.py:234 #, docstring msgid "" "This plugin allows users to define aliases to commands and combinations\n" " of commands (via nesting)." msgstr "" "Este plugin permite a los usuarios definir los alias de comandos y " "combinaciones\n" "de comandos (a través de anidación)." #: plugin.py:299 #, docstring msgid "" "\n" "\n" " Locks an alias so that no one else can change it.\n" " " msgstr "" "\n" "\n" "Bloquea un alias para que nadie más puede cambiarlo.\n" "\t\tHay saltos de línea aquí. Cada uno\n" " " #: plugin.py:308 plugin.py:322 msgid "There is no such alias." msgstr "No hay tal alias." #: plugin.py:313 #, docstring msgid "" "\n" "\n" " Unlocks an alias so that people can define new aliases over it.\n" " " msgstr "" "\n" "\n" "Desbloquea un alias para que las personas puedan definir nuevos alias sobre " "él.\n" " " #: plugin.py:334 msgid "That name isn't valid. Try %q instead." msgstr "Ese nombre no es válido. Trate% q en vez." #: plugin.py:379 #, docstring 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" "Define un alias que ejecuta . El \n" "debe estar en el estándar \"argumento de comando [argumento " "nestedcommand]\"\n" "argumentos al alias; que van a ser llenados con el primero, segundo, etc.\n" "argumentos. $ 1, $ 2, etc. puede ser usado para los argumentos necesarios. @ " "1, @ 2,\n" "etc. puede ser utilizado para argumentos opcionales. $ * Simplemente " "significa \"todo\n" "restantes argumentos, \"y no puede ser combinado con argumentos opcionales.\n" " " #: plugin.py:402 #, docstring msgid "" "\n" "\n" " Removes the given alias, if unlocked.\n" " " msgstr "" "\n" "\n" "Elimina los alias dados, si desbloqueado.\n" " " limnoria-2020.03.17/plugins/Alias/locales/fi.po0000644000175000017500000000764613634634532020426 0ustar valval00000000000000# Alias plugin in Limnoria. # Copyright (C) 2011, 2012 Limnoria # Mikaela Suomalainen , 2011, 2012. # msgid "" msgstr "" "Project-Id-Version: Supybot Alias plugin\n" "POT-Creation-Date: 2014-12-20 13:30+EET\n" "PO-Revision-Date: 2014-12-20 13:47+0200\n" "Last-Translator: Mikaela Suomalainen \n" "Language-Team: suomi <>\n" "Language: fi_FI\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: \n" "X-Generator: Poedit 1.6.10\n" #: plugin.py:46 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:105 #, fuzzy msgid "" "Encodes [a-z0-9.]+ into [a-z][a-z0-9].\n" " Format: aa(d)+." msgstr "" "Salaa [a-z0-9.]+ sisään [a-z][a-z0-9].\n" " Muoto: aa(d)+." #: plugin.py:221 msgid " at least" msgstr "vähintään" #: plugin.py:223 plugin.py:228 msgid "" "\n" "\n" "Alias for %q." msgstr "" "\n" "\n" "Alias %q:lle." #: plugin.py:224 plugin.py:229 msgid "argument" msgstr "parametri" #: plugin.py:234 #, fuzzy msgid "" "This plugin allows users to define aliases to commands and combinations\n" " of commands (via nesting)." msgstr "" "Tämä plugini sallii käyttäjien määrittää aliaksia komennoille ja komentojen " "yhdistelmille (sisäytyksellä)." #: plugin.py:299 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:308 plugin.py:322 msgid "There is no such alias." msgstr "Tuollaista aliasta ei ole." #: plugin.py:313 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:334 msgid "That name isn't valid. Try %q instead." msgstr "Tuo nimi ei ole kelvollinen. Yritä sen sijaan %q:ta." #: plugin.py:379 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" " pitäisi olla tavallinen \"komento parametri [sisäkkäinen komento " "parametrit]\"\n" " parametrejä aliakselle; ne täytetään ensimmäinen, toinen, jne.\n" " Parametrit. $1, $2, jne. ovat vaadittava parametrejä. @1, @2,\n" " jne. ovat valinnaisia parametrejä. $* tarkoittaa yksinkertaisesti " "\"kaikki\n" " jäljellä olevat parametrit,\" ja johon ei voida yhdistää vaihtoehtoisia " "parametrejä.\n" " " #: plugin.py:402 msgid "" "\n" "\n" " Removes the given alias, if unlocked.\n" " " msgstr "" "\n" "\n" " Poistaa annetun aliaksen jos se ei ole lukittu.\n" " " #~ msgid "You've attempted more nesting than is currently allowed on this bot." #~ msgstr "" #~ "Yritit sisällyttää useampia komentoja, kuin tämä botti sallii juuri nyt." limnoria-2020.03.17/plugins/Alias/locales/fr.po0000644000175000017500000000636213634634532020431 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-01-22 13:38+CET\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: Limnoria \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" "Language: fr\n" #: plugin.py:46 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 "" "Retourne le canal duquel vient le message ou le canal donné en argument.\n" "\n" "Si le canal était donné en argument, args est modifié (le canal est " "supprimé)." #: plugin.py:108 msgid "" "Encodes [a-z0-9.]+ into [a-z][a-z0-9].\n" " Format: aa(d)+." msgstr "." #: plugin.py:219 msgid "You've attempted more nesting than is currently allowed on this bot." msgstr "" "Vous avez essayé d’utiliser plus d’imbrication que ce qui est actuellement " "autorisé sur ce bot." #: plugin.py:224 msgid " at least" msgstr "au moins" #: plugin.py:226 plugin.py:231 msgid "" "\n" "\n" "Alias for %q." msgstr "" "\n" "\n" "Alias pour %q." #: plugin.py:227 plugin.py:232 msgid "argument" msgstr "argument" #: plugin.py:300 msgid "" "\n" "\n" " Locks an alias so that no one else can change it.\n" " " msgstr "" "\n" "\n" "Vérouille un alias pour que personne d'autre ne puisse le changer." #: plugin.py:309 plugin.py:323 msgid "There is no such alias." msgstr "Cet alias n'existe pas." #: plugin.py:314 msgid "" "\n" "\n" " Unlocks an alias so that people can define new aliases over it.\n" " " msgstr "" "\n" "\n" "Déverrouille un alias de façon à ce que des gens puissent le redéfinir." #: plugin.py:335 msgid "That name isn't valid. Try %q instead." msgstr "Ce nom n'est pas valide. Essayez plutôt %q." #: plugin.py:383 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" "Défini un alias qui exécute . L' peut être dans le " "standard \"commande argument [commandeimbriquee argument]\". Les arguments " "donnés à l'alias doivent être donnés dans l'ordre. Vous pouvez utiliser $1, " "$2, etc pour symboliser les arguments obligatoires qui seront donnés à " "l'alias, et @1, @2, etc pour symboliser ceux optionnels. $* signifie " "simplement *tous* les arguments restants, et ne peut être combiné avec des " "arguments optionnels." #: plugin.py:406 msgid "" "\n" "\n" " Removes the given alias, if unlocked.\n" " " msgstr "" "\n" "\n" "Supprime l'alias donné, si il n'est pas verrouillé." limnoria-2020.03.17/plugins/Alias/locales/hu.po0000644000175000017500000000624613634634532020437 0ustar valval00000000000000# Limnoria Alias plugin. # Copyright (C) 2011 Limnoria # nyuszika7h , 2011. # Mikaela Suomalainen , 2012. # msgid "" msgstr "" "Project-Id-Version: Limnoria Alias\n" "POT-Creation-Date: 2012-03-11 20:58+UTC\n" "PO-Revision-Date: 2012-04-27 15:12+0200\n" "Last-Translator: Mikaela Suomalainen \n" "Language-Team: Finnish <>\n" "Language: \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" "Plural-Forms: nplurals=2; plural=(n!=1);\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 "" "Kiírja a csatorna nevét, ahonnan az üzenet jött, vagy a paraméterként megadott csatornát.\n" "\n" "Ha a csatorna meg volt adva paraméterként, a paraméterek módosulnak (a csatorna eltávolításra kerül).\n" #: plugin.py:164 msgid " at least" msgstr " legalább" #: plugin.py:165 msgid "" "\n" "\n" "Alias for %q." msgstr "" "\n" "\n" " Locks an alias so that no one else can change it.\n" " " msgstr "" "<álnév>\n" "\n" "Lezár egy álnevet, hogy senki más ne változtathassa meg.\n" #: plugin.py:229 #: plugin.py:243 msgid "There is no such alias." msgstr "Nincs ilyen álnév." #: plugin.py:234 msgid "" "\n" "\n" " Unlocks an alias so that people can define new aliases over it.\n" " " msgstr "" "<álnév>\n" "\n" "Feloldja egy álnév lezárását, hogy az emberek új álnevekkel írhassák felül." #: plugin.py:254 msgid "That name isn't valid. Try %q instead." msgstr "Az a név nem érvényes. Próbáld meg %q-t inkább." #: 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 "" " <álnév>\n" "\n" "Meghatároz egy nevű álnevet, amely futtatja <álnév> parancsot. Az <álnév>-nek a szabványos \"parancs paraméter [beágyazottparancs paraméter]\" álnév paraméterei formában kell lennie; ezek ki lesznek töltve az első, második stb. paraméterekkel. $1, $2 stb. használható kötelező paraméterekhez. @1, @2 stb. használható választható paraméterekhez. $* azt jelenti, \"az összes hátralévő paraméter,\" és nem kombinálható választható paraméterekkel.\n" #: plugin.py:315 msgid "" "\n" "\n" " Removes the given alias, if unlocked.\n" " " msgstr "" "\n" "\n" "Eltávolítja a megadott álnevet, ha nincs lezárva.\n" limnoria-2020.03.17/plugins/Alias/locales/it.po0000644000175000017500000000573613634634532020442 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-07 08:23+0200\n" "Last-Translator: skizzhg \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugin.py:45 #, docstring 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 "" "Restituisce il canale da dove proviene il messaggio o il canale fornito come argomento.\n" "\n" " Se il canale è stato dato come argomento, quest'ultimo viene modificato (il canale\n" " viene rimosso).\n" " " #: plugin.py:164 msgid " at least" msgstr " almeno" #: plugin.py:165 msgid "" "\n" "\n" "Alias for %q." msgstr "" "\n" "\n" "Alias per %q." #: plugin.py:166 msgid "argument" msgstr "argomento" #: plugin.py:220 #, docstring msgid "" "\n" "\n" " Locks an alias so that no one else can change it.\n" " " msgstr "" "\n" "\n" " Blocca un alias affinché nessun altro possa modificarlo.\n" " " #: plugin.py:229 plugin.py:243 msgid "There is no such alias." msgstr "Non c'è nessun alias." #: plugin.py:234 #, docstring msgid "" "\n" "\n" " Unlocks an alias so that people can define new aliases over it.\n" " " msgstr "" "\n" "\n" " Sblocca un alias affinché chiunque possa ridefinirne di nuovi.\n" " " #: plugin.py:254 msgid "That name isn't valid. Try %q instead." msgstr "Nome non valido. Prova %q invece." #: plugin.py:292 #, docstring 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" " Definisce un che esegue . deve essere nello\n" " standard \"comando argomento [comando_nidificato argomento]\"; gli\n" " argomenti dati devono essere riportati in sequenza. Per gli argomenti\n" " richiesti è possibile utilizzare $1, $2, ecc., mentre @1, @2, ecc. per\n" " quelli opzionali. $* significa semplicemente \"tutti gli argomenti\n" " rimanenti\" e non può essere combinato con quelli opzionali.\n" " " #: plugin.py:315 #, docstring msgid "" "\n" "\n" " Removes the given alias, if unlocked.\n" " " msgstr "" "\n" "\n" " Rimuove l'alias specificato, se questo non è bloccato.\n" " " limnoria-2020.03.17/plugins/Alias/plugin.py0000644000175000017500000004332513634634532017710 0ustar valval00000000000000### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2014, James McCoy # 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 re import sys import types import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.utils.minisix as minisix import supybot.ircutils as ircutils import supybot.registry as registry import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Alias') # Copied from the old privmsgs.py. def getChannel(irc, msg, args): """Returns the channel the msg came over or the channel given in args. If the channel was given in args, args is modified (the channel is removed). """ if args and msg.channel: if conf.supybot.reply.requireChannelCommandsToBeSentInChannel(): if args[0] != msg.channel: s = 'Channel commands must be sent in the channel to which ' \ 'they apply; if this is not the behavior you desire, ' \ 'ask the bot\'s administrator to change the registry ' \ 'variable ' \ 'supybot.reply.requireChannelCommandsToBeSentInChannel ' \ 'to False.' raise callbacks.Error(s) return args.pop(0) elif msg.channel: return msg.channel else: raise callbacks.Error('Command must be sent in a channel or ' \ 'include a channel in its arguments.') def getArgs(args, required=1, optional=0, wildcard=0): if len(args) < required: raise callbacks.ArgumentError if len(args) < required + optional: ret = list(args) + ([''] * (required + optional - len(args))) elif len(args) >= required + optional: if not wildcard: ret = list(args[:required + optional - 1]) ret.append(' '.join(args[required + optional - 1:])) else: ret = list(args) return ret class AliasError(Exception): pass dollarRe = re.compile(r'\$(\d+)') def findBiggestDollar(alias): dollars = dollarRe.findall(alias) dollars = list(map(int, dollars)) dollars.sort() if dollars: return dollars[-1] else: return 0 atRe = re.compile(r'@(\d+)') def findBiggestAt(alias): ats = atRe.findall(alias) ats = list(map(int, ats)) ats.sort() if ats: return ats[-1] else: return 0 def needsEscaping(alias): return '.' in alias or '|' in alias def escapeAlias(alias): """Encodes dots and pipes Format: aa((d|p))+.""" prefix = '' new_alias = '' prefixes = 0 for index, char in enumerate(alias): if char == '.': prefix += '%sd' % index prefixes += 1 elif char == '|': prefix += '%sp' % index prefixes += 1 else: new_alias += char pre_prefix = 'a%ia' % prefixes return pre_prefix + prefix + new_alias def unescapeAlias(alias): alias = alias[1:] # Strip the leading 'a' escaped_nb = '' while alias[0] in '0123456789': escaped_nb += alias[0] alias = alias[1:] alias = alias[1:] escaped_nb = int(escaped_nb) escaped_chars = [] while alias[0] in '0123456789': current_group = '' while alias[0] in '0123456789': current_group += alias[0] alias = alias[1:] if alias[0] == 'd': char = '.' elif alias[0] == 'p': char = '|' else: char = alias[0] alias = alias[1:] escaped_chars.append((int(current_group), char)) if len(escaped_chars) == escaped_nb: break new_alias = '' index = 0 for char in alias: if escaped_chars and index == escaped_chars[0][0]: new_alias += escaped_chars[0][1] escaped_chars.pop(0) index += 1 new_alias += char index += 1 return new_alias def makeNewAlias(name, alias): original = alias biggestDollar = findBiggestDollar(original) biggestAt = findBiggestAt(original) wildcard = '$*' in original if biggestAt and wildcard: raise AliasError('Can\'t mix $* and optional args (@1, etc.)') if original.count('$*') > 1: raise AliasError('There can be only one $* in an alias.') testTokens = callbacks.tokenize(original) if testTokens and isinstance(testTokens[0], list): raise AliasError('Commands may not be the result of nesting.') def f(self, irc, msg, args): alias = original.replace('$nick', msg.nick) if '$channel' in original: channel = getChannel(irc, msg, args) alias = alias.replace('$channel', channel) tokens = callbacks.tokenize(alias) if biggestDollar or biggestAt: args = getArgs(args, required=biggestDollar, optional=biggestAt, wildcard=wildcard) max_len = conf.supybot.reply.maximumLength() args = list([x[:max_len] for x in args]) def regexpReplace(m): idx = int(m.group(1)) return args[idx-1] def replace(tokens, replacer): for (i, token) in enumerate(tokens): if isinstance(token, list): replace(token, replacer) else: tokens[i] = replacer(token) replace(tokens, lambda s: dollarRe.sub(regexpReplace, s)) if biggestAt: assert not wildcard args = args[biggestDollar:] replace(tokens, lambda s: atRe.sub(regexpReplace, s)) if wildcard: assert not biggestAt # Gotta remove the things that have already been subbed in. i = biggestDollar while i: args.pop(0) i -= 1 def everythingReplace(tokens): for (i, token) in enumerate(tokens): if isinstance(token, list): if everythingReplace(token): return if token == '$*': tokens[i:i+1] = args return True elif '$*' in token: tokens[i] = token.replace('$*', ' '.join(args)) return True return False everythingReplace(tokens) # Limit memory use by constraining the size of the message being passed # in to the alias. Also tracking nesting to avoid endless recursion. maxLength = conf.supybot.reply.maximumLength() tokens = [t[:maxLength] for t in tokens] self.Proxy(irc, msg, tokens, nested=irc.nested + 1) flexargs = '' if biggestDollar and (wildcard or biggestAt): flexargs = _(' at least') try: doc = format(_('\n\nAlias for %q.'), flexargs, (biggestDollar, _('argument')), alias) except UnicodeDecodeError: if minisix.PY2: alias = alias.decode('utf8') doc = format(_('\n\nAlias for %q.'), flexargs, (biggestDollar, _('argument')), alias) f = utils.python.changeFunctionName(f, name, doc) return f class Alias(callbacks.Plugin): """This plugin allows users to define aliases to commands and combinations of commands (via nesting).""" def __init__(self, irc): self.__parent = super(Alias, self) self.__parent.__init__(irc) # Schema: {alias: [command, locked, commandMethod]} self.aliases = {} # XXX This should go. aliases should be a space separate list, etc. group = conf.supybot.plugins.Alias.aliases group2 = conf.supybot.plugins.Alias.escapedaliases prefixLen = len(registry.split('supybot.plugins.alias.aliases')) for (name, alias) in registry._cache.items(): name = name.lower() nameSplit = registry.split(name) if len(nameSplit) > prefixLen+1: continue if name.startswith('supybot.plugins.alias.aliases.'): name = nameSplit[-1] conf.registerGlobalValue(group, name, registry.String('', '')) conf.registerGlobalValue(group.get(name), 'locked', registry.Boolean(False, '')) elif name.startswith('supybot.plugins.alias.escapedaliases.'): name = nameSplit[-1] conf.registerGlobalValue(group2, name, registry.String('', '')) conf.registerGlobalValue(group2.get(name), 'locked', registry.Boolean(False, '')) for (name, value) in group.getValues(fullNames=False): name = name.lower() # Just in case. command = value() locked = value.locked() self.aliases[name] = [command, locked, None] for (name, value) in group2.getValues(fullNames=False): name = name.lower() # Just in case. command = value() locked = value.locked() self.aliases[unescapeAlias(name)] = [command, locked, None] for (alias, (command, locked, _)) in self.aliases.copy().items(): try: self.addAlias(irc, alias, command, locked) except Exception as e: self.log.exception('Exception when trying to add alias %s. ' 'Removing from the Alias database.', alias) del self.aliases[alias] def isCommandMethod(self, name): if not self.__parent.isCommandMethod(name): if name in self.aliases: return True else: return False else: return True def listCommands(self): return self.__parent.listCommands(self.aliases.keys()) def getCommandMethod(self, command): try: return self.__parent.getCommandMethod(command) except AttributeError: return self.aliases[command[0]][2] def aliasRegistryGroup(self, name): if needsEscaping(name): return self.registryValue('escapedaliases', value=False) else: return self.registryValue('aliases', value=False) def aliasRegistryNode(self, name): group = self.aliasRegistryGroup(name) if needsEscaping(name): return group.get(escapeAlias(name)) else: return group.get(name) def aliasRegistryRemove(self, name): group = self.aliasRegistryGroup(name) if needsEscaping(name): group.unregister(escapeAlias(name)) else: group.unregister(name) def setLocked(self, name, value): self.aliases[name][1] = value self.aliasRegistryNode(name).locked.setValue(value) def isValidName(self, name): if not re.search(self.registryValue('validName'), name): return False if not registry.isValidRegistryName(name): return False return True @internationalizeDocstring def lock(self, irc, msg, args, name): """ Locks an alias so that no one else can change it. """ if name in self.aliases and self.isCommandMethod(name): self.setLocked(name, True) irc.replySuccess() else: irc.error(_('There is no such alias.')) lock = wrap(lock, [('checkCapability', 'admin'), 'commandName']) @internationalizeDocstring def unlock(self, irc, msg, args, name): """ Unlocks an alias so that people can define new aliases over it. """ if name in self.aliases and self.isCommandMethod(name): self.setLocked(name, False) irc.replySuccess() else: irc.error(_('There is no such alias.')) unlock = wrap(unlock, [('checkCapability', 'admin'), 'commandName']) def addAlias(self, irc, name, alias, lock=False): if not self.isValidName(name): raise AliasError('Invalid alias name.') realName = callbacks.canonicalName(name) if name != realName: s = format(_('That name isn\'t valid. Try %q instead.'), realName) raise AliasError(s) name = realName if self.isCommandMethod(name): if realName not in self.aliases: s = 'You can\'t overwrite commands in this plugin.' raise AliasError(s) if name in self.aliases: (currentAlias, locked, _) = self.aliases[name] if locked and currentAlias != alias: raise AliasError(format('Alias %q is locked.', name)) f = makeNewAlias(name, alias) f = types.MethodType(f, self) if name in self.aliases: # We gotta remove it so its value gets updated. self.aliasRegistryRemove(name) aliasGroup = self.aliasRegistryGroup(name) if needsEscaping(name): confname = escapeAlias(name) else: confname = name conf.registerGlobalValue(aliasGroup, confname, registry.String(alias, '')) conf.registerGlobalValue(aliasGroup.get(confname), 'locked', registry.Boolean(lock, '')) self.aliases[name] = [alias, lock, f] def removeAlias(self, name, evenIfLocked=False): name = callbacks.canonicalName(name) if name in self.aliases and self.isCommandMethod(name): if evenIfLocked or not self.aliases[name][1]: del self.aliases[name] self.aliasRegistryRemove(name) else: raise AliasError('That alias is locked.') else: raise AliasError('There is no such alias.') @internationalizeDocstring def add(self, irc, msg, args, name, alias): """ Defines an alias that executes . The should be in the standard "command argument [nestedcommand argument]" arguments to the alias; they'll be filled with the first, second, etc. arguments. $1, $2, etc. can be used for required arguments. @1, @2, etc. can be used for optional arguments. $* simply means "all remaining arguments," and cannot be combined with optional arguments. """ if ' ' not in alias: # If it's a single word, they probably want $*. alias += ' $*' try: self.addAlias(irc, name, alias) self.log.info('Adding alias %q for %q (from %s)', name, alias, msg.prefix) irc.replySuccess() except AliasError as e: irc.error(str(e)) add = wrap(add, ['commandName', 'text']) @internationalizeDocstring def remove(self, irc, msg, args, name): """ Removes the given alias, if unlocked. """ try: self.removeAlias(name) self.log.info('Removing alias %q (from %s)', name, msg.prefix) irc.replySuccess() except AliasError as e: irc.error(str(e)) remove = wrap(remove, ['commandName']) @internationalizeDocstring def list(self, irc, msg, args, optlist): """[--locked|--unlocked] Lists alias names of a particular type, defaults to all aliases if no --locked or --unlocked option is given. """ optlist = dict(optlist) if len(optlist)>1: irc.error(_('Cannot specify --locked and --unlocked simultaneously')) return aliases = [] for name in self.aliases.keys(): if self.isCommandMethod(name): if 'locked' in optlist: if self.aliases[name][1]: aliases.append(name) elif 'unlocked' in optlist: if not self.aliases[name][1]: aliases.append(name) else: aliases.append(name) if aliases: aliases.sort() irc.reply(format('%L', aliases)) else: if len(optlist): irc.reply(_('There are no aliases of that type.')) else: irc.reply(_('There are no aliases.')) list = wrap(list, [getopts({'locked':'', 'unlocked':''})]) Class = Alias # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Alias/test.py0000644000175000017500000001747513634634532017400 0ustar valval00000000000000# -*- coding: utf8 -*- ### # Copyright (c) 2002-2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * import supybot.conf as conf import supybot.plugin as plugin import supybot.registry as registry from supybot.utils.minisix import u from . import plugin as Alias class FunctionsTest(SupyTestCase): def testFindBiggestDollar(self): self.assertEqual(Alias.findBiggestDollar(''), 0) self.assertEqual(Alias.findBiggestDollar('foo'), 0) self.assertEqual(Alias.findBiggestDollar('$0'), 0) self.assertEqual(Alias.findBiggestDollar('$1'), 1) self.assertEqual(Alias.findBiggestDollar('$2'), 2) self.assertEqual(Alias.findBiggestDollar('$2 $10'), 10) self.assertEqual(Alias.findBiggestDollar('$3'), 3) self.assertEqual(Alias.findBiggestDollar('$3 $2 $1'), 3) self.assertEqual(Alias.findBiggestDollar('foo bar $1'), 1) self.assertEqual(Alias.findBiggestDollar('foo $2 $1'), 2) self.assertEqual(Alias.findBiggestDollar('foo $0 $1'), 1) self.assertEqual(Alias.findBiggestDollar('foo $1 $3'), 3) self.assertEqual(Alias.findBiggestDollar('$10 bar $1'), 10) class AliasTestCase(ChannelPluginTestCase): plugins = ('Alias', 'Filter', 'Utilities', 'Format', 'Reply') def testNoAliasWithNestedCommandName(self): self.assertError('alias add foo "[bar] baz"') def testDoesNotOverwriteCommands(self): # We don't have dispatcher commands anymore #self.assertError('alias add alias "echo foo bar baz"') self.assertError('alias add add "echo foo bar baz"') self.assertError('alias add remove "echo foo bar baz"') self.assertError('alias add lock "echo foo bar baz"') self.assertError('alias add unlock "echo foo bar baz"') def testAliasHelp(self): self.assertNotError('alias add slashdot foo') self.assertRegexp('help slashdot', "Alias for .*foo") self.assertNotError('alias add nonascii echo éé') self.assertRegexp('help nonascii', "Alias for .*echo éé") def testRemove(self): self.assertNotError('alias add foo echo bar') self.assertResponse('foo', 'bar') self.assertNotError('alias remove foo') self.assertError('foo') def testDollars(self): self.assertNotError('alias add rot26 "rot13 [rot13 $1]"') self.assertResponse('rot26 foobar', 'foobar') def testMoreDollars(self): self.assertNotError('alias add rev "echo $3 $2 $1"') self.assertResponse('rev foo bar baz', 'baz bar foo') def testAllArgs(self): self.assertNotError('alias add swap "echo $2 $1 $*"') self.assertResponse('swap 1 2 3 4 5', '2 1 3 4 5') self.assertError('alias add foo "echo $1 @1 $*"') self.assertNotError('alias add moo echo $1 $*') self.assertError('moo') self.assertResponse('moo foo', 'foo') self.assertResponse('moo foo bar', 'foo bar') def testChannel(self): self.assertNotError('alias add channel echo $channel') self.assertResponse('alias channel', self.channel) def testNick(self): self.assertNotError('alias add sendingnick "rot13 [rot13 $nick]"') self.assertResponse('sendingnick', self.nick) def testAddRemoveAlias(self): cb = self.irc.getCallback('Alias') cb.addAlias(self.irc, 'foobar', 'echo sbbone', lock=True) self.assertResponse('foobar', 'sbbone') self.assertRaises(Alias.AliasError, cb.removeAlias, 'foobar') cb.removeAlias('foobar', evenIfLocked=True) self.assertFalse('foobar' in cb.aliases) self.assertError('foobar') self.assertRegexp('alias add abc\x07 ignore', 'Error.*Invalid') def testOptionalArgs(self): self.assertNotError('alias add myrepr "repr @1"') self.assertResponse('myrepr foo', '"foo"') self.assertResponse('myrepr ""', '""') def testNoExtraSpaces(self): self.assertNotError('alias add foo "action takes $1\'s money"') self.assertResponse('foo bar', '\x01ACTION takes bar\'s money\x01') def testNoExtraQuotes(self): self.assertNotError('alias add myre "echo s/$1/$2/g"') self.assertResponse('myre foo bar', 's/foo/bar/g') def testUnicode(self): self.assertNotError(u('alias add \u200b echo foo')) self.assertResponse(u('\u200b'), 'foo') self.assertNotError('alias add café echo bar') self.assertResponse('café', 'bar') def testSimpleAliasWithoutArgsImpliesDollarStar(self): self.assertNotError('alias add exo echo') self.assertResponse('exo foo bar baz', 'foo bar baz') class EscapedAliasTestCase(ChannelPluginTestCase): plugins = ('Alias', 'Utilities') def setUp(self): registry._cache.update( {'supybot.plugins.Alias.escapedaliases.a1a3dfoobar': 'echo baz', 'supybot.plugins.Alias.escapedaliases.a1a3dfoobar.locked': 'False'}) super(EscapedAliasTestCase, self).setUp() def testReadDatabase(self): self.assertResponse('foo.bar', 'baz') def testAdd(self): self.assertNotError('alias add spam.egg echo hi') self.assertResponse('spam.egg', 'hi') self.assertNotError('alias add spam|egg echo hey') self.assertResponse('spam|egg', 'hey') self.assertNotError('alias remove spam.egg') self.assertError('spam.egg') self.assertNotError('spam|egg') self.assertNotError('alias remove spam|egg') self.assertError('spam.egg') self.assertError('spam|egg') def testWriteDatabase(self): self.assertNotError('alias add fooo.spam echo egg') self.assertResponse('fooo.spam', 'egg') self.assertTrue(hasattr(conf.supybot.plugins.Alias.escapedaliases, 'a1a4dfooospam')) self.assertEqual(conf.supybot.plugins.Alias.escapedaliases.a1a4dfooospam(), 'echo egg') self.assertNotError('alias add foo.spam.egg echo supybot') self.assertResponse('foo.spam.egg', 'supybot') self.assertTrue(hasattr(conf.supybot.plugins.Alias.escapedaliases, 'a2a3d8dfoospamegg')) self.assertEqual(conf.supybot.plugins.Alias.escapedaliases.a2a3d8dfoospamegg(), 'echo supybot') self.assertEqual(Alias.unescapeAlias('a2a3d8dfoospamegg'), 'foo.spam.egg') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Anonymous/0000755000175000017500000000000013634634547016776 5ustar valval00000000000000limnoria-2020.03.17/plugins/Anonymous/__init__.py0000644000175000017500000000466413634634532021113 0ustar valval00000000000000### # Copyright (c) 2005, Daniel DiPaolo # 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. ### """ Allows folks to talk through the bot anonymously. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.strike __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Anonymous/config.py0000644000175000017500000000662013634634532020613 0ustar valval00000000000000### # Copyright (c) 2005, Daniel DiPaolo # 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 from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Anonymous') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Anonymous', True) Anonymous = conf.registerPlugin('Anonymous') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Anonymous, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) conf.registerChannelValue(conf.supybot.plugins.Anonymous, 'requirePresenceInChannel', registry.Boolean(True, _("""Determines whether the bot should require people trying to use this plugin to be in the channel they wish to anonymously send to."""))) conf.registerChannelValue(conf.supybot.plugins.Anonymous, 'requireRegistration', registry.Boolean(True, _("""Determines whether the bot should require people trying to use this plugin to be registered."""))) conf.registerChannelValue(conf.supybot.plugins.Anonymous, 'requireCapability', registry.String('', _("""Determines what capability (if any) the bot should require people trying to use this plugin to have."""))) conf.registerGlobalValue(conf.supybot.plugins.Anonymous, 'allowPrivateTarget', registry.Boolean(False, _("""Determines whether the bot will allow the "tell" command to be used. If true, the bot will allow the "tell" command to send private messages to other users."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Anonymous/locales/0000755000175000017500000000000013634634547020420 5ustar valval00000000000000limnoria-2020.03.17/plugins/Anonymous/locales/de.po0000644000175000017500000001017213634634532021343 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2011-06-09 18:26+CEST\n" "PO-Revision-Date: 2011-10-28 12:55+0100\n" "Last-Translator: Florian Besser \n" "Language-Team: German \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: de\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 "Legt fest ob Leute im Kanal sein müssen, an den anonym senden wollen." #: config.py:53 msgid "" "Determines whether the bot should require\n" " people trying to use this plugin to be registered." msgstr "Legt fest ob Nutzer registiert sein müssen um dieses Plugin zu benutze." #: config.py:56 msgid "" "Determines what capability (if any) the bot should\n" " require people trying to use this plugin to have." msgstr "Legt fest welche Fähgikeiten (falls überhaupt) der Bot verlangt von Personen die versuchen dieses Plugin zu benutzen." #: 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 "Legt fest ob der Bot verlangt, dass Ziele des Befehls \"say\" öffentlich sind (z.B. Kanäle). Falls das auf True gesetzt ist, wird ber Bot Nutzern erlauben den Befehl \"say\" zu nutzen um anderen Nutzern private Nachrichten zu senden." #: plugin.py:40 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 "Das Plugin erlaubt Nutzern durch den Bot anonym zu bleiben. Der 'do' Befehl lässt den Bot eine anonyme Aktion in einem Kanal ausführen und der 'say' Befehl lässt Nutzer durch den Bot sprechen. Da das Ganze natürlich leicht missbraucht werden kann, willst du vielleicht supybot.plugins.Anonymous.requireCapability setzen, sodass nur Nutzer mit dieser Fähigkeit das Plugin benutzen können. Für etwas mehr Sicherheit kannst du mit supybot.plugins.Anonymous.requirePresenceInChannel verlangen, dass der Nutzer in dem Kanal sein muss in dem er anonym senden will oder du kannst verlangen, dass der Nutzer registriert sein muss indem du supybot.plugins.Anonymous.requireRegistration setzt." #: plugin.py:64 msgid "You must be in %s to %q in there." msgstr "Du musst in %s sein um %q dort auszuführen." #: plugin.py:68 msgid "I'm lobotomized in %s." msgstr "Ich bin hirnamputiert in %s." #: plugin.py:71 msgid "That channel has set its capabilities so as to disallow the use of this plugin." msgstr "Für den Kanal sind die Fähigkeiten so gesetzt, dass sie das benutzen dieses Plugins nicht erlauben." #: plugin.py:74 msgid "%q cannot be used to send private messages." msgstr "%q kann nicht verwendet werden um private Nachrichten zu versenden." #: plugin.py:80 msgid "" " \n" "\n" " Sends to . Can only send to if\n" " supybot.plugins.Anonymous.allowPrivateTarget is True.\n" " " msgstr "" " \n" "\n" "Sendet an . Kann nur an senden wenn supybot.plugins.Anonymous.allowPrivateTarget auf True gesetzt ist." #: plugin.py:94 msgid "" " \n" "\n" " Performs in .\n" " " msgstr "" " \n" "\n" "Führt die im aus." limnoria-2020.03.17/plugins/Anonymous/locales/es.po0000644000175000017500000001141413634634532021362 0ustar valval00000000000000# Spanish translation for limnoria # Copyright (c) 2015 Limnoria contributors 2015 # This file is distributed under the same license as the Limnoria package. # Aaron Farias , 2015. # msgid "" msgstr "" "Project-Id-Version: limnoria\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2014-12-24 15:42+0000\n" "PO-Revision-Date: 2015-01-01 16:56+0000\n" "Last-Translator: Aaron Farias \n" "Language-Team: Spanish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-01-01 17:04+0000\n" "X-Generator: Launchpad (build 17286)\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 "" "Determina si\n" "el robot debe requerir la gente tratando de utilizar este plugin para poder " "estar en el\n" "canal que desean enviar anónimamente." #: config.py:53 msgid "" "Determines whether the bot should require\n" " people trying to use this plugin to be registered." msgstr "" "Determina si el bot debe exigir\n" "personas que tratan de utilizar este plugin para ser registrados." #: config.py:56 msgid "" "Determines what capability (if any) the bot should\n" " require people trying to use this plugin to have." msgstr "" "Determina lo que la capacidad (si lo hay) que el bot debe\n" "requieren que las personas tratando de usar este plugin para poder tener." #: config.py:59 msgid "" "Determines whether the bot will allow the\n" " \"tell\" command to be used. If true, the bot will allow the \"tell\"\n" " command to send private messages to other users." msgstr "" "Determina si el bot permitirá al\n" "Comando \"decirle\" a utilizar. De ser cierto, el bot le permitirá al " "\"decir\"\n" "comando para enviar mensajes privados a otros usuarios." #: plugin.py:41 #, docstring 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 "" "Este plugin permite a los usuarios actúan a través de la bot anónima. El " "\"hacer\"\n" "comando tiene el robot realice una acción anónimo en un canal determinado, " "y\n" "el 'dicen' comando permite que otras personas hablan a través de la bot. " "Desde\n" "esto puede ser bastante maltratado, es posible que desee establecer\n" "supybot.plugins.Anonymous.requireCapability tan sólo los usuarios con que\n" "capacidad puede utilizar este plugin. Para mayor seguridad, se puede " "requerir que\n" "el usuario ser * en * el canal que están tratando de hacer frente de forma " "anónima con\n" "supybot.plugins.Anonymous.requirePresenceInChannel, o puede requerir\n" "que el usuario se ha registrado mediante el establecimiento de\n" "supybot.plugins.Anonymous.requireRegistration.\n" " " #: plugin.py:65 msgid "You must be in %s to %q in there." msgstr "Usted debe estar en% s para% q allí." #: plugin.py:69 msgid "I'm lobotomized in %s." msgstr "Estoy lobotomizado en%s." #: plugin.py:72 msgid "" "That channel has set its capabilities so as to disallow the use of this " "plugin." msgstr "" "Ese canal ha puesto sus capacidades a fin de no permitir el uso de este " "plugin." #: plugin.py:75 msgid "" "This command is disabled (supybot.plugins.Anonymous.allowPrivateTarget is " "False)." msgstr "" "Este comando está desactivado (supybot.plugins.Anonymous.allowPrivateTarget " "es falso)." #: plugin.py:80 #, docstring msgid "" " \n" "\n" " Sends to .\n" " " msgstr "" " \n" "\n" "Envía al .\n" " " #: plugin.py:92 #, docstring msgid "" " \n" "\n" " Sends to . Can only be used if\n" " supybot.plugins.Anonymous.allowPrivateTarget is True.\n" " " msgstr "" " \n" "\n" "Envía a . Sólo puede utilizarse si\n" "supybot.plugins.Anonymous.allowPrivateTarget es True.\n" " " #: plugin.py:106 #, docstring msgid "" " \n" "\n" " Performs in .\n" " " msgstr "" " \n" "\n" "Realiza en .\n" " " limnoria-2020.03.17/plugins/Anonymous/locales/fi.po0000644000175000017500000001236313634634532021355 0ustar valval00000000000000# Anonymous plugin in Limnoria. # Copyright (C) 2011-2014 Limnoria # Mikaela Suomalainen , 2011-2014. # msgid "" msgstr "" "Project-Id-Version: Supybot Anonymous\n" "POT-Creation-Date: 2014-12-20 11:59+EET\n" "PO-Revision-Date: \n" "Last-Translator: Mikaela Suomalainen \n" "Language-Team: \n" "Language: fi_FI\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 1.6.10\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äärittää vaatiiko \n" "botti ihmisten, jotka yrittävät käyttää tätä lisäosaa, olemaan\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äärittää vaatiiko botti\n" "ihmisiä, jotka yrittävät käyttää tätä lisäosaa, olemaan 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ää minkä valtuuden (jos minkään) botti vaatii\n" " ihmisiltä, jotka yrittävät käyttää tätä lisäosaa." #: config.py:59 #, fuzzy msgid "" "Determines whether the bot will allow the\n" " \"tell\" command to be used. If true, the bot will allow the \"tell\"\n" " command to send private messages to other users." msgstr "" "Määrittää salliiko botti \"tell\"-komennon käytön. Jos tämä on True, botti " "sallii \"tell\"-komennon\n" " lähettävän yksityisviestejä 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ä tuntemattoman toiminnon annetulla " "kanavalla ja\n" " komento 'say' sallii toisten ihmisten puhua botin läpi. Koska\n" " tätä voidaan väärinkäyttää helposti voit tahtoa asettaa asetuksen\n" " supybot.plugins.Anonymous.requireCapability niin, että vain käyttäjät " "tuolla\n" " valtuudella voivat käyttää tätä lisäosaa. Lisäturvallisuuden vuoksi voit " "vaatia, että käyttäjien täytyy *olla* kanavilla 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:65 msgid "You must be in %s to %q in there." msgstr "Sinun täytyy olla kanavalla %s %q sinne." #: plugin.py:69 msgid "I'm lobotomized in %s." msgstr "Minut on lobotomoitu kanavalla %s." #: plugin.py:72 msgid "" "That channel has set its capabilities so as to disallow the use of this " "plugin." msgstr "Tuo kanava on asettanut valtuudet kieltämään tämän Pluginin käytön." #: plugin.py:75 msgid "" "This command is disabled (supybot.plugins.Anonymous.allowPrivateTarget is " "False)." msgstr "" "Tämä komento on poistettu käytöstä (supybot.plugins.Anonymous." "allowPrivateTarget on False)." #: plugin.py:80 msgid "" " \n" "\n" " Sends to .\n" " " msgstr "" " \n" " Lähettää ." #: plugin.py:92 msgid "" " \n" "\n" " Sends to . Can only be used if\n" " supybot.plugins.Anonymous.allowPrivateTarget is True.\n" " " msgstr "" " \n" "\n" " Lähettää . voidaan lähettää\n" " viestejä vain asetusarvon supybot.plugins.Anonymous.allowPrivateTarget\n" " ollessa True.\n" " " #: plugin.py:106 msgid "" " \n" "\n" " Performs in .\n" " " msgstr "" " \n" "\n" "Suorittaa .\n" " " #~ msgid "%q cannot be used to send private messages." #~ msgstr "%q:ta ei voi käyttää yksityisviestien lähettämiseen." #~ msgid "" #~ " \n" #~ "\n" #~ " Sends to . Can only send to if\n" #~ " supybot.plugins.Anonymous.allowPrivateTarget is True.\n" #~ " " #~ msgstr "" #~ " \n" #~ "\n" #~ " Lähettää . voidaan lähettää " #~ "viestejä vain \n" #~ " asetusarvon supybot.plugins.Anonymous.allowPrivateTarget ollessa " #~ "True.\n" #~ " " limnoria-2020.03.17/plugins/Anonymous/locales/fr.po0000644000175000017500000000702513634634532021365 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-06-09 18:26+CEST\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Limnoria \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" #: 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 "Détermine si le bot requiérera que les gens soient sur le canal pour y envoyer des messages anonymement." #: config.py:53 msgid "" "Determines whether the bot should require\n" " people trying to use this plugin to be registered." msgstr "Détermine si le bot requiérera que les personnes utilisant ce plugin soient enregistrées." #: config.py:56 msgid "" "Determines what capability (if any) the bot should\n" " require people trying to use this plugin to have." msgstr "Détermine quelle capacité (s'il y en a une) le bot requiéra que les gens utilisant ce plugin aient." #: 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 "Détermine si le bot requiérera que les cibles de la commande \"say\" soient publiques (c'est à dire des canaux). Si c'est True, le bot autorisera les personnes à utiliser la commande \"say\" pour envoyer des messages à d'autres utilisateurs en privé." #: plugin.py:40 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 "" #: plugin.py:64 msgid "You must be in %s to %q in there." msgstr "Vous devez être sur %s pour y utiliser %q." #: plugin.py:68 msgid "I'm lobotomized in %s." msgstr "Je suis lobotomisé sur %s." #: plugin.py:71 msgid "That channel has set its capabilities so as to disallow the use of this plugin." msgstr "Ce canal a définit ses capacités de façon à désactiver l'utilisation de ce plugin." #: plugin.py:74 msgid "%q cannot be used to send private messages." msgstr "%q ne peut pas être utilisé pour envoyer des messages privés." #: plugin.py:80 msgid "" " \n" "\n" " Sends to . Can only send to if\n" " supybot.plugins.Anonymous.allowPrivateTarget is True.\n" " " msgstr "" " \n" "\n" "Envoie le au . Vous ne pouvez envoyer à que si supybot.plugins.Anonymous.allowPrivateTarget vaut True." #: plugin.py:94 msgid "" " \n" "\n" " Performs in .\n" " " msgstr "" " \n" "\n" "Effectue l' sur le ." limnoria-2020.03.17/plugins/Anonymous/locales/hu.po0000644000175000017500000001064713634634532021376 0ustar valval00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: Limnoria Anonymous\n" "POT-Creation-Date: 2011-06-09 18:26+CEST\n" "PO-Revision-Date: 2011-07-21 17:32+0100\n" "Last-Translator: nyuszika7h \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" #: 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 "Meghatározza, hogy a bot megkövetelje-e, hogy a bővítményt használni kívánó emberek abban a csatornában legyenek, ahová névtelenül szeretnének küldeni." #: config.py:53 msgid "" "Determines whether the bot should require\n" " people trying to use this plugin to be registered." msgstr "Meghatározza, hogy a bot megkövetelje-e, hogy a bővítményt használni kívánó emberek regisztrálva legyenek." #: config.py:56 msgid "" "Determines what capability (if any) the bot should\n" " require people trying to use this plugin to have." msgstr "Meghatározza, hogy milyen képesség (ha van ilyen) legyen szükséges a bővítmény használatához." #: 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 "Meghatározza, hogy a bot megkövetelje-e, hogy a \"say\" parancs célja publikus legyen (pl., csatornák). Ha ez True, a bot megengedi az embereknek, hogy a \"say\" paranccsal privát üzenetet küldjenek másoknak." #: plugin.py:40 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 "Ez a bővítmény megengedi a felhasználóknak, hogy névtelenül cselekedjenek a boton keresztül. A 'do' parancs hatására a bot végrehajt egy névtelen tevékenységet egy megadott csatornában, és a 'say' parancs megengedi másoknak, hogy a boton keresztül beszéljenek. Mivel ezzel elég könnyen vissza lehet élni, érdemes beállítani a supybot.plugins.Anonymous.requireCapability-t, hogy csak az adott képességgel rendelkező felhasználók használhassák ezt a bővítményt. Extra biztonságért a supybot.plugins.Anonymous.requirePresenceInChannel-lel megkövetelheted, hogy a felhasználó abban a csatornában legyen, amelyet névtelenül szeretnének megcímezni, vagy a supybot.plugins.Anonymous.requireRegistration-nel megkövetelheted, hogy a felhasználó regisztálva legyen." #: plugin.py:64 msgid "You must be in %s to %q in there." msgstr "%s-ban kell lenned, hogy a %q parancsot használd ott." #: plugin.py:68 msgid "I'm lobotomized in %s." msgstr "Némítva vagyok %s-ban." #: plugin.py:71 msgid "That channel has set its capabilities so as to disallow the use of this plugin." msgstr "A megadott csatornában nem használhatod a bővítményt a csatorna képességei miatt." #: plugin.py:74 msgid "%q cannot be used to send private messages." msgstr "A %q parancs nem használható privát üzenetek küldésére." #: plugin.py:80 msgid "" " \n" "\n" " Sends to . Can only send to if\n" " supybot.plugins.Anonymous.allowPrivateTarget is True.\n" " " msgstr "" " \n" "\n" "Elküldi -et -nek. Csak akkor küldhet -nek, ha a supybot.plugins.Anonymous.allowPrivateTarget True." #: plugin.py:94 msgid "" " \n" "\n" " Performs in .\n" " " msgstr "" " \n" "\n" "Végrehajtja -et -ban." limnoria-2020.03.17/plugins/Anonymous/locales/it.po0000644000175000017500000001025213634634532021366 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-08-10 00:13+0200\n" "Last-Translator: skizzhg \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\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 "" "Determina se il bot richieda che gli utenti siano in canale\n" " per inviare messaggi in forma anonima." #: config.py:53 msgid "" "Determines whether the bot should require\n" " people trying to use this plugin to be registered." msgstr "" "Determina se il bot richieda che gli utenti siano registrati\n" " per usare il plugin." #: config.py:56 msgid "" "Determines what capability (if any) the bot should\n" " require people trying to use this plugin to have." msgstr "" "Determina quali capacità (eventuali) debbano\n" " avere gli utenti per utilizzare questo plugin." #: 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 "" "Determina se il bot richiederà che le destinazioni del comando \"say\" siano\n" " pubbliche (ovvero i canali). Se impostato a True, il bot permetterà\n" " di usare il comando \"say\" per inviare messaggi privati ad altri utenti." #: plugin.py:40 #, docstring 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 "" "Questo plugin permette agli utenti di agire attraverso il bot in modo anonimo.\n" " Il comando \"do\" esegue un'azione anonima in un dato canale, mentre \"say\"\n" " permette di parlare tramite il bot. Giacché si può facilmente abusarne, è\n" " possibile impostare supybot.plugins.Anonymous.requireCapability in modo che\n" " solo gli utenti con determinate capacità possano usare il plugin. Per una\n" " maggiore sicurezza è ppossibile richiedere con supybot.plugins.Anonymous.requirePresenceInChannel\n" " che l'utente intenzionato a parlare anonimamente sia in canale; o anche, tramite\n" " supybot.plugins.Anonymous.requireRegistration, che l'utente sia registrato." " " #: plugin.py:64 msgid "You must be in %s to %q in there." msgstr "Devi essere in %s per %q." #: plugin.py:68 msgid "I'm lobotomized in %s." msgstr "In %s sono lobotomizzato." #: plugin.py:71 msgid "That channel has set its capabilities so as to disallow the use of this plugin." msgstr "Questo canale ha le capacità impostate in modo da impedire l'utilizzo di questo plugin." #: plugin.py:74 msgid "%q cannot be used to send private messages." msgstr "%q non può essere usato per inviare messaggi privati." #: plugin.py:80 #, docstring msgid "" " \n" "\n" " Sends to . Can only send to if\n" " supybot.plugins.Anonymous.allowPrivateTarget is True.\n" " " msgstr "" " \n" "\n" " Invia a . Può solo inviare a se\n" " supybot.plugins.Anonymous.allowPrivateTarget è impostato a True.\n" " " #: plugin.py:94 #, docstring msgid "" " \n" "\n" " Performs in .\n" " " msgstr "" " \n" "\n" " Esegue in .\n" " " limnoria-2020.03.17/plugins/Anonymous/plugin.py0000644000175000017500000001303413634634532020641 0ustar valval00000000000000### # Copyright (c) 2005, Daniel DiPaolo # Copyright (c) 2014, James McCoy # 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.ircdb as ircdb import supybot.utils as utils from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.callbacks as callbacks import supybot.ircutils as ircutils from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Anonymous') class Anonymous(callbacks.Plugin): """This plugin allows users to act through the bot anonymously. The 'do' command has the bot perform an anonymous action in a given channel, and the 'say' command allows other people to speak through the bot. Since this can be fairly well abused, you might want to set supybot.plugins.Anonymous.requireCapability so only users with that capability can use this plugin. For extra security, you can require that the user be *in* the channel they are trying to address anonymously with supybot.plugins.Anonymous.requirePresenceInChannel, or you can require that the user be registered by setting supybot.plugins.Anonymous.requireRegistration. """ def _preCheck(self, irc, msg, target, action): if self.registryValue('requireRegistration', target, irc.network): try: foo = ircdb.users.getUser(msg.prefix) except KeyError: irc.errorNotRegistered(Raise=True) capability = self.registryValue('requireCapability', target, irc.network) if capability: if not ircdb.checkCapability(msg.prefix, capability): irc.errorNoCapability(capability, Raise=True) if action != 'tell': require_presence = self.registryValue('requirePresenceInChannel', target, irc.network) if require_presence and \ msg.nick not in irc.state.channels[target].users: irc.error(format(_('You must be in %s to %q in there.'), target, action), Raise=True) c = ircdb.channels.getChannel(target) if c.lobotomized: irc.error(format(_('I\'m lobotomized in %s.'), target), Raise=True) if not c._checkCapability(self.name()): irc.error(_('That channel has set its capabilities so as to ' 'disallow the use of this plugin.'), Raise=True) elif not self.registryValue('allowPrivateTarget'): irc.error(_('This command is disabled (supybot.plugins.Anonymous.' 'allowPrivateTarget is False).'), Raise=True) @internationalizeDocstring def say(self, irc, msg, args, target, text): """ Sends to . """ self._preCheck(irc, msg, target, 'say') self.log.info('Saying %q in %s due to %s.', text, target, msg.prefix) irc.queueMsg(ircmsgs.privmsg(target, text)) irc.noReply() say = wrap(say, ['inChannel', 'text']) def tell(self, irc, msg, args, target, text): """ Sends to . Can only be used if supybot.plugins.Anonymous.allowPrivateTarget is True. """ self._preCheck(irc, msg, target, 'tell') self.log.info('Telling %q to %s due to %s.', text, target, msg.prefix) irc.queueMsg(ircmsgs.privmsg(target, text)) irc.noReply() tell = wrap(tell, ['nick', 'text']) @internationalizeDocstring def do(self, irc, msg, args, channel, text): """ Performs in . """ self._preCheck(irc, msg, channel, 'do') self.log.info('Performing %q in %s due to %s.', text, channel, msg.prefix) irc.reply(text, action=True, to=channel) do = wrap(do, ['inChannel', 'text']) Anonymous = internationalizeDocstring(Anonymous) Class = Anonymous # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Anonymous/test.py0000644000175000017500000000704413634634532020326 0ustar valval00000000000000### # Copyright (c) 2005, Daniel DiPaolo # Copyright (c) 2014, James McCoy # 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 AnonymousTestCase(ChannelPluginTestCase): plugins = ('Anonymous',) def testSay(self): self.assertError('anonymous say %s I love you!' % self.channel) origreg = conf.supybot.plugins.Anonymous.requireRegistration() try: conf.supybot.plugins.Anonymous.requireRegistration.setValue(False) m = self.assertNotError('anonymous say %s foo!' % self.channel) self.assertTrue(m.args[1] == 'foo!') finally: conf.supybot.plugins.Anonymous.requireRegistration.setValue(origreg) def testTell(self): self.assertError('anonymous tell %s I love you!' % self.nick) origreg = conf.supybot.plugins.Anonymous.requireRegistration() origpriv = conf.supybot.plugins.Anonymous.allowPrivateTarget() try: conf.supybot.plugins.Anonymous.requireRegistration.setValue(False) self.assertError('anonymous tell %s foo!' % self.channel) conf.supybot.plugins.Anonymous.allowPrivateTarget.setValue(True) m = self.assertNotError('anonymous tell %s foo!' % self.nick) self.assertTrue(m.args[1] == 'foo!') finally: conf.supybot.plugins.Anonymous.requireRegistration.setValue(origreg) conf.supybot.plugins.Anonymous.allowPrivateTarget.setValue(origpriv) def testAction(self): m = self.assertError('anonymous do %s loves you!' % self.channel) try: orig = conf.supybot.plugins.Anonymous.requireRegistration() conf.supybot.plugins.Anonymous.requireRegistration.setValue(False) m = self.assertNotError('anonymous do %s loves you!'%self.channel) self.assertEqual(m.args, ircmsgs.action(self.channel, 'loves you!').args) finally: conf.supybot.plugins.Anonymous.requireRegistration.setValue(orig) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/AutoMode/0000755000175000017500000000000013634634547016523 5ustar valval00000000000000limnoria-2020.03.17/plugins/AutoMode/__init__.py0000644000175000017500000000513513634634532020632 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Automatically ops, voices, or halfops, or bans people when they join a channel, according to their capabilities. If you want your bot automatically op users when they join your channel, this is the plugin to load. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/AutoMode/config.py0000644000175000017500000001057513634634532020344 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('AutoMode') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('AutoMode', True) AutoMode = conf.registerPlugin('AutoMode') conf.registerChannelValue(AutoMode, 'enable', registry.Boolean(True, _("""Determines whether this plugin is enabled. """))) conf.registerGlobalValue(AutoMode, 'owner', registry.Boolean(False, _("""Determines whether this plugin will automode owners even if they don't have op/halfop/voice/whatever capability."""))) conf.registerChannelValue(AutoMode, 'alternativeCapabilities', registry.Boolean(True, _("""Determines whether the bot will check for 'alternative capabilities' (ie. autoop, autohalfop, autovoice) in addition to/instead of classic ones."""))) conf.registerChannelValue(AutoMode, 'fallthrough', registry.Boolean(True, _("""Determines whether the bot will "fall through" to halfop/voicing when auto-opping is turned off but auto-halfopping/voicing are turned on."""))) conf.registerChannelValue(AutoMode, 'op', registry.Boolean(False, _("""Determines whether the bot will automatically op people with the ,op capability when they join the channel. """))) conf.registerChannelValue(AutoMode, 'halfop', registry.Boolean(False, _("""Determines whether the bot will automatically halfop people with the ,halfop capability when they join the channel."""))) conf.registerChannelValue(AutoMode, 'voice', registry.Boolean(False, _("""Determines whether the bot will automatically voice people with the ,voice capability when they join the channel."""))) conf.registerChannelValue(AutoMode, 'ban', registry.Boolean(True, _("""Determines whether the bot will automatically ban people who join the channel and are on the banlist."""))) conf.registerChannelValue(AutoMode.ban, 'period', registry.PositiveInteger(86400, _("""Determines how many seconds the bot will automatically ban a person when banning."""))) conf.registerChannelValue(AutoMode, 'delay', registry.Integer(0, _("""Determines how many seconds the bot will wait before applying a mode. Has no effect on bans."""))) conf.registerChannelValue(AutoMode, 'extra', registry.SpaceSeparatedListOfStrings([], _("""Extra modes that will be applied to a user. Example syntax: user1+o-v user2+v user3-v"""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/AutoMode/locales/0000755000175000017500000000000013634634547020145 5ustar valval00000000000000limnoria-2020.03.17/plugins/AutoMode/locales/de.po0000644000175000017500000000543113634634532021072 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2012-04-19 21:37+CEST\n" "PO-Revision-Date: 2012-04-27 15:38+0200\n" "Last-Translator: Mikaela Suomalainen \n" "Language-Team: German \n" "Language: de\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: German\n" "X-Poedit-Country: GERMANY\n" #: config.py:46 msgid "" "Determines whether this plugin is enabled.\n" " " msgstr "Legt fest ob das Plugin aktiv ist." #: config.py:49 msgid "" "Determines whether this plugin will automode\n" " owners even if they don't have op/halfop/voice/whatever capability." msgstr "Legt fest ob das Plugin Besitzern automatisch den Modus zuweist, obwohl sie nicht die op/halfop/voice/wasauchimmer Fähigkeit besitzen." #: config.py:52 #, fuzzy msgid "" "Determines whether the bot will \"fall\n" " through\" to halfop/voicing when auto-opping is turned off but\n" " auto-halfopping/voicing are turned on." msgstr "Legt fest ob der Bot \"zurückfällt\" auf Halboperator/Sprechrechte wenn automatischer Operator abgeschaltet ist, aber automatische Halboperator/Sprecherechte angeschaltet sind." #: config.py:56 msgid "" "Determines whether the bot will automatically\n" " op people with the ,op capability when they join the channel.\n" " " msgstr "Legt fest ob der Bot Personen, mit der ,op Fähigkeit, automatisch Operatorrechte zuweisen soll, wenn sie den Kanal betreten." #: config.py:60 msgid "" "Determines whether the bot will automatically\n" " halfop people with the ,halfop capability when they join the\n" " channel." msgstr "Legt fest ob der Bot Personen, mit der ,halfop Fähigkeit, automatisch Halboperatorrechte zuweisen soll, wenn sie den Kanal betreten." #: config.py:64 msgid "" "Determines whether the bot will automatically\n" " voice people with the ,voice capability when they join the\n" " channel." msgstr "Legt fest ob der Bot Personen, mit der ,voice Fähigkeit, automatisch Sprechrechte zuweisen soll, wenn sie den Kanal betreten." #: config.py:68 msgid "" "Determines whether the bot will automatically\n" " ban people who join the channel and are on the banlist." msgstr "Legt fest ob der Bot automatisch Personen bannen soll die auf der Banliste stehen." #: config.py:71 msgid "" "Determines how many seconds the bot\n" " will automatically ban a person when banning." msgstr "Legt fest wieviele Sekunden der Bot Personen automatisch bannt." #: config.py:75 #, fuzzy msgid "" "Determines how many seconds the bot will wait\n" " before applying a mode. Has no effect on bans." msgstr "Legt fest wieviele Sekunden der Bot Personen automatisch bannt." limnoria-2020.03.17/plugins/AutoMode/locales/es.po0000644000175000017500000001010613634634532021104 0ustar valval00000000000000# Spanish translation for Limnoria # Copyright (c) 2015 Limnoria 2015 # This file is distributed under the same license as the Limnoria package. # Aaron Farias , 2015. # msgid "" msgstr "" "Project-Id-Version: limnoria\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2014-12-24 15:43+0000\n" "PO-Revision-Date: 2015-01-02 18:47+0000\n" "Last-Translator: Aaron Farias \n" "Language-Team: Spanish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-01-02 18:48+0000\n" "X-Generator: Launchpad (build 17286)\n" #: config.py:46 msgid "" "Determines whether this plugin is enabled.\n" " " msgstr "" "Determina si este plugin está habilitado.\n" " " #: config.py:49 msgid "" "Determines whether this plugin will automode\n" " owners even if they don't have op/halfop/voice/whatever capability." msgstr "" "Determina si este plugin AutoMode\n" "propietarios incluso si no tienen lo que sea la capacidad op/halfop/voz." #: config.py:52 msgid "" "Determines whether the bot will\n" " check for 'alternative capabilities' (ie. autoop, autohalfop,\n" " autovoice) in addition to/instead of classic ones." msgstr "" "Determina si el bot\n" "comprobar 'capacidades alternativas \"(es decir. autoop, autohalfop,\n" "autovoz) además de/en lugar de los clásicos." #: config.py:56 msgid "" "Determines whether the bot will \"fall\n" " through\" to halfop/voicing when auto-opping is turned off but\n" " auto-halfopping/voicing are turned on." msgstr "" "Determina si el bot \"caerá\n" "a través \"de HALFOP / sonoridad cuando auto-Op está apagado pero\n" "auto-halfop a si mismos / sonido están activados." #: config.py:60 msgid "" "Determines whether the bot will automatically\n" " op people with the ,op capability when they join the channel.\n" " " msgstr "" "Determina si el bot automáticamente\n" "dar op con el , capacidad de op cuando se unen al canal.\n" " " #: config.py:64 msgid "" "Determines whether the bot will automatically\n" " halfop people with the ,halfop capability when they join the\n" " channel." msgstr "" "Determina si el bot automáticamente\n" "personas HALFOP con el , capacidad halfop cuando se unen a la\n" "canal." #: config.py:68 msgid "" "Determines whether the bot will automatically\n" " voice people with the ,voice capability when they join the\n" " channel." msgstr "" "Determina si el bot automáticamente\n" "gente de voz con el , capacidad de voz cuando se unen a la\n" "canal." #: config.py:72 msgid "" "Determines whether the bot will automatically\n" " ban people who join the channel and are on the banlist." msgstr "" "Determina si el bot automáticamente\n" "prohibir a la gente que se unen al canal y están en la lista de ban." #: config.py:75 msgid "" "Determines how many seconds the bot\n" " will automatically ban a person when banning." msgstr "" "Determina cuántos segundos el bot\n" "prohibirá automáticamente a una persona cuando la prohibición." #: config.py:79 msgid "" "Determines how many seconds the bot will wait\n" " before applying a mode. Has no effect on bans." msgstr "" "Determina cuántos segundos esperará el bot\n" "antes de aplicar un modo. No tiene ningún efecto sobre las prohibiciones." #: config.py:83 msgid "" "Extra modes that will be\n" " applied to a user. Example syntax: user1+o-v user2+v user3-v" msgstr "" "Modos adicionales que serán\n" " se aplica a un usuario. Ejemplo de sintaxis: usuario1 + o-v + v usuario2 " "usuario3-v" #: plugin.py:48 #, docstring msgid "" "This plugin, when configured, allows the bot to automatically set modes\n" " on users when they join." msgstr "" "Este plugin, cuando se configura, permite al bot para establecer " "automáticamente los modos\n" "en los usuarios cuando se unen." #: plugin.py:80 #, docstring msgid "" "Determines whether or not a mode has already\n" " been applied." msgstr "" "Determina si un modo ya tiene o \n" "ha aplicado." limnoria-2020.03.17/plugins/AutoMode/locales/fi.po0000644000175000017500000001013413634634532021074 0ustar valval00000000000000# AutoMode plugin in Limnoria. # Copyright (C) 2011, 2012 Limnoria # Mikaela Suomalainen , 2011, 2012. # msgid "" msgstr "" "Project-Id-Version: Supybot AutoMode\n" "POT-Creation-Date: 2014-12-20 11:29+EET\n" "PO-Revision-Date: 2014-12-20 11:38+0200\n" "Last-Translator: Mikaela Suomalainen \n" "Language-Team: suomi <>\n" "Language: fi_FI\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: \n" "X-Generator: Poedit 1.6.10\n" #: config.py:46 msgid "" "Determines whether this plugin is enabled.\n" " " msgstr "Määrittää onko tämä lisäosa käytössä." #: config.py:49 msgid "" "Determines whether this plugin will automode\n" " owners even if they don't have op/halfop/voice/whatever capability." msgstr "" "Määrittää vaihtaako botti omistajien tilaa automaattisesti, vaikka heillä ei " "olisi\n" " op/halfop/voice/ihansama valtuutta." #: config.py:52 msgid "" "Determines whether the bot will\n" " check for 'alternative capabilities' (ie. autoop, autohalfop,\n" " autovoice) in addition to/instead of classic ones." msgstr "" "Määrittää tarkistaako botti 'vaihtoehtoisten valtuuksien' varalta (esim. " "autoop,\n" " autohalfop, autovoice) klassisten lisäksi/sijaan." #: config.py:56 msgid "" "Determines whether the bot will \"fall\n" " through\" to halfop/voicing when auto-opping is turned off but\n" " auto-halfopping/voicing are turned on." msgstr "" "Määrittää \"alentuuko\" botti halfoppaamiseen/voicen antamiseen kun " "automaattinen\n" " oprtaattori aseman antaminen on poistettu käytöstä, mutta automaattinen\n" " puolioperaattori aseman/äänen antaminen on käytössä." #: config.py:60 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, joilla on #,op valtuus automaattisesti, kun he liittyvät " "kanavalle." #: config.py:64 msgid "" "Determines whether the bot will automatically\n" " halfop people with the ,halfop capability when they join the\n" " channel." msgstr "" "Määrittää antaako botti puolioperaattorin aseman automaattisesti, kun " "ihmiset, joilla on #,halfop valtuus liittyvät kanavalle." #: config.py:68 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 äänen ihmisille, joilla on #," "voice\n" " valtuus heidän liittyessään kanavalle." #: config.py:72 msgid "" "Determines whether the bot will automatically\n" " ban people who join the channel and are on the banlist." msgstr "" "Määrittää antaako botti porttikiellon ihmisille,\n" " jotka liittyvät kanavalle ja ovat porttikieltolistalla." #: config.py:75 msgid "" "Determines how many seconds the bot\n" " will automatically ban a person when banning." msgstr "" "Määrittää kuinka moneksi sekuntiksi botti antaa porttikiellon henkilöle, " "antaessaan\n" " porttikieltoa automaattisesti." #: config.py:79 msgid "" "Determines how many seconds the bot will wait\n" " before applying a mode. Has no effect on bans." msgstr "" "Määrittää kuinka monta sekuntia botti odottaa ennen, kuin asettaa tilan. " "Tällä ei ole\n" " vaikutusta porttikieltoihin." #: config.py:83 msgid "" "Extra modes that will be\n" " applied to a user. Example syntax: user1+o-v user2+v user3-v" msgstr "" "Ylimääräiset tilat, jotka asetetaan käyttäjään.\n" " Esimerkki syntaksi: käyttäjä1+o-v käyttäjä2+v käyttäjä3-v" #: plugin.py:48 #, fuzzy msgid "" "This plugin, when configured, allows the bot to automatically set modes\n" " on users when they join." msgstr "" "Määritettynä tämä plugin voi asettaa tiloja kanavalle liittyjiin " "automaattisesti." #: plugin.py:80 msgid "" "Determines whether or not a mode has already\n" " been applied." msgstr "Määrittää asettaako botti tilan vai eikö, jos tila se on jo asetettu." limnoria-2020.03.17/plugins/AutoMode/locales/fr.po0000644000175000017500000000707413634634532021116 0ustar valval00000000000000# Valentin Lorentz , 2012. msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-01-21 19:30+CET\n" "PO-Revision-Date: 2014-01-21 19:33+0100\n" "Last-Translator: \n" "Language-Team: French \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: config.py:46 msgid "" "Determines whether this plugin is enabled.\n" " " msgstr "Détermine si ce plugin est activé." #: config.py:49 msgid "" "Determines whether this plugin will automode\n" " owners even if they don't have op/halfop/voice/whatever capability." msgstr "" "Détermine si ce plugin mettra des modes automatiques sur les owners, même si " "ils n'ont pas la capacité op/halfop/voice/..." #: config.py:52 msgid "" "Determines whether the bot will\n" " check for 'alternative capabilities' (ie. autoop, autohalfop,\n" " autovoice) in addition to/instead of classic ones." msgstr "" "Détermine si le bot vérifiera les « capacités alternatives » (ie. autoop, " "autohalfop, autovoice) en plus/à la place des capacités classiques." #: config.py:56 msgid "" "Determines whether the bot will \"fall\n" " through\" to halfop/voicing when auto-opping is turned off but\n" " auto-halfopping/voicing are turned on." msgstr "" "Détermine si le bot ne halfopera/voicera pas lorsque l'auto-op est " "désactivé, même si l'auto-halfopvoice est activé." #: config.py:60 msgid "" "Determines whether the bot will automatically\n" " op people with the ,op capability when they join the channel.\n" " " msgstr "" "Détermine si le bot opera automatiquement les gens qui ont la capacité " ",op lorsqu'ils rejoignent le canal." #: config.py:64 msgid "" "Determines whether the bot will automatically\n" " halfop people with the ,halfop capability when they join the\n" " channel." msgstr "" "Détermine si le bot halfopera les gens qui ont la capacité ,halfop " "lorsqu'ils rejoignent le canal." #: config.py:68 msgid "" "Determines whether the bot will automatically\n" " voice people with the ,voice capability when they join the\n" " channel." msgstr "" "Détermine si le bot voicera automatiquement les gens avec la capacité " ",voice lorsqu'ils rejoingent le canal." #: config.py:72 msgid "" "Determines whether the bot will automatically\n" " ban people who join the channel and are on the banlist." msgstr "" "Détermine si le bot bannira automatiquement les personnes qui rejoignent le " "canal et qui sont sur la liste de bannissement." #: config.py:75 msgid "" "Determines how many seconds the bot\n" " will automatically ban a person when banning." msgstr "" "Détermine combien de secondes durera le bannissement que le bot posera sur " "une personne." #: config.py:79 msgid "" "Determines how many seconds the bot will wait\n" " before applying a mode. Has no effect on bans." msgstr "" "Détermine combien de secondes le bot attendra avant d'appliquer un mode. " "Cela n'a aucun effet sur les bannissements." #: config.py:83 msgid "" "Extra modes that will be\n" " applied to a user. Example syntax: user1+o-v user2+v user3-v" msgstr "" "Des modes supplémentaires à appliquer à un utilisateur. Par exemple : " "user1+o-v user2+v user3-v" #: plugin.py:78 msgid "" "Determines whether or not a mode has already\n" " been applied." msgstr "Détermine si un mode a déjà été appliqué ou non." limnoria-2020.03.17/plugins/AutoMode/locales/it.po0000644000175000017500000000537413634634532021124 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2012-04-23 19:29+0200\n" "Last-Translator: skizzhg \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:46 msgid "" "Determines whether this plugin is enabled.\n" " " msgstr "" "Determina se il plugin è abilitato.\n" " " #: config.py:49 msgid "" "Determines whether this plugin will automode\n" " owners even if they don't have op/halfop/voice/whatever capability." msgstr "" "Determina se il plugin darà automaticamente il mode ai proprietari.\n" " anche se non hanno la capacità op, halfop, voice, ecc..." #: config.py:52 msgid "" "Determines whether the bot will \"fall\n" " through\" to halfop/voicing when auto-opping is turned off but\n" " auto-halfopping/voicing are turned on." msgstr "" "Determina se il the bot non darà l'halfop o il voice\n" " quando l'op automatico è disabilitato ma l'halfop o il voice\n" " automatico sono attivati." #: config.py:56 msgid "" "Determines whether the bot will automatically\n" " op people with the ,op capability when they join the channel.\n" " " msgstr "" "Determina se il bot darà automaticamente l'op agli utenti che hanno\n" " la capacità ,op quando entrano in canale.\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 "" "Determina se il bot darà automaticamente l'halfop agli utenti che hanno\n" " la capacità ,halfop quando entrano in canale.\n" " " #: config.py:64 msgid "" "Determines whether the bot will automatically\n" " voice people with the ,voice capability when they join the\n" " channel." msgstr "" "Determina se il bot darà automaticamente il voice agli utenti che hanno\n" " la capacità ,voice quando entrano in canale.\n" " " #: config.py:68 msgid "" "Determines whether the bot will automatically\n" " ban people who join the channel and are on the banlist." msgstr "" "Determina se il bot bannerà automaticamente gli utenti che\n" " entrano in canale e sono nella lista dei ban.\n" " " #: config.py:71 msgid "" "Determines how many seconds the bot\n" " will automatically ban a person when banning." msgstr "" "Determina quanti secondi durerà il ban applicato a un utente." #: config.py:75 msgid "" "Determines how many seconds the bot will wait\n" " before applying a mode. Has no effect on bans." msgstr "" "Determina quanti secondi aspetterà il bot prima di applicare un mode.\n" " Non ha effetto sui ban." limnoria-2020.03.17/plugins/AutoMode/plugin.py0000644000175000017500000001622413634634532020372 0ustar valval00000000000000### # Copyright (c) 2004, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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 re import time import supybot.log as log import supybot.conf as conf import supybot.ircdb as ircdb import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.schedule as schedule import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('AutoMode') class Continue(Exception): pass # Used below, look in the "do" function nested in doJoin. class AutoMode(callbacks.Plugin): """This plugin, when configured, allows the bot to automatically set modes on users when they join.""" def doJoin(self, irc, msg): channel = msg.channel network = irc.network if ircutils.strEqual(irc.nick, msg.nick): return if not self.registryValue('enable', channel, network): return fallthrough = self.registryValue('fallthrough', channel, network) def do(type): cap = ircdb.makeChannelCapability(channel, type) cap_auto = ircdb.makeChannelCapability(channel, 'auto'+type) try: apply_mode = ircdb.checkCapability(msg.prefix, cap, ignoreOwner=not self.registryValue('owner'), ignoreChannelOp=True, ignoreDefaultAllow=True) except KeyError: apply_mode = False if self.registryValue('alternativeCapabilities', channel, network): try: override = ircdb.checkCapability(msg.prefix, cap_auto, ignoreOwner=not self.registryValue('owner'), ignoreChannelOp=True, ignoreDefaultAllow=True) except KeyError: override = False else: override = False if apply_mode or override: if override or self.registryValue(type, channel, network): self.log.info('Scheduling auto-%s of %s in %s @ %s.', type, msg.prefix, channel, network) def dismiss(): """Determines whether or not a mode has already been applied.""" l = getattr(irc.state.channels[channel], type+'s') return (msg.nick in l) msgmaker = getattr(ircmsgs, type) schedule_msg(msgmaker(channel, msg.nick), dismiss) raise Continue # Even if fallthrough, let's only do one. elif not fallthrough: self.log.debug('%s has %s, but supybot.plugins.AutoMode.%s' ' is not enabled in %s @ %s, refusing to ' 'fall through.', msg.prefix, cap, type, channel, network) raise Continue def schedule_msg(msg, dismiss): def f(): if not dismiss(): irc.queueMsg(msg) else: self.log.info('Dismissing auto-mode for %s.', msg.args[2]) delay = self.registryValue('delay', channel, network) if delay: schedule.addEvent(f, time.time() + delay) else: f() def extra_modes(): try: user = ircdb.users.getUser(ircdb.users.getUserId(msg.prefix)) except KeyError: return pattern = re.compile(r'-|\+') for item in self.registryValue('extra', channel, network): try: username, modes = pattern.split(item, maxsplit=1) modes = item[len(username)] + modes except ValueError: # No - or + in item log.error(('%r is not a valid item for ' 'supybot.plugins.AutoMode.extra') % item) continue if username != user.name: continue else: self.log.info('Scheduling auto-modes %s of %s in %s @ %s.', modes, msg.prefix, channel, network) modes = [modes] + \ ([msg.nick]*len(pattern.sub('', modes))) schedule_msg(ircmsgs.mode(channel, modes), lambda :False) break try: do('op') if 'h' in irc.state.supported['prefix']: do('halfop') except Continue: return finally: extra_modes() c = ircdb.channels.getChannel(channel) if c.checkBan(msg.prefix) and self.registryValue('ban', channel, network): period = self.registryValue('ban.period', channel, network) if period: def unban(): try: if msg.prefix in irc.state.channels[channel].bans: irc.queueMsg(ircmsgs.unban(channel, msg.prefix)) except KeyError: # We're not in the channel anymore. pass schedule.addEvent(unban, time.time()+period) banmask =conf.supybot.protocols.irc.banmask.makeBanmask(msg.prefix) irc.queueMsg(ircmsgs.ban(channel, banmask)) irc.queueMsg(ircmsgs.kick(channel, msg.nick)) try: do('voice') except Continue: return Class = AutoMode # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/AutoMode/test.py0000644000175000017500000000331713634634532020052 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * class AutoModeTestCase(PluginTestCase): plugins = ('AutoMode',) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/BadWords/0000755000175000017500000000000013634634547016513 5ustar valval00000000000000limnoria-2020.03.17/plugins/BadWords/__init__.py0000644000175000017500000000473313634634532020625 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Filters bad words on outgoing messages from the bot, so the bot can't be made to say bad words. """ 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__ = "" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/BadWords/config.py0000644000175000017500000001244713634634532020334 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from __future__ import division import time import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('BadWords') def configure(advanced): from supybot.questions import output, expect, anything, something, yn conf.registerPlugin('BadWords', True) if yn(_('Would you like to add some bad words?')): words = anything(_('What words? (separate individual words by ' 'spaces)')) conf.supybot.plugins.BadWords.words.set(words) class LastModifiedSetOfStrings(registry.SpaceSeparatedSetOfStrings): lastModified = 0 def setValue(self, v): self.lastModified = time.time() registry.SpaceSeparatedListOfStrings.setValue(self, v) BadWords = conf.registerPlugin('BadWords') conf.registerGlobalValue(BadWords, 'words', LastModifiedSetOfStrings([], _("""Determines what words are considered to be 'bad' so the bot won't say them."""))) conf.registerChannelValue(BadWords,'requireWordBoundaries', registry.Boolean(False, _("""Determines whether the bot will require bad words to be independent words, or whether it will censor them within other words. For instance, if 'darn' is a bad word, then if this is true, 'darn' will be censored, but 'darnit' will not. You probably want this to be false. After changing this setting, the BadWords regexp needs to be regenerated by adding/removing a word to the list, or reloading the plugin."""))) class String256(registry.String): def __call__(self): s = registry.String.__call__(self) return s * (1024//len(s)) def __str__(self): return self.value conf.registerGlobalValue(BadWords, 'nastyChars', String256('!@#&', _("""Determines what characters will replace bad words; a chunk of these characters matching the size of the replaced bad word will be used to replace the bad words you've configured."""))) class ReplacementMethods(registry.OnlySomeStrings): validStrings = ('simple', 'nastyCharacters') conf.registerGlobalValue(BadWords, 'replaceMethod', ReplacementMethods('nastyCharacters', _("""Determines the manner in which bad words will be replaced. 'nastyCharacters' (the default) will replace a bad word with the same number of 'nasty characters' (like those used in comic books; configurable by supybot.plugins.BadWords.nastyChars). 'simple' will replace a bad word with a simple strings (regardless of the length of the bad word); this string is configurable via supybot.plugins.BadWords.simpleReplacement."""))) conf.registerGlobalValue(BadWords,'simpleReplacement', registry.String('[CENSORED]', _("""Determines what word will replace bad words if the replacement method is 'simple'."""))) conf.registerGlobalValue(BadWords, 'stripFormatting', registry.Boolean(True, _("""Determines whether the bot will strip formatting characters from messages before it checks them for bad words. If this is False, it will be relatively trivial to circumvent this plugin's filtering. If it's True, however, it will interact poorly with other plugins that do coloring or bolding of text."""))) conf.registerChannelValue(BadWords, 'kick', registry.Boolean(False, _("""Determines whether the bot will kick people with a warning when they use bad words."""))) conf.registerChannelValue(BadWords.kick, 'message', registry.NormalizedString(_("""You have been kicked for using a word prohibited in the presence of this bot. Please use more appropriate language in the future."""), _("""Determines the kick message used by the bot when kicking users for saying bad words."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/BadWords/locales/0000755000175000017500000000000013634634547020135 5ustar valval00000000000000limnoria-2020.03.17/plugins/BadWords/locales/es.po0000644000175000017500000001474513634634532021111 0ustar valval00000000000000# Spanish translation for Limnoria # Copyright (c) 2015 Limnoria Contributors 2015 # This file is distributed under the same license as the Limnoria package. # Aaron Farias , 2015. # msgid "" msgstr "" "Project-Id-Version: limnoria\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2014-03-26 09:31+0000\n" "PO-Revision-Date: 2015-01-02 19:15+0000\n" "Last-Translator: Aaron Farias \n" "Language-Team: Spanish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-01-09 18:01+0000\n" "X-Generator: Launchpad (build 17298)\n" #: config.py:42 msgid "Would you like to add some bad words?" msgstr "¿Te gustaría añadir alguna mala palabra?" #: config.py:43 msgid "What words? (separate individual words by spaces)" msgstr "¿Qué palabras? (palabras individuales separadas por espacios)" #: config.py:55 msgid "" "Determines what words are\n" " considered to be 'bad' so the bot won't say them." msgstr "" "Determina qué palabras son\n" "considera que es \"malo\" por lo que el bot no decirlas." #: config.py:58 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 "" "Determina si el bot requerirá mal\n" "palabras sean palabras independientes, o si se van a censurar dentro de " "otra\n" "palabras. Por ejemplo, si 'maldito' es una mala palabra, entonces si esto es " "cierto, 'maldito'\n" "serán censurados, pero 'darnit' no lo hará. Es probable que esta sea\n" "falsa. Después de cambiar este ajuste, los badwords expresión regular tiene " "que ser\n" "regenerado por añadir/eliminar una palabra a la lista, o volver a cargar el\n" "plugin." #: config.py:75 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 "" "Determina qué caracteres reemplazarán malas palabras; un\n" "parte de estos caracteres que coincida con el tamaño de la mala palabra " "reemplazado voluntad\n" "usarse para reemplazar las malas palabras que ha configurado." #: config.py:83 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 "" "Determina la manera en que\n" "serán reemplazados malas palabras. 'NastyCharacters' (por defecto), " "sustituirá a un\n" "mala palabra con el mismo número de 'personajes desagradables' (como los " "utilizados en\n" "cómics; configurable por supybot.plugins.BadWords.nastyChars).\n" "\"Simple\" reemplazará una mala palabra con un simple cuerdas " "(independientemente de la\n" "longitud de la mala palabra); esta cadena es configurable a través de\n" "supybot.plugins.BadWords.simpleReplacement." #: config.py:91 msgid "" "Determines what word will replace bad\n" " words if the replacement method is 'simple'." msgstr "" "Determina qué palabra reemplazará mal\n" "es decir, si el método de sustitución es \"simple\"." #: config.py:94 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 "" "Determina si el bot se tira\n" "caracteres de formato de los mensajes antes de que se les comprueba malas " "palabras.\n" "Si esto es falso, será relativamente trivial para eludir de este plugin\n" "filtrado. Si es cierto, sin embargo, va a interactuar mal con otro\n" "plugins que hacen coloración o la negrita del texto." #: config.py:101 msgid "" "Determines whether the bot will kick people with\n" " a warning when they use bad words." msgstr "" "Determina si el bot se iniciará a las personas con\n" "una advertencia cuando use malas palabras." #: config.py:104 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 "" "Has sido expulsado por usar una palabra\n" "prohibido en la presencia de este robot. Por favor, use más apropiado\n" "idioma en el futuro." #: config.py:106 msgid "" "Determines the kick message used by the\n" " bot when kicking users for saying bad words." msgstr "" "Determina el mensaje patada utilizado por el\n" "bot cuando patadas usuarios por decir malas palabras." #: plugin.py:46 #, docstring 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 "" "Mantiene una lista de palabras que el bot no está permitido decir.\n" "También se puede utilizar para echar a la gente que dicen estas palabras, si " "el bot\n" "tiene op." #: plugin.py:115 #, docstring msgid "" "takes no arguments\n" "\n" " Returns the list of words being censored.\n" " " msgstr "" "no tiene argumentos\n" "\n" "Devuelve la lista de palabras que se censuró.\n" " " #: plugin.py:125 msgid "I'm not currently censoring any bad words." msgstr "No estoy actualmente censurando las malas palabras." #: plugin.py:130 #, docstring msgid "" " [ ...]\n" "\n" " Adds all s to the list of words being censored.\n" " " msgstr "" " [ ...]\n" "\n" "Añade todos s a la lista de palabras que se censuró.\n" " " #: plugin.py:142 #, docstring msgid "" " [ ...]\n" "\n" " Removes s from the list of words being censored.\n" " " msgstr "" " [ ...]\n" "\n" "Elimina s de la lista de palabras que se censuró.\n" " " limnoria-2020.03.17/plugins/BadWords/locales/fi.po0000644000175000017500000001504613634634532021073 0ustar valval00000000000000# BadWords plugin in Limnoria. # Copyright (C) 2011 Limnoria # Mikaela Suomalainen , 2011. # msgid "" msgstr "" "Project-Id-Version: Supybot BadWords\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: \n" "Last-Translator: Mikaela 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: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ä sanat? (Rajoita 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 asetus on true, 'pah'\n" " sensuroidaan, mutta 'pahus' ei sensuroida. Sinä luultavasti tahdot pitää tämän\n" " false:na. Tämän asetuksen muuttamisen jälkeen, BadWords säännöllinen lauseke täytyy\n" " luoda uudelleen lisäämällä/poistamalla sana listalta, tai lataamalla lisäosa \n" " uudelleen." #: 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" " osia näistä merkeistä, jotka sopivat pahan sanan 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 merkkiketjulla (riippumatta\n" " pahan sanan koosta); tämä merkkiketju 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 opit." #: 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 "En ole sensuroimassa yhtään pahaa sanaa juuri nyt." #: plugin.py:128 msgid "" " [ ...]\n" "\n" " Adds all s to the list of words being censored.\n" " " msgstr "" " [ ...]\n" "\n" " Lisää kaikki (t) sensuroitaviin sanoihin.\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" " " #~ 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" limnoria-2020.03.17/plugins/BadWords/locales/fr.po0000644000175000017500000001337513634634532021107 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Limnoria \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" #: config.py:40 msgid "Would you like to add some bad words?" msgstr "Voulez-vous ajouter quelques mots interdits ?" #: config.py:41 msgid "What words? (separate individual words by spaces)" msgstr "Quels mots ? (séparez chaque mot par un espace)" #: config.py:53 msgid "" "Determines what words are\n" " considered to be 'bad' so the bot won't say them." msgstr "Détermine quels mots sont considérés comme interdits, donc le bot ne les dira pas." #: 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 "Détermine si le bot requiert que les gros mots soient indépendants, ou si ils peuvent être dans d'autres mots. Par exemple, si 'merde' est censuré et que c'est True, 'merde' sera censuré, mais pas 'emmerder'. Il est probablement mieux que ceci soit False. Après avoir modifié ce paramètre, l'expression régulière de BadWords doit être regénérée en ajoutant/supprimant un mot de la liste, ou en rechargeant le plugin." #: 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 "Détermine par quels caractères seront remplacés les gros mots ; un morceau de ces caractères ayant la même taille que le mot remplacé sera utilisé pour remplacer les gros mots que vous avez configurés." #: 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 "Détermine la manière dont les gros mots sont remplacés. 'nastyCharacters' (par défaut) remplacera un gros mot par le même nombre de 'caractères obscènes' (comme ceux utilisés dans les bandes dessinées ; configurables dans supybot.plugins.BadWords.nastyChars). 'simple' remplacera le mot pas une simple chaîne (peu importe la taille du gros mot) ; cette chaîne est configurable dans supybot.plugins.BadWords.simpleReplacement." #: config.py:89 msgid "" "Determines what word will replace bad\n" " words if the replacement method is 'simple'." msgstr "Détermin quel mot remplacera les mots interdits, si la méthode de remplacement est '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 "Détermine si le bot retirera les caractères de formattage avant de vérifier qu'ils contiennent des gros mots. Si ceci est False, ce sera relativement simple de contourner la protection. Si c'est True, toutefois, il fera perdre toute couleur ou formattage des autres plugins" #: config.py:99 msgid "" "Determines whether the bot will kick people with\n" " a warning when they use bad words." msgstr "Détermine si le bot kickera les gens avec un avertissement lorsqu'ils utilisent des gros mots." #: 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 "Vous avez été kické(e) parce que vous avez utilisé un mot interdit en la présence de ce bot. Veuillez utiliser un langage plus approprié par le futur." #: config.py:104 msgid "" "Determines the kick message used by the\n" " bot when kicking users for saying bad words." msgstr "Détermine le message de kick utilisé par le bot lorsqu'il kick des utilisateurs utilisant des gros mots." #: 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 "" #: plugin.py:113 msgid "" "takes no arguments\n" "\n" " Returns the list of words being censored.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne une liste de mots qui sont censurés." #: plugin.py:123 msgid "I'm not currently censoring any bad words." msgstr "Je ne censure actuellement aucun mot." #: plugin.py:128 msgid "" " [ ...]\n" "\n" " Adds all s to the list of words being censored.\n" " " msgstr "" " [ ...]\n" "\n" "Ajoute tous les mots à la liste des mots que le bot ne doit pas dire." #: plugin.py:140 msgid "" " [ ...]\n" "\n" " Removes s from the list of words being censored.\n" " " msgstr "" " [ ...]\n" "\n" "Retire les s de la liste des mots que le bot ne doit pas dire." limnoria-2020.03.17/plugins/BadWords/locales/it.po0000644000175000017500000001377713634634532021122 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-28 23:29+0200\n" "Last-Translator: skizzhg \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:40 msgid "Would you like to add some bad words?" msgstr "Vuoi aggiungere delle parole volgari?" #: config.py:41 msgid "What words? (separate individual words by spaces)" msgstr "Quali parole? (separa ciascuna con uno spazio)" #: config.py:53 msgid "" "Determines what words are\n" " considered to be 'bad' so the bot won't say them." msgstr "" "Determina quali parole sono considerate \"volgari\" per cui il bot non le pronuncerà.\n" #: 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 "" "Determina se il bot richieda che le parole volgari siano indipendenti o all'interno\n" " di altre parole. Ad esempio, se \"merda\" è volgare e questa opzione è impostata\n" " a True, verrà censurata, ma \"merdata\" non lo sarà; probabilmente si preferisce\n" " che questa sia su False. Dopo aver modificato questa impostazione, la regexp di\n" " BadWords deve essere rigenerata con l'aggiunta o la rimozione di una parola\n" " dall'elenco o ricaricando il plugin." #: 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 "" "Determina quali caratteri sostituiranno le parole volgari, verranno utilizzati\n" " rimpiazzando la parola da censurare in tutta la sua lunghezza." #: 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 "" "Determina come verranno sostituite le parole volgari. \"nastyCharacters\" (predefinito)\n" " rimpiazzerà la parola con lo stesso numero di \"brutti caratteri\" (come quelli usati\n" " nei fumetti; configurabile da supybot.plugins.BadWords.nastyChars). \"simple\" sostituirà\n" " una parola volgare con una stringa semplice (indipendentemente dalla lunghezza della parola);\n" " questa stringa è configurabile tramite supybot.plugins.BadWords.simpleReplacement." #: config.py:89 msgid "" "Determines what word will replace bad\n" " words if the replacement method is 'simple'." msgstr "" "Determina quale parola sostituirà quelle volgari se viene usato il metodo \"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 "" "Determina se il bot rimuoverà i caratteri di formattazione prima di controllare\n" " che contengano parole volgari. Se impostato a False sarà relativamente facile\n" " aggirare i filtri di questo plugin; tuttavia se impostato a True non interagirà\n" " con altri plugin che colorano o rendono il testo grassetto." #: config.py:99 msgid "" "Determines whether the bot will kick people with\n" " a warning when they use bad words." msgstr "" "Determina se il bot caccerà (kick) gli utenti con un avvertimento quando usano volgarità." #: 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 "" "Sei stato/a cacciato/a per aver usato parole proibite in presenza del bot.\n" " In futuro utilizza un linguaggio più appropriato." #: config.py:104 msgid "" "Determines the kick message used by the\n" " bot when kicking users for saying bad words." msgstr "" "Determina il messaggio del kick utilizzato dal bot per espellere gli utenti che scrivono volgarità." #: plugin.py:46 #, docstring 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 "" "Mantiene un elenco di parole che il bot non può dire.\n" " Se il bot ha lo stato di operatore, può essere anche utilizzato\n" " per cacciare (kick) utenti che scrivono queste parole." #: plugin.py:113 #, docstring msgid "" "takes no arguments\n" "\n" " Returns the list of words being censored.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Riporta l'elenco delle parole censurate.\n" " " #: plugin.py:123 msgid "I'm not currently censoring any bad words." msgstr "Al momento non ho alcuna parola censurata." #: plugin.py:128 #, docstring msgid "" " [ ...]\n" "\n" " Adds all s to the list of words being censored.\n" " " msgstr "" " [ ...]\n" "\n" " Aggiunge all'elenco di quelle da censurare.\n" " " #: plugin.py:140 #, docstring msgid "" " [ ...]\n" "\n" " Removes s from the list of words being censored.\n" " " msgstr "" " [ ...]\n" "\n" " Rimuove dall'elenco di quelle da censurare.\n" " " limnoria-2020.03.17/plugins/BadWords/plugin.py0000644000175000017500000001524113634634532020360 0ustar valval00000000000000### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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 re import time import supybot.conf as conf import supybot.utils as utils import supybot.ircdb as ircdb import supybot.ircmsgs as ircmsgs from supybot.commands import * import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('BadWords') class BadWords(callbacks.Privmsg): """Maintains a list of words that the bot is not allowed to say. Can also be used to kick people that say these words, if the bot has op.""" def __init__(self, irc): self.__parent = super(BadWords, self) self.__parent.__init__(irc) # This is so we can not filter certain outgoing messages (like list, # which would be kinda useless if it were filtered). self.filtering = True self.lastModified = 0 self.words = conf.supybot.plugins.BadWords.words def callCommand(self, name, irc, msg, *args, **kwargs): if ircdb.checkCapability(msg.prefix, 'admin'): self.__parent.callCommand(name, irc, msg, *args, **kwargs) else: irc.errorNoCapability('admin') def sub(self, m): replaceMethod = self.registryValue('replaceMethod') if replaceMethod == 'simple': return self.registryValue('simpleReplacement') elif replaceMethod == 'nastyCharacters': return self.registryValue('nastyChars')[:len(m.group(1))] def inFilter(self, irc, msg): self.filtering = True # We need to check for bad words here rather than in doPrivmsg because # messages don't get to doPrivmsg if the user is ignored. if msg.command == 'PRIVMSG' and self.words(): channel = msg.channel self.updateRegexp(channel, irc.network) s = ircutils.stripFormatting(msg.args[1]) if irc.isChannel(channel) \ and self.registryValue('kick', channel, irc.network): if self.regexp.search(s): c = irc.state.channels[channel] cap = ircdb.makeChannelCapability(channel, 'op') if c.isHalfopPlus(irc.nick): if c.isHalfopPlus(msg.nick) or \ ircdb.checkCapability(msg.prefix, cap): self.log.debug("Not kicking %s from %s, because " "they are halfop+ or can't be " "kicked.", msg.nick, channel) else: message = self.registryValue('kick.message', channel, irc.network) irc.queueMsg(ircmsgs.kick(channel, msg.nick, message)) else: self.log.warning('Should kick %s from %s, but not opped.', msg.nick, channel) return msg def updateRegexp(self, channel, network): if self.lastModified < self.words.lastModified: self.makeRegexp(self.words(), channel, network) self.lastModified = time.time() def outFilter(self, irc, msg): if self.filtering and msg.command == 'PRIVMSG' and self.words(): channel = msg.channel self.updateRegexp(channel, irc.network) s = msg.args[1] if self.registryValue('stripFormatting'): s = ircutils.stripFormatting(s) t = self.regexp.sub(self.sub, s) if t != s: msg = ircmsgs.privmsg(msg.args[0], t, msg=msg) return msg def makeRegexp(self, iterable, channel, network): s = '(%s)' % '|'.join(map(re.escape, iterable)) if self.registryValue('requireWordBoundaries', channel, network): s = r'\b%s\b' % s self.regexp = re.compile(s, re.I) @internationalizeDocstring def list(self, irc, msg, args): """takes no arguments Returns the list of words being censored. """ L = list(self.words()) if L: self.filtering = False utils.sortBy(str.lower, L) irc.reply(format('%L', L)) else: irc.reply(_('I\'m not currently censoring any bad words.')) list = wrap(list, ['admin']) @internationalizeDocstring def add(self, irc, msg, args, words): """ [ ...] Adds all s to the list of words being censored. """ set = self.words() set.update(words) self.words.setValue(set) irc.replySuccess() add = wrap(add, ['admin', many('something')]) @internationalizeDocstring def remove(self, irc, msg, args, words): """ [ ...] Removes s from the list of words being censored. """ set = self.words() for word in words: set.discard(word) self.words.setValue(set) irc.replySuccess() remove = wrap(remove, ['admin', many('something')]) Class = BadWords # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/BadWords/test.py0000644000175000017500000000737113634634532020046 0ustar valval00000000000000### # Copyright (c) 2002-2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf from supybot.test import * class BadWordsTestCase(PluginTestCase): plugins = ('BadWords', 'Utilities', 'Format', 'Filter') badwords = ('shit', 'ass', 'fuck') def tearDown(self): # .default() doesn't seem to be working for BadWords.words #default = conf.supybot.plugins.BadWords.words.default() #conf.supybot.plugins.BadWords.words.setValue(default) conf.supybot.plugins.BadWords.words.setValue([]) def _test(self): for word in self.badwords: self.assertRegexp('echo %s' % word, '(?!%s)' % word) self.assertRegexp('echo [colorize %s]' % word, '(?!%s)' % word) self.assertRegexp('echo foo%sbar' % word, '(?!%s)' % word) self.assertRegexp('echo foo %s bar' % word, '(?!%s)' % word) self.assertRegexp('echo [format join "" %s]' % ' '.join(word), '(?!%s)' % word) with conf.supybot.plugins.BadWords.requireWordBoundaries \ .context(True): self.assertRegexp('echo foo%sbar' % word, word) self.assertRegexp('echo foo %sbar' % word, word) self.assertRegexp('echo foo%s bar' % word, word) self.assertRegexp('echo foo %s bar' % word, '(?!%s)' % word) def _NegTest(self): for word in self.badwords: self.assertRegexp('echo %s' % word, word) self.assertRegexp('echo foo%sbar' % word, word) self.assertRegexp('echo [format join "" %s]' % ' '.join(word),word) def testAddbadwords(self): self.assertNotError('badwords add %s' % ' '.join(self.badwords)) self._test() def testDefault(self): self._NegTest() def testRemovebadwords(self): self.assertNotError('badwords add %s' % ' '.join(self.badwords)) self.assertNotError('badwords remove %s' % ' '.join(self.badwords)) self._NegTest() def testList(self): self.assertNotError('badwords list') self.assertNotError('badwords add shit') self.assertNotError('badwords add ass') self.assertResponse('badwords list', 'ass and shit') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Channel/0000755000175000017500000000000013634634547016356 5ustar valval00000000000000limnoria-2020.03.17/plugins/Channel/__init__.py0000644000175000017500000000460413634634532020465 0ustar valval00000000000000### # Copyright (c) 2004-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Basic channel management commands. Many of these commands require their caller to have the #channel,op capability. This plugin is loaded by default. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = { supybot.authors.skorobeus: ['enable', 'disable'], } from . import config from . import plugin from importlib import reload reload(plugin) # In case we're being reloaded. if world.testing: from . import test Class = plugin.Class configure = config.configure limnoria-2020.03.17/plugins/Channel/config.py0000644000175000017500000000657513634634532020204 0ustar valval00000000000000### # Copyright (c) 2004-2005, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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.utils as utils import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Channel') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Channel', True) Channel = conf.registerPlugin('Channel') conf.registerChannelValue(Channel, 'alwaysRejoin', registry.Boolean(True, _("""Determines whether the bot will always try to rejoin a channel whenever it's kicked from the channel."""))) conf.registerChannelValue(Channel, 'nicksInPrivate', registry.Boolean(True, _("""Determines whether the output of 'nicks' will be sent in private. This prevents mass-highlights of a channel's users, accidental or on purpose."""))) conf.registerChannelValue(Channel, 'rejoinDelay', registry.NonNegativeInteger(0, _("""Determines how many seconds the bot will wait before rejoining a channel if kicked and supybot.plugins.Channel.alwaysRejoin is on."""))) conf.registerChannelValue(Channel, 'partMsg', registry.String('Limnoria $version', _("""Determines what part message should be used by default. If the part command is called without a part message, this will be used. If this value is empty, then no part message will be used (they are optional in the IRC protocol). The standard substitutions ($version, $nick, etc.) are all handled appropriately."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Channel/locales/0000755000175000017500000000000013634634547020000 5ustar valval00000000000000limnoria-2020.03.17/plugins/Channel/locales/de.po0000644000175000017500000006620513634634532020733 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: sdf\n" "POT-Creation-Date: 2012-03-11 20:58+UTC\n" "PO-Revision-Date: 2012-04-27 15:33+0200\n" "Last-Translator: Mikaela 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" "Generated-By: pygettext.py 1.5\n" #: config.py:48 msgid "" "Determines whether the bot will always try to\n" " rejoin a channel whenever it's kicked from the channel." msgstr "Legt fest ob der Bot immer probieren soll den Kanal wieder zu betreten, falls er gekickt wurde." #: plugin.py:69 msgid "" "[] [ ...]\n" "\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" "Setzt den Modus für auf , mit den angegeben Argumenten. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:76 msgid "change the mode" msgstr "ändert den Modus" #: plugin.py:80 msgid "" "[] []\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 "" "[] []\n" "\n" "Setzt das Kanal Limit auf . Falls 0 ist, oder nicht angegeben wird, wird das Kanal Limit entfernt. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:90 msgid "change the limit" msgstr "ändert das Limit" #: plugin.py:95 msgid "" "[]\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 "" "[]\n" "\n" "Setzt +m auf , sodass nur Ops und Voice Benutzer Nachrichten an den Kanal senden können. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:102 msgid "moderate the channel" msgstr "moderiert den Kanel" #: plugin.py:106 msgid "" "[]\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 "" "[]\n" "\n" "Setzt -m auf , sodass jeder Nachrichten an den Kanal senden kann. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:114 msgid "unmoderate the channel" msgstr "Kanal nicht mehr moderieren" #: plugin.py:118 msgid "" "[] []\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 "" "[] []\n" "\n" "Setzt das Schlüsselwort für auf . Fall nicht angegeben wird, wird die Schlüsselwort entfernt, und jeder kann dem beitreten. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:130 msgid "change the keyword" msgstr "ändert das Schlüsselwort" #: plugin.py:135 msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" "Falls du die #channel,op Fähigkeit hast, wird dies allen s die du angibst Op geben. Falls du keine s angibst, wird dir Op gegeben. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:147 msgid "op someone" msgstr "jemandem Operatorrechte geben." #: plugin.py:151 msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" "Falls du die #channel,halfop Fähigkeit hast, wird allen angegeben s halb-Op gegeben. Falls du keine s angibst wird dir halb-Op gegeben. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:163 msgid "halfop someone" msgstr "jemandem Halfop geben" #: plugin.py:168 msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" "Falls du die #channel,voice Fähigkeit hast, wird allen angegeben s Voice gegeben. Falls du keine s angibst wird dir Voice gegeben. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:190 msgid "voice someone" msgstr "Sprechrecht geben" #: plugin.py:195 msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" "Falls du die #channel,op Fähigkeit hast, wird allen angegeben Nicks Op entzogen. Falls keine Nicks angegeben sind, wird Op der Person entzogen die den Befehl gab." #: 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 "Ich verweigere es mir selbst Op zu entziehen. Falls du mir wirklich op entziehen willst, befehle mir dir Op zu geben und tu es selbst." #: plugin.py:210 msgid "deop someone" msgstr "Operator entziehen" #: plugin.py:215 msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" "Falls du die #channel,op Fähigkeit hast, wird allen angegeben Nicks halb-Op entzogen. Falls keine Nicks angegeben sind, wird halb-Op der Person entzogen die den Befehl gab." #: 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 "Ich verweigere mich mir halb-Op zu entziehen. Falls du mir halb-Op entziehen willst, befehle mir dir Op zu geben und tu es selbst." #: plugin.py:230 msgid "dehalfop someone" msgstr "entzieht jemanden halb-Op" #: plugin.py:235 msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" "Falls du die #channel,op Fähigkeit hast, wird allen angegeben Nicks Voice entzogen. Falls keine Nicks angegeben sind, wird Voice der Person entzogen die den Befehl gab." #: 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 "Ich bin zu Feige um mir Voice zu entziehen. Falls du mir wirklich Voice entziehen willst, befehle mir dir Op zu geben und entziehe mir Voice selbst." #: 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" "Falls du die #channel,op Fähigkeit hast, wird der Bot den Kanal verlassen und danach wieder betreten. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: 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" "Kickt die s aus dem mit der . Falls keine angegeben wurde, wird der Nick der Person die den Befehl gesendet hat, als Begründung genommen. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:276 msgid "I cowardly refuse to kick myself." msgstr "Ich bin zu feige um mich selbst zu kicken." #: plugin.py:281 msgid "The reason you gave is longer than the allowed length for a KICK reason on this server." msgstr "Der Grund den du mir gegeben hast war länger als die erlaubte für KICK Gründe auf diesem Server." #: plugin.py:286 msgid "kick someone" msgstr "jemanden kicken" #: 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" "Falls du die #channel,op Fähigkeit hast, wird den kickbannen, solange wie in Sekunden angegeben, oder andernfalls (falls 0 Sekunden oder gar keine Sekunden angeben werden) wird die Person für immer gebannt. --exact bannt nur die exakte Hostmaske; --nick bannt nur den Nick; --user bannt nur den Benuter und --host bannt nur diese Hostmaske. Du kannst die Optionen miteinander kombinieren wie du willst. ist die Begründung für den Kick. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:311 msgid "I cowardly refuse to kickban myself." msgstr "Ich bin zu feige um mich selbst zu kicken und zu bannen." #: plugin.py:318 msgid "I haven't seen %s." msgstr "Ich habe % nicht gesehen." #: plugin.py:326 msgid "I cowardly refuse to ban myself." msgstr "Ich bin zu feige um mich selbst zu bannen." #: plugin.py:353 msgid "%s has %s too, you can't ban them." msgstr "%s hat auch %s, du kannst ihn/sie/es nicht bannen." #: plugin.py:365 msgid "kick or ban someone" msgstr "jemanden kicken oder bannen" #: plugin.py:372 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" "Entbannt im . Falls die nicht angegeben wird, werden alle Bans entfernt, die momenaten im gebannt sind und auf deine momentane Hostmaske zutreffen. Besonders nützlich um dich selsbt zu entbannen, falls du versehentlich im Kanal gebannt wurdest. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:389 msgid "All bans on %s matching %s have been removed." msgstr "Alle Bans von %s die auf %s zutreffen wurden entfernt." #: plugin.py:393 msgid "No bans matching %s were found on %s." msgstr "Keine passenden Bans %s wurden in %s gefunden." #: plugin.py:396 msgid "unban someone" msgstr "jemanden entbannen" #: plugin.py:401 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" "Falls du die #channel,op Fähigkeit hast, wird dazu eingeladen den zu betreten. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:410 msgid "invite someone" msgstr "jemanden einladen" #: plugin.py:429 msgid "%s is already in %s." msgstr "%s ist schon in %s." #: plugin.py:436 msgid "There is no %s on this network." msgstr "%s nicht auf diesem Netzwerk." #: plugin.py:448 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" "Falls du die #channel,op Fähigkeit besitzt, wird dies den Bot \"hirnamputieren\", er wird still und wird nicht auf Anfragen reagieren, die im Kanal getätigt werden. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:463 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" "Falls du die #channel,op Fähigkeit hast, wird dem Bot sein Hirn zurückgegeben, ab diesem Zeitpunkt antwortet er wieder auf Anfragen aus dem Kanal. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:478 msgid "" "takes no arguments\n" "\n" " Returns the channels in which this bot is lobotomized.\n" " " msgstr "" "hat keine Argumente\n" "\n" "Gibt die Kanäle aus in denen der Bot hirnamputiert ist." #: plugin.py:493 msgid "I'm currently lobotomized in %L." msgstr "Ich bin momentan hirnamputiert in %L." #: plugin.py:496 msgid "I'm not currently lobotomized in any channels that you're in." msgstr "Momentan bin ich nicht hirnamputiert in allen Kanälen in denen du bist." #: plugin.py:503 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" "Falls du die #channel,op Fähigkeit hast, wird einem beständigen Ban mit der (oder dem Benutzer der mit verbunden wird) verboten mit dem Bot zu interagieren. Andere Plugins setzen es womöglich durch das der Ban Benutzer beim betreten eines Kanals wirklich bannt. ist optional, und legt fest wann (in Sekunden von jetzt an) der Ban abläuft, falls nicht angegeben wird der Ban niemals automatisch ablaufen. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:523 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" "Falls du die #channel,op Fähigkeit hast, wird der beständige Ban auf aufgehoben. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:535 msgid "There are no persistent bans for that hostmask." msgstr "Es gibt keine beständigen Bans für diese Hostmaske." #: plugin.py:540 #, fuzzy msgid "" "[]\n" "\n" " If you have the #channel,op capability, this will show you the\n" " current persistent bans on the .\n" " " msgstr "" "[]\n" "\n" "Falls du die #channel,op Fähigkeit hast, werden die momentan beständigen Bans im angezeigt." #: plugin.py:550 msgid "%q (expires %t)" msgstr "%q (läuft ab:%t)" #: plugin.py:553 msgid "%q (never expires)" msgstr "%q(läuft niemals ab)" #: plugin.py:557 msgid "There are no persistent bans on %s." msgstr "Es gibt keine beständigen Bans für %s." #: plugin.py:564 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" "Falls du die #channel,op Fähigkeit hast, wird eine beständige Ignorierung auf gesetzt oder auf die Hostmaske die momentan mit verbunden wird. ist optional und gibt an wann (in Sekunden von jetzt an) die Ignorierung abläuft, falls dies nicht angegeben wird, wird die Ignorierung niemals automatisch ablaufen. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:582 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" "Falls du die #channel,op Fähigkeit hast, wird dies den dauerhaften Ignore von im Kanal entfernen. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:594 msgid "There are no ignores for that hostmask." msgstr "Es gibt keine Ignorierungen für diese Hostmaske." #: plugin.py:599 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" "Listet die Hostmasken auf, die der Bot im gegeben Kanal ignoriert. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:608 msgid "I'm not currently ignoring any hostmasks in %q" msgstr "Momentan ignoriere ich keine Hostmasken in %s." #: plugin.py:619 #, fuzzy msgid "" "[] [ ...]\n" "\n" " If you have the #channel,op capability, this will give the\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" "Falls du die #channel,op Fähigkeit hast, wird die Kanalfähigkeit dem Benutzer (oder dem Benutzer auf den passt) gegeben. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:635 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" "Falls du die #channel,op Fähigkeit hast, wird die die Fähigkeit im Kanal vom Benutzer entfernt, der momentan als angemeldet ist (oder auf des Benutzers auf den die zutrifft). ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:654 msgid "That user didn't have the %L %s." msgstr "Der Nutzer hatte nicht %L %s." #: plugin.py:663 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" "Falls du die #channel,op Fähigkeit hast, wird dies die Standard Antwort auf nicht-macht-bezogene (also nicht {Op, halb-Op, Voice}) Fähigkeiten auf die angegeben Antwort setzen. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:681 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" "Falls du die #channel,op Fähigkeit hast, wird die Kanalfähigkeit für alle Benutzer hinzugefügt. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:696 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" "Falls du die #channel,op Fähigkeit hast, wird die Kanalfähigkeit entfernt, sodass die Benutzerfähigkeiten oder Kanal Standardfähigkeiten vorrang haben. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:712 msgid "capability" msgstr "Fähigkeit" #: plugin.py:715 msgid "I do not know about the %L %s." msgstr "Ich weiß nichts von %L %s." #: plugin.py:722 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" "Gibt die Fähigkeiten zurück, die es im gibt. ist nur notwendig, falls die Nachricht nicht im Kanal selbt gegeben wurde." #: plugin.py:734 msgid "" "[] [] []\n" "\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" "Falls du die #channel,op Fähigkeit hast, wird dies den im deaktivieren, falls er deaktiviert wurde. Falls angegeben wurde, wird nur für dieses deaktiviert. Falls nur angegeben wurde, werden alle Befehle des Plugins deaktiviert. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:750 #: plugin.py:789 msgid "The %s plugin does not have a command called %s." msgstr "%s Plugin hat keinen Befehl %s." #: plugin.py:757 #: plugin.py:796 msgid "No plugin or command named %s could be found." msgstr "Kein Plugin oder Befehl mit dem Namen %s konnte gefunden werden." #: plugin.py:773 msgid "" "[] [] []\n" "\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" "Falls du die #channel,op Fähigkeit hast, wird dies den im aktivieren, falls er deaktiviert wurde. Falls angegeben wurde, wird nur für dieses aktiviert. Falls nur angegeben wurde, werden alle Befehle des Plugins aktiviert. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:810 msgid "%s was not disabled." msgstr "%s wurde nicht abgeschaltet." #: plugin.py:819 msgid "" "[] [--count]\n" "\n" " Returns the nicks in . is only necessary if the\n" " message isn't sent in the channel itself. Returns only the number of\n" " nicks if --count option is provided.\n" " " msgstr "" "[] [--count]\n" "\n" "Gibt die Nicks in zurück. ist nur notwendig, wenn die Nachricht nicht im Kanal selbst gesendet wurde. Gibt nur die Anzahl der Nicks an, falls die --count Option angegeben wurde." #: plugin.py:831 msgid "You don't have access to that information." msgstr "Ich habe keinen Zugriff auf diese Informationen." #: plugin.py:845 msgid "" "Internal message for notifying all the #channel,ops in a channel of\n" " a given situation." msgstr "Interne Nachricht die alle #channel,ops eines Kanals über eine Situation informiert." #: plugin.py:848 msgid "Alert to all %s ops: %s" msgstr "Alarm an alle %s Operatoren: %s" #: plugin.py:850 msgid " (from %s)" msgstr "(von %s)" #: plugin.py:858 msgid "" "[] \n" "\n" " Sends to all the users in who have the ,op\n" " capability.\n" " " msgstr "" "[] \n" "\n" "Sendet , die die ,op Fähigkeit haben." limnoria-2020.03.17/plugins/Channel/locales/fi.po0000644000175000017500000010146113634634532020733 0ustar valval00000000000000# # Mikaela Suomalainen , 2012. # msgid "" msgstr "" "Project-Id-Version: Supybot Channel\n" "POT-Creation-Date: 2014-12-20 11:59+EET\n" "PO-Revision-Date: 2014-12-20 12:14+0200\n" "Last-Translator: Mikaela Suomalainen \n" "Language-Team: suomi <>\n" "Language: fi_FI\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: \n" "X-Generator: Poedit 1.6.10\n" #: 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:46 msgid "" "This plugin provides various commands for channel management, such\n" " as setting modes and channel-wide bans/ignores/capabilities. This is\n" " a core Supybot plugin that should not be removed!" msgstr "" "Tämä plugin antaa erilaisia kanavanhallinta komentoja, kuten tilojen ja " "kanavanlaajuisten\n" " bannien/ignoreiden/valtuuksien asettamisen. Tämä on ydin Supybot-plugin, " "jota ei pitäisi poistaa." #: plugin.py:78 msgid "" "[] [ ...]\n" "\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" "Asettaa tilaksi , lähettäen annetut parametrit.\n" " on vaadittu vain, jos viestiä ei lähetetä kanavalta\n" " jonka tilaa ollaan muuttamassa.\n" " " #: plugin.py:85 msgid "change the mode" msgstr "vaihda tila" #: plugin.py:89 msgid "" "[] []\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 "" "[] []\n" "\n" "Asettaa kanavan . Jos on 0, tai ei annettu,\n" "kanavan rajoitus poistetaan. on vaadittu vain jos\n" "viestiä ei lähetetä kanavalta itseltään.\n" " " #: plugin.py:99 msgid "change the limit" msgstr "Vaihda rajoitusta" #: plugin.py:104 msgid "" "[]\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 "" "[]\n" "\n" "Asettaa tilan +m , sallien vain operaattoreiden ja käyttäjien " "jolla on ääni\n" " lähettää viestejä kanavalle. on vaadittu vain jos\n" " viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:111 msgid "moderate the channel" msgstr "valvo kanavaa" #: plugin.py:115 msgid "" "[]\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 "" "[]\n" "\n" "Asettaa tilan -m , sallien jokaisen\n" " lähettää viestejä kanavalla. on vaadittu vain jos\n" " viestiä ei lähetetä kanavalta itseltään.\n" " " #: plugin.py:123 msgid "unmoderate the channel" msgstr "lopeta kanavan valvominen" #: plugin.py:127 msgid "" "[] []\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 "" "[] []\n" "\n" " Asettaa avainsanan . Jos ei ole " "annettu, poistaa\n" " avainsana vaatimuksen liittymisestä. on " "vaadittu vain\n" " jos viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:139 msgid "change the keyword" msgstr "vaihtaa avainsanan" #: plugin.py:144 msgid "" "[] [ ...]\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 "" "[] [ ...]\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" " itsellään.\n" " " #: plugin.py:156 msgid "op someone" msgstr "oppaa joku" #: plugin.py:160 msgid "" "[] [ ...]\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 "" "[] [ ...]\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" " " #: plugin.py:172 msgid "halfop someone" msgstr "halfoppaa joku" #: plugin.py:193 msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" " Jos sinulla on #kanava,voice valtuus, tämä antaa kaikille\n" " äänen. Jos et anna yhtään , tämä\n" " antaa sinulle äänen. on vaadittu vain jos viestiä ei\n" " lähetetä kanavalla itsellään.\n" " " #: plugin.py:201 msgid "voice someone" msgstr "anna jollekulle ääni" #: plugin.py:206 msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" " Jos sinulla on #kanava,op valtuus, tämä poistaa opit\n" " kaikilta annetuilta nimimerkeiltä. Jos nimimerkkejä ei ole annettu, " "poistaa\n" " opit henkilöltä joka lähettää viestin.\n" " " #: plugin.py:213 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 poistamasta oppeja itseltäni. Jos todella " "tahdot poistaa minulta opit, käske minun opata sinut ja sitten poista opit " "minut itse." #: plugin.py:221 msgid "deop someone" msgstr "deoppaa joku" #: plugin.py:226 msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" " Jos sinulla on #kanava,op valtuus, tämä poistaa half-op:n\n" " kaikilta annetuilta nimimerkeiltä. Jos nimimerkkejä ei anneta,\n" " viestin lähettäneeltä henkilöltä poistetaan puoli-kanavaoperaattorin " "oikeudet.\n" " " #: plugin.py:233 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 poistamaan half-oppeja itseltäni. Jos haluat " "minut half-opatuksi, käske minun opata sinut ja sitten poista half-op minut " "itse." #: plugin.py:241 msgid "dehalfop someone" msgstr "poista joltakulta half-op" #: plugin.py:246 msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" " Jos sinulla on #kanava,op valtuus, tämä poistaa äänen kaikilta\n" " annetuilta nimimerkeiltä. Jos nimimerkkejä ei ole annettu, poistaa " "äänen henkilöltä,\n" " joka lähettää viestin.\n" " " #: plugin.py:253 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 ääntä itseltäni. Jos todella " "tahdot poistaa minulta äänen, käske minut oppaamaan sinut ja sitten poista " "ääni minulta itse." #: plugin.py:262 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:275 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" " itseltään.\n" " " #: plugin.py:283 msgid "I cowardly refuse to kick myself." msgstr "Minä pelkurimaisesti kieltäydyn potkimasta itseäni." #: plugin.py:288 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:293 msgid "kick someone" msgstr "potki joku" #: plugin.py:299 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ä potkii ja antaa " "porttikiellon \n" " niin moneksi sekuntiksi kuin määrität, tai muuten (jos määrität 0 " "sekuntia tai\n" " et määritä sekuntien lukumäärää) se antaa henkilölle ikuisen " "porttikiellon.\n" " --exact antaa porttikiellon vain hostmaskille; --nick antaa " "porttikiellon vain nimimerkille;\n" " --user antaa porttikiellon vain käyttäjälle, ja --host antaa " "porttikiellon vain isännälle. Voit\n" " yhdistää näitä vaihtoehtoja mielesi mukaan. on syy, jonka " "vuoksi annat\n" " potkun.\n" " on vaadittu vain jos viestiä ei lähetetä kanavalla\n" " itsellään.\n" " " #: plugin.py:316 msgid "kick or ban someone" msgstr "potki tai anna jollekulle porttikielto" #: plugin.py:324 msgid "" "[] [--{exact,nick,user,host}] []\n" "\n" " If you have the #channel,op capability, this will ban for\n" " as many seconds as you specify, otherwise (if you specify 0 seconds " "or\n" " don't specify a number of seconds) it will ban the person " "indefinitely.\n" " --exact can be used to specify an exact hostmask. You can combine " "the\n" " exact, nick, user, and host options as you choose. is " "only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[] [--{exact,nick,user,host}] []\n" " Mikäli käyttäjällä on #kanava,op valtuus, tämä antaa " "porttikiellon\n" " niin pitkäksi aikaa, kuin määritetään sekunneissa, muutoin (jos määrität 0 " "sekuntia\n" " tai et määritä sekuntejen määrää ollenkaan) henkilölle annetaan " "määrittämättömän\n" " ajan pituinen porttikielto. --exact lippua voidaan käyttää tarkan " "hostmaskin\n" " määrittämiseen. Asetuksia, exact, nimimerkki, käyttäjä ja isäntä , voiaan " "yhdistää\n" " käyttäjän halun mukaan. on tarpeellinen vain, ellei viestiä " "lähetetä \n" " kanavalla itsellään." #: plugin.py:338 plugin.py:564 msgid "ban someone" msgstr "anna jollekulle porttikielto" #: plugin.py:358 msgid "I haven't seen %s." msgstr "Minä en ole nähnyt käyttäjää %s." #: plugin.py:368 msgid "I cowardly refuse to kickban myself." msgstr "" "Minä pelkurimaisesti kieltäydyn potkimasta itseäni ja antamasta itselleni " "porttikieltoa." #: plugin.py:377 msgid "I cowardly refuse to ban myself." msgstr "Minä pelkurimaisesti kieltäydyn antamasta itselleni porttikieltoa." #: plugin.py:405 msgid "%s has %s too, you can't ban them." msgstr "" "Käyttäjälltä %s on myös valtuus %s, et voi antaa hänelle/sille porttikieltoa." #: plugin.py:416 #, fuzzy 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 porttikiellon . Jos " "ei ole annettu, poistaa porttikiellon\n" " kaikilta hostmaskeilta, joilla on porttikielto ja jotka " "täsmäävät sinun\n" " hostmaskiisi. Etenkin hyödyllinen jos saat \n" " odottomattomasti (tai vahingossa) porttikiellon kanavalta. " "on\n" " vaadittu vain jos viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:436 msgid "All bans on %s matching %s have been removed." msgstr "" "Kaikki porttikiellot, jotka täsmäävät kanavalla %s käyttäjään %s on " "poistettu." #: plugin.py:440 msgid "No bans matching %s were found on %s." msgstr "Banneja, jotka täsmäävät käyttäjään %s ei löydetty kanavalla %s." #: plugin.py:443 msgid "unban someone" msgstr "poista joltakulta porttikielto" #: plugin.py:450 msgid "" "[]\n" "\n" " List all bans on the channel.\n" " If is not given, it defaults to the current channel." msgstr "" "[]\n" "\n" " Luettelee kaikki kanavan porttikiellot.\n" " Jos ei ole annettu, se on oletuksena nykyinen kanava." #: plugin.py:454 msgid "No bans." msgstr "Ei porttikieltoja." #: plugin.py:459 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:468 msgid "invite someone" msgstr "kutsu joku" #: plugin.py:487 msgid "%s is already in %s." msgstr "%s on jo kanavalla %s" #: plugin.py:494 msgid "There is no %s on this network." msgstr "Käyttäjä %s ei ole tässä verkossa." #: plugin.py:506 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 itsellään.\n" " " #: plugin.py:521 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 " "itsellään.\n" " " #: plugin.py:536 msgid "" "takes no arguments\n" "\n" " Returns the channels in which this bot is lobotomized.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Palauttaa kanavat, joilla botti on lobotomoitu.\n" " " #: plugin.py:551 msgid "I'm currently lobotomized in %L." msgstr "Minut on tällä hetkellä lobotomoity seuraavilla kanavilla: %L." #: plugin.py:554 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:568 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 porttikiellon estääkseen bottia olemasta " "vuorovaikutuksessa annetun\n" " kanssa (tai nykyisen hostmaskin. " "Toiset\n" " lisäosat saattavat pakottaa tämän porttikiellon antamalla " "porttikiellon käyttäjille, joiden\n" " hostmaskit täsmäävät lisättyyn porttikieltoon. on " "vaihtoehtoinen\n" " parametri , kun määritetään ( \"sekunteja alkaen nyt\") milloin " "porttikielto\n" " vanhentuu; jos mitään ei ole annettu, porttikielto ei ikinä " "vanhene automaattisesti.\n" " on vaadittu vain jos viestiä ei lähetetä\n" " kanavalla itsellään.\n" " " #: plugin.py:588 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 porttikiellon . on vaadittu vain " "jos\n" " viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:600 msgid "There are no persistent bans for that hostmask." msgstr "Tuolla hostmaskilla ei ole pysyvää porttikieltoa." #: plugin.py:605 #, fuzzy msgid "" "[] []\n" "\n" " If you have the #channel,op capability, this will show you the\n" " current persistent bans on the that match the given\n" " mask, if any (returns all of them otherwise).\n" " Note that you can use * as a wildcard on masks and \\* to match\n" " actual * in masks\n" " " msgstr "" "[] \n" "\n" " Jos sinulla on #kanava,op valtuus, tämä poistaa\n" " pysyvän porttikiellon . on vaadittu vain " "jos\n" " viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:623 msgid "%q (expires %t)" msgstr "%q (venhentuu %t)" #: plugin.py:626 msgid "%q (never expires)" msgstr "%q (ei vanhene ikinä)" #: plugin.py:630 msgid "There are no persistent bans on %s." msgstr "Kanavalla %s ei ole pysyviä porttikieltoja." #: plugin.py:637 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" " huomiotta jättämisen tai hostmaskiin\n" " joka on käytössä. on vaihtoehtoinen " "parametri\n" " kun määritetään ( \"sekunteja alkaen nyt\")milloin huomiotta " "jättäminen vanhentuu; jos\n" " sitä ei ole annettu, huomiotta jättäminen ei ikinä vanhene " "automaattisesti.\n" " on vaadittu vain jos viestiä ei lähetetä\n" " kanavalla itsellään.\n" " " #: plugin.py:655 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 huomiotta jättämisen kanavalla. " "on vaadittu vain jos\n" " viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:667 msgid "There are no ignores for that hostmask." msgstr "Tuolla hostmaskilla ei ole pysyviä huomiotta jättämisiä." #: plugin.py:672 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 huomiotta jättämis " "listalla annetulla kanavalla.\n" " on vaadittu vain, jos viestiä ei lähetetä\n" " kanavalla itsellään.\n" " " #: plugin.py:681 msgid "I'm not currently ignoring any hostmasks in %q" msgstr "En tällä hetkellä jätä mitään hostmaskia huomiotta kanavalla %q." #: plugin.py:692 msgid "" "[] [ ...]\n" "\n" " If you have the #channel,op capability, this will give the\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 \n" " (tai käyttäjälle, jonka " "määrittää)\n" " kanavalla. on\n" " vaadittu vain jos viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:708 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ä poistaa\n" " tällä hetkellä tunnistautuneelta käyttäjältä (tai " "käyttäjältä, jonka \n" " täsmää) kanavalla. on\n" " vaadittu vain jos viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:727 msgid "That user didn't have the %L %s." msgstr "Tuolla käyttäjällä ei ollut valtuutta %L kanavalla %s." #: plugin.py:736 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-liittyvän (jokin muu kuin {op, halfop, voice}\n" " valtuusarvon, jonka annat. on vaadittu vain\n" " jos viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:754 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" " kaikille käyttäjille kanavalla. on\n" " vaadittu vain jos viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:769 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 kanavan\n" " joten jokaisen käyttäjäkohtainen valtuus tai\n" " kanavan oletusvaltuus otetaan käyttöön. on vaadittu\n" " vain jos viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:785 msgid "capability" msgstr "valtuus" #: plugin.py:788 msgid "I do not know about the %L %s." msgstr "En tiedä valtuudesta %L kanavalla %s." #: plugin.py:795 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" " Palauttaa listan valtuuksista, jotka ovat . " "on\n" " vaadittu vain, jos viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:807 msgid "" "[] [] []\n" "\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" " Jos sinulla on #kanava,op valtuus, tämä poistaa käytöstä\n" " . Jos on annettu, 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:823 plugin.py:862 msgid "The %s plugin does not have a command called %s." msgstr "%s lisäosassa ei ole komentoa %s." #: plugin.py:830 plugin.py:869 msgid "No plugin or command named %s could be found." msgstr "Lisäosaa tai komentoa nimeltä %s ei löytynyt." #: plugin.py:846 msgid "" "[] [] []\n" "\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" " Jos sinulla on #kanava,op valtuus, tämä ottaa käyttöön\n" " jos se on poistettu 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:883 msgid "%s was not disabled." msgstr "%s ei ollut poistettu käytöstä." #: plugin.py:892 msgid "" "[] [--count]\n" "\n" " Returns the nicks in . is only necessary if the\n" " message isn't sent in the channel itself. Returns only the number " "of\n" " nicks if --count option is provided.\n" " " msgstr "" "[]\n" "\n" " Palauttaa listan nimimerkit, jotka ovat . on " "vaadittu vain jos\n" " viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:907 msgid "You don't have access to that information." msgstr "Sinulla ei ole pääsyoikeutta tuohon tietoon." #: plugin.py:921 msgid "" "Internal message for notifying all the #channel,ops in a channel of\n" " a given situation." msgstr "" "Sisäinen viesti huomattamaan kaikkia henkilöitä, joilla on #kanava,op " "valtuus kanavalla,\n" " annetusta tilanteesta." #: plugin.py:924 msgid "Alert to all %s ops: %s" msgstr "Hälytys kaikille kanavan %s operaattoreille: %s" #: plugin.py:926 msgid " (from %s)" msgstr "(lähettänyt %s)" #: plugin.py:935 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, jotka ovat ja " "joilla on ,op\n" " valtuus.\n" " " #~ msgid "" #~ "[]\n" #~ "\n" #~ " If you have the #channel,op capability, this will show you " #~ "the\n" #~ " current persistent bans on the .\n" #~ " " #~ msgstr "" #~ "[]\n" #~ "\n" #~ " Jos sinulla on #kanava,op valtuus, tämä näyttää\n" #~ " pysyvät bannit #kanavalla.\n" #~ " " limnoria-2020.03.17/plugins/Channel/locales/fr.po0000644000175000017500000007163413634634532020754 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2013-03-03 19:39+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Limnoria \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" #: config.py:48 msgid "" "Determines whether the bot will always try to\n" " rejoin a channel whenever it's kicked from the channel." msgstr "" "Détermine si le bot tentera toujours de rejoindre un canal duquel il a été " "kické." #: plugin.py:69 msgid "" "[] [ ...]\n" "\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" "Définit le sur le canal, en envoyant les arguments donnés. " "n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:76 msgid "change the mode" msgstr "changer le mode" #: plugin.py:80 msgid "" "[] []\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 "" "[] []\n" "\n" "Définit la du canal. Si est 0, ou n'est pas donné, " "supprime la limite du canal. n'est nécessaire que si le message " "n'est pas envoyé sur le canal lui-même." #: plugin.py:90 msgid "change the limit" msgstr "changer la limite" #: plugin.py:95 msgid "" "[]\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 "" "[]\n" "\n" "Définit le +m sur le canal, ce qui fait que seuls les ops et les " "utilisateurs voicés peuvent envoyer des message au canal. n'est " "nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:102 msgid "moderate the channel" msgstr "modérer le canal" #: plugin.py:106 msgid "" "[]\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 "" "[]\n" "\n" "Définit le -m sur le , ce qui fait que tout le monde peut à nouveau " "envoyer des messages au canal. n'est nécessaire que si le message " "n'est pas envoyé sur le canal lui-même." #: plugin.py:114 msgid "unmoderate the channel" msgstr "démodérer le canal" #: plugin.py:118 msgid "" "[] []\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 "" "[] []\n" "\n" "Définit la du . Si la n'est pas donnée, retire la clef " "du . n'est nécessaire que si le message n'est pas envoyé sur " "le canal lui-même." #: plugin.py:130 msgid "change the keyword" msgstr "changer la clef" #: plugin.py:135 msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" "Si vous avez la capacité #canal,op, ceci vous permet de oper tous les " "s. Si vous ne fournissez aucun , ceci vous opera. n'est " "nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:147 msgid "op someone" msgstr "oper quelqu'un" #: plugin.py:151 msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" "Si vous avez la capacité #canal,halfop, ceci vous permet de halfoper tous " "les s. Si vous ne fournissez aucun , ceci vous halfopera. " " n'est nécessaire que si le message n'est pas envoyé sur le canal lui-" "même." #: plugin.py:163 msgid "halfop someone" msgstr "halfoper quelqu'un" #: plugin.py:184 msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" "Si vous avez la capacité #canal,voice, ceci vous permet de voicer tous les " "s. Si vous ne fournissez aucun , ceci vous voicera. " "n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:192 msgid "voice someone" msgstr "voicer quelqu'un" #: plugin.py:197 msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" "Si vous avez la capacité #canal,op, ceci vous permet de déoper tous les " "s. Si vous ne fournissez aucun , ceci vous déopera. " "n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:204 msgid "" "I cowardly refuse to deop myself. If you really want me deopped, tell me to " "op you and then deop me yourself." msgstr "" "Je suis trop couard pour me déoper moi-même. Si vous voulez vraiment me " "déoper, dîtes-moi de vous oper, et déopez-moi vous-même." #: plugin.py:212 msgid "deop someone" msgstr "déoper quelqu'un" #: plugin.py:217 msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" "Si vous avez la capacité #canal,op, ceci vous permet de déhalfoper tous les " "s. Si vous ne fournissez aucun , ceci vous déhalfopera. " "n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:224 msgid "" "I cowardly refuse to dehalfop myself. If you really want me dehalfopped, " "tell me to op you and then dehalfop me yourself." msgstr "" "Je suis trop couard pour me déhalfoper moi-même. Si vous voulez vraiment me " "déhalfoper, dîtes-moi de vous oper, et déhalfopez-moi vous-même." #: plugin.py:232 msgid "dehalfop someone" msgstr "déhalfoper quelqu'un" #: plugin.py:237 msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" "Si vous avez la capacité #canal,op, ceci vous permet de dévoicer tous les " "s. Si vous ne fournissez aucun , ceci vous dévoicera. " "n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:244 msgid "" "I cowardly refuse to devoice myself. If you really want me devoiced, tell " "me to op you and then devoice me yourself." msgstr "" "Je suis trop couard pour me dévoicer moi-même. Si vous voulez vraiment me " "dévoicer, dîtes-moi de vous oper, et dévoicez-moi vous-même." #: plugin.py:253 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" "Si vous avez la capacité #canal,op,, ceci fera \"cycler\" le bot, ou un PART " "et un JOIN. n'est nécessaire que si le message n'est pas envoyé sur " "le canal lui-même." #: plugin.py:266 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" "Kicke le(s) (s) du pour la . Si la raison n'est pas " "donnée, le nick de la personne envoyant la commande est utilisé. " "n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:274 msgid "I cowardly refuse to kick myself." msgstr "Je suis trop couard pour me kicker moi-même." #: plugin.py:279 msgid "" "The reason you gave is longer than the allowed length for a KICK reason on " "this server." msgstr "" "La raison que vous avez donnée est plus longue que la taille autorisée pour " "une raison de KICK sur ce serveur." #: plugin.py:284 msgid "kick someone" msgstr "kicker quelqu'un" #: plugin.py:290 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" "Si vous avez la capacité #canal,op, ceci kickbannira pendant le " "nombre de que vous avez spécifiées, ou, si vous n'avez rien " "indiqué (ou indiqué 0), bannira la personne indéfiniment.--exact ne bannit " "que le masque d'hôte exacte ; --nick bannit le nick ; --user bannit " "l'ident ; --host bannit l'hôte. Vous pouvez combiner ces options à votre gré." " est une raison que vous donnez pour le kick. n'est " "nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:307 msgid "kick or ban someone" msgstr "kicker ou bannir quelqu'un" #: plugin.py:315 msgid "" "[] [--{exact,nick,user,host}] []\n" "\n" " If you have the #channel,op capability, this will ban 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.\n" " is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[] [--{exact,nick,user,host}] []\n" "\n" "Si vous avez la capacité #canal,op, ceci bannira pendant le nombre de " " que vous avez spécifiées, ou, si vous n'avez rien indiqué (ou " "indiqué 0), bannira la personne indéfiniment.--exact ne bannit que le masque " "d'hôte exacte ; --nick bannit le nick ; --user bannit l'ident ; --host " "bannit l'hôte. Vous pouvez combiner ces options à votre gré. est une " "raison que vous donnez pour le kick. n'est nécessaire que si le " "message n'est pas envoyé sur le canal lui-même." #: plugin.py:331 msgid "ban someone" msgstr "bannir quelqu'un" #: plugin.py:351 msgid "I haven't seen %s." msgstr "Je n'ai jamais vu %s." #: plugin.py:361 msgid "I cowardly refuse to kickban myself." msgstr "Je suis trop couard pour me kickbannir moi-même." #: plugin.py:370 msgid "I cowardly refuse to ban myself." msgstr "Je suis trop couard pour me bannir moi-même." #: plugin.py:398 msgid "%s has %s too, you can't ban them." msgstr "%s est aussi %s, je ne peux le/la bannir." #: plugin.py:410 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" "Débannit le du . Si le n'est pas " "donné, débannit tous les masques d'hôte qui correspondent à votre masque " "d'hôte actuel. Particulièrement utile lorsque vous avez été banni(e) de " "manière inatendue ou accidentelle d'un canal. n'est nécessaire que " "si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:430 msgid "All bans on %s matching %s have been removed." msgstr "Tous les bannissements sur %s correspondant à %s ont été supprimés." #: plugin.py:434 msgid "No bans matching %s were found on %s." msgstr "Aucun bannissement correspondant à %s n'a été trouvé sur %s." #: plugin.py:437 msgid "unban someone" msgstr "débannir quelqu'un" #: plugin.py:444 msgid "" "[]\n" "\n" " List all bans on the channel.\n" " If is not given, it defaults to the current channel." msgstr "" "[]Liste tous les bannissements du salon. Si n'est pas donné, " "il correspond au canal actuel." #: plugin.py:448 msgid "No bans." msgstr "Pas de bannissement." #: plugin.py:453 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" "Si vous avez la capacité #canal,op, ceci invitera à rejoindre le " ". n'est nécessaire que si le message n'est pas envoyé sur le " "canal lui-même." #: plugin.py:462 msgid "invite someone" msgstr "inviter quelqu'un" #: plugin.py:481 msgid "%s is already in %s." msgstr "%s est déjà sur %s." #: plugin.py:488 msgid "There is no %s on this network." msgstr "Il n'y a aucun %s sur ce réseau." #: plugin.py:500 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" "Si vous avez la capacité #canal,op, ceci \"lobotomisera\" le bot, c'est à " "dire qu'il sera silencieux et ne répondra plus à aucune requête faite sur le " "canal. n'est nécessaire que si le message n'est pas envoyé sur le " "canal lui-même." #: plugin.py:515 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" "Si vous avez la capacité #canal,op, ceci \"dé-lobotomisera\" le bot, c'est à " "dire qu'il ne sera plus silencieux et répondra à nouveau aux requêtes faites " "sur le canal. n'est nécessaire que si le message n'est pas envoyé " "sur le canal lui-même." #: plugin.py:530 msgid "" "takes no arguments\n" "\n" " Returns the channels in which this bot is lobotomized.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne les canaux sur lesquels le bot est lobotomisé." #: plugin.py:545 msgid "I'm currently lobotomized in %L." msgstr "Je suis actuellement lobotomisé sur %L." #: plugin.py:548 msgid "I'm not currently lobotomized in any channels that you're in." msgstr "Je ne suis actuellement lobotomisé sur aucun canal où vous êtes." #: plugin.py:555 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" "Si vous avez la capacité #canal,op, ceci affectera un bannisement persistant " "au spécifié. D'autres plugins pourraient renforcer ce " "bannissement en bannissant vraiment les utilisateurs correspondant à ce " "masque d'hôte lorsqu'ils entrent. est un argument option, " "correspondant à la durée, en secondes, que doit avoir le bannissement." #: plugin.py:575 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" "Si vous avez la capacité #canal,op, ceci supprimera le bannissement " "persistant sur le . n'est nécessaire que si le " "message n'est pas envoyé sur le canal lui-même." #: plugin.py:587 msgid "There are no persistent bans for that hostmask." msgstr "Il n'y a pas de bannissement persistant pour ce masque d'hôte." #: plugin.py:592 msgid "" "[]\n" "\n" " If you have the #channel,op capability, this will show you the\n" " current persistent bans on the .\n" " " msgstr "" "[]\n" "\n" "Si vous avez la capacité #canal,op, ceci vous affichera la liste des " "bannissements persistants sur le canal." #: plugin.py:602 msgid "%q (expires %t)" msgstr "%q (expire dans %t)" #: plugin.py:605 msgid "%q (never expires)" msgstr "%q (n'expire jamais)" #: plugin.py:609 msgid "There are no persistent bans on %s." msgstr "Il n'y a pas de bannissement persistant sur %s." #: plugin.py:616 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" "Vous permet d'ajouter un ignore persistant sur le , ou le " "masque associé au , à condition d'avoir la capacité #canal,op. " " est un argument optionnel, correspondant au nombre de secondes " "durant lesquelles l'ignore sera effectif ; si il n'est pas donné, l'ignore " "n'expirera jamais. n'est nécessaire que si le message n'est pas " "envoyé sur le canal lui-même." #: plugin.py:634 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" "Si vous avez la capacité #canal,op, ceci supprimera le masque d'ignorance " "persistant du sur le canal. n'est nécessaire que si " "le message n'est pas envoyé sur le canal lui-même." #: plugin.py:646 msgid "There are no ignores for that hostmask." msgstr "Il n'y a pas d'ignorance pour ce masque d'hôte." #: plugin.py:651 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" "Liste les masques d'hôte que le bot ignore sur le canal donné. n'est " "nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:660 msgid "I'm not currently ignoring any hostmasks in %q" msgstr "Je n'ignore actuellement aucun masque d'hôte sur %q." #: plugin.py:671 msgid "" "[] [ ...]\n" "\n" " If you have the #channel,op capability, this will give the\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" "Si vous avez la permission #canal,op : donne à l'utilisateur (ou celui " "ayant actuellement le ) la sur le canal. n'est " "nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:687 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" " Vous permet de prendre la de l'utilisateur (ou celui à qui " "appartient le , à condition que vous ayez la capacité #canal," "op. n'est nécessaire que si le message n'est pas envoyé sur le canal " "lui-même." #: plugin.py:706 msgid "That user didn't have the %L %s." msgstr "Cet utilisateur n'a pas les %L %s." #: plugin.py:715 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 "" "[] {on|off}\n" "\n" "Si vous avez la capacité #canal,op, cela vous permet de définir la réponse " "par défaut relative aux problèmes de pouvoir nécessaire op, halfop, voice). " " n'est nécessaire que si le message n'est pas envoyé sur " "le canal lui-même." #: plugin.py:733 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" "Si vous avez la capacité #canal,op ceci ajoutera la de canal à " "tous les utilisateurs du canal. n'est nécessaire que si le message " "n'est pas envoyé sur le canal lui-même." #: plugin.py:748 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" "Si vous avez la capacité #canal,op ceci désactivera le fait que tous les " "utilisateurs du canal aient la de canal. n'est nécessaire " "que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:764 msgid "capability" msgstr "capacité" #: plugin.py:767 msgid "I do not know about the %L %s." msgstr "Je ne sais rien à propos des %L %s." #: plugin.py:774 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" "Retourne les capacité présentes sur le . n'est nécessaire que " "si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:786 msgid "" "[] [] []\n" "\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" " Si vous avez la capacité #canal,op, ceci désactive la sur le " ". Si le est fourni, toutes les commandes de ce plugin seront " "désactivées. n'est nécessaire que si le message n'est pas envoyé sur " "le canal lui-même." #: plugin.py:802 plugin.py:841 msgid "The %s plugin does not have a command called %s." msgstr "Le plugin %s n'a pas de commande appelée %s." #: plugin.py:809 plugin.py:848 msgid "No plugin or command named %s could be found." msgstr "Aucun plugin ou commande appelé %s n'a pû être trouvé." #: plugin.py:825 msgid "" "[] [] []\n" "\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" "Si vous avez la capacité #canal,op, ceci activera la sur " "le , si elle a auparavant été désactivée. Si le seule " "la commande de ce plugin sera activée. n'est nécessaire " "n'est pas envoyé sur le canal lui-même." #: plugin.py:862 msgid "%s was not disabled." msgstr "%s n'était pas désactivé." #: plugin.py:871 msgid "" "[] [--count]\n" "\n" " Returns the nicks in . is only necessary if the\n" " message isn't sent in the channel itself. Returns only the number " "of\n" " nicks if --count option is provided.\n" " " msgstr "" "[] [--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. Ne retourne que le nombre de nicks " "si --count est donné." #: plugin.py:883 msgid "You don't have access to that information." msgstr "Vous n'avez pas accès à cette information" #: plugin.py:897 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:900 msgid "Alert to all %s ops: %s" msgstr "Alerte à tous les ops de %s : %s" #: plugin.py:902 msgid " (from %s)" msgstr "(de %s)" #: plugin.py:910 msgid "" "[] \n" "\n" " Sends to all the users in who have the ," "op\n" " capability.\n" " " msgstr "" "[] \n" "\n" "Envoie le à tous les utilisateurs sur qui ont la capacité " "#canal,op." limnoria-2020.03.17/plugins/Channel/locales/hu.po0000644000175000017500000006730613634634532020762 0ustar valval00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: Limnoria Channel\n" "POT-Creation-Date: 2012-03-11 20:58+UTC\n" "PO-Revision-Date: 2012-04-27 14:49+0200\n" "Last-Translator: Mikaela 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" "Generated-By: pygettext.py 1.5\n" #: config.py:48 msgid "" "Determines whether the bot will always try to\n" " rejoin a channel whenever it's kicked from the channel." msgstr "Meghatározza, hogy a bot mindig megpróbáljon-e visszatérni egy csatornára, ha kirúgják onnan." #: plugin.py:69 msgid "" "[] [ ...]\n" "\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" " módját -ra állítja, elküldve a kapott paramétereket. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:76 msgid "change the mode" msgstr "megváltoztatni a módot" #: plugin.py:80 msgid "" "[] []\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 "" "[] []\n" "\n" "A csatorna korlátját -ra állítja. Ha 0 vagy nem meghatározott, eltávolítja a csatorna korlátját. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:90 msgid "change the limit" msgstr "megváltoztatni a korlátot" #: plugin.py:95 msgid "" "[]\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 "" "[]\n" "\n" "-t +m-re állítja, hogy csak operátorok és hanggal rendelkező felhasználók küldhessenek üzeneteket a csatornára. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:102 msgid "moderate the channel" msgstr "moderálni a csatornát" #: plugin.py:106 msgid "" "[]\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 "" "[]\n" "\n" "-t -m-re állítja, hogy mindenki küldhessen üzeneteket a csatornára. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:114 msgid "unmoderate the channel" msgstr "kikapcsolni a moderálást a csatornán" #: plugin.py:118 msgid "" "[] []\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 "[] [] kulcsát -ra állítja. Ha nem meghatározott, eltávolíta a kulcsot -ról. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:130 msgid "change the keyword" msgstr "megváltoztatni a kulcsot" #: plugin.py:135 msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" "Ha rendelkezel a #csatorna,op képességgel, ez operátor státuszt ad minden -nek, amit megadtál. Ha nem adsz meg -et, ez neked ad operátor státuszt. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:147 msgid "op someone" msgstr "operátor státuszt adni valakinek" #: plugin.py:151 msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" "Ha rendelkezel a #csatorna,halfop képességgel, ez fél-operátor státuszt ad minden -nek, amit megadtál. Ha nem adsz meg -et, ez neked ad fél-operátor státuszt. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:163 msgid "halfop someone" msgstr "fél-operátor státuszt adni valakinek" #: plugin.py:168 msgid "" "[] [ ...]\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 "" "[] []\n" "\n" "Ha rendelkezel a #csatorna,voice képességgel, ez hangot ad minden megadott -nek. Ha nem adsz meg -et, ez neked ad hangot. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:190 msgid "voice someone" msgstr "hangot adni valakinek" #: plugin.py:195 msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" "Ha rendelkezel a #csatorna,op képességgel, ez eltávolítja az operátor státuszt minden megadott névről. Ha nincs név megadva, rólad távolítja el az operátor státuszt. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: 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 "Gyáván megtagadom, hogy eltávolítsam magamról az operátor státuszt. Ha el akarod távolítani rólam az operátor státuszt, mondd, hogy adjak operátor státuszt neked, és távolítsd el rólam." #: plugin.py:210 msgid "deop someone" msgstr "eltávolítani az operátor státuszt valakiről" #: plugin.py:215 msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" "Ha rendelkezel a #csatorna,halfop képességgel, ez eltávolítja a fél-operátor státuszt minden -ről, amit megadtál. Ha nincs név megadva, ez rólad távolítja el a fél-operátor státuszt. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: 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 "Gyáván megtagadom, hogy eltávolítsam magamról a fél-operátor státuszt. Ha el akarod távolítani rólam az operátor státuszt, mondd, hogy adjak operátor státuszt neked, és távolítsd el rólam a fél-operátor státuszt." #: plugin.py:230 msgid "dehalfop someone" msgstr "eltávolítani a fél-operátor státuszt valakiről" #: plugin.py:235 msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" "Ha rendelkezel a #csatorna,op képességgel, ez eltávolítja a hangot minden -ről, amit megadtál. Ha nincs név megadva, ez rólad távolítja el a hangot.. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: 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 "Gyáván megtagadom, hogy eltávolítsam magamról a hangot. Ha el akarod távolítani rólam a hangot, mondd, hogy adjak operátor státuszt neked, és távolítsd el rólam a hangot." #: 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" "Ha rendelkezel a #csatorna,op képességgel, ennek hatására a bot kilép majd visszalép a csaotnára. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: 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" "Kirúgja (ek)-et -ról -ért. Ha nincs megadva, a parancsot használó ember nevét használja okként. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:276 msgid "I cowardly refuse to kick myself." msgstr "Gváván megtagadom, hogy kirúgjam magam." #: plugin.py:281 msgid "The reason you gave is longer than the allowed length for a KICK reason on this server." msgstr "A megadott ok hosszabb a KICK parancs megengedett hosszúságánál ezen a szerveren." #: plugin.py:286 msgid "kick someone" msgstr "kirúgni valakit" #: 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" "Ha rendelkezel a #csatorna,op képességgel, ez kirúgja és kitiltja -et a megadott időre, vagy (ha 0 másodpercet adsz meg vagy nem adsz meg másodpercet) a végtelenségig tiltja a megadott embert. --exact csak a pontos hosztot tiltja ki; --nick csak a nevet tiltja ki; --user csak a felhasználót tiltja ki, és --host csak a hosztot tiltja ki. Kombinálhatod az opciókat ahogy szeretnéd. egy ok a kirúgáshoz." #: plugin.py:311 msgid "I cowardly refuse to kickban myself." msgstr "Gyáván megtagadom, hogy kirúgjam és kitiltsam magam." #: plugin.py:318 msgid "I haven't seen %s." msgstr "Nem láttam %s-t." #: plugin.py:326 msgid "I cowardly refuse to ban myself." msgstr "Gyáván megtagadom, hogy kitiltsam magam." #: plugin.py:353 msgid "%s has %s too, you can't ban them." msgstr "%s-nek is van %s, nem tilthatod ki őt." #: plugin.py:365 msgid "kick or ban someone" msgstr "kirúgni vagy kitiltani valakit" #: plugin.py:372 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" "Eltávolítja a tiltást -ról -ban. Ha nincs megadva, az összes tiltást eltávolítja -ról, ami illeszkedik rád. Főleg akkor hasznos, hogy eltávolítsd a tiltást magadról, ha váratlanul (vagy véletlenül) ki lettél tiltva a csatornáról. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:389 msgid "All bans on %s matching %s have been removed." msgstr "Minden tiltás %s-ban, ami illeszkedik %s-ra el lett távolítva." #: plugin.py:393 msgid "No bans matching %s were found on %s." msgstr "Nem található %s-ra illeszkedő tiltás %s-ban." #: plugin.py:396 msgid "unban someone" msgstr "eltávolítani a tiltást valakiről" #: plugin.py:401 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" "Ha rendelkezel a #csatorna,op képességgel, meghívja -et -ra. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:410 msgid "invite someone" msgstr "meghívni valakit" #: plugin.py:429 msgid "%s is already in %s." msgstr "%s már %s-ban van." #: plugin.py:436 msgid "There is no %s on this network." msgstr "Nincs %s ezen a hálózaton." #: plugin.py:448 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" "Ha rendelkezel a #csatorna,op képességgel, ez \"némítja\" a botot, ezzel némáva teszi azt és nem fog válaszolni a csatornában végrehajtott kérésekre. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:463 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" "Ha rendelkezel a #csatorna,op képességgel, ez megszünteti a bot némítását, így az újra válaszol a csatornában vérgehajtott kérésekre. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:478 msgid "" "takes no arguments\n" "\n" " Returns the channels in which this bot is lobotomized.\n" " " msgstr "" "paraméter nélküli\n" "\n" "Kiírja a csatornákat, ahol a bot némítva van." #: plugin.py:493 msgid "I'm currently lobotomized in %L." msgstr "Jelenleg némítva vagyok %L-ban." #: plugin.py:496 msgid "I'm not currently lobotomized in any channels that you're in." msgstr "Jelenleg nem vagyok némítva egy csatornában sem, ahol vagy." #: plugin.py:503 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" "Ha rendelkezel a #csatorna,op képességgel, ez megtiltja -nak (vagy jelenlegi hosztjának), hogy a botot használja a csatornában. Más bővítmények kényszeríthetik a tiltást azzal, hogy kitiltják a felhasználókat, akikre illeszkedik a hoszt amikor belépnek. egy nem kötelező paaméter, meghatározza, hogy mikor (hány \"másodperc múlva\") járjon le a tiltás; ha nincs megadva, a tiltás soha nem fog automatikusan lejárni. csak akkor szükséges, ha az üzenet nem a csaotnában van elküldve." #: plugin.py:523 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" "Ha rendelkezel a #csatorna,op képességgel, ez eltávolítja a tiltást -ról. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:535 msgid "There are no persistent bans for that hostmask." msgstr "Nincsenek tiltások erre a hosztra." #: plugin.py:540 #, fuzzy msgid "" "[]\n" "\n" " If you have the #channel,op capability, this will show you the\n" " current persistent bans on the .\n" " " msgstr "" "[]\n" "\n" "Ha rendelkezel a #csatorna,op képességgel, ez megmutatja neked a jelenlegi tiltásokat -n." #: plugin.py:550 msgid "%q (expires %t)" msgstr "%q (lejár %t)" #: plugin.py:553 msgid "%q (never expires)" msgstr "%q (soha nem jár le)" #: plugin.py:557 msgid "There are no persistent bans on %s." msgstr "Nincsenek tiltások %s-on." #: plugin.py:564 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" "Ha rendelkezel a #csatorna,op képességgel, ez mellőzi -ot vagy jelenlegi hosztját. egy nem kötelező paraméter, meghatározza, hogy mikor (hány \"másodperc múlva\" járjon le a mellőzés; ha nincs megadva, a mellőzés soha nem fog automatikusan lejárni. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:582 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" "Ha rendelkezel a #csatorna,op képességgel, ez eltávolítja a mellőzést -ról a csatornában. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:594 msgid "There are no ignores for that hostmask." msgstr "Nincsenek mellőzések erre a hosztra." #: plugin.py:599 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" "Kiírja a hosztokat, amelyeket a bot mellőz a megadott csatornában. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:608 msgid "I'm not currently ignoring any hostmasks in %q" msgstr "Nem mellőzők egy hosztot sem %q-ban." #: plugin.py:619 #, fuzzy msgid "" "[] [ ...]\n" "\n" " If you have the #channel,op capability, this will give the\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" "Ha rendelkezel a #csatorna,op képességgel, ez felhasználónak (vagy felhasználójának) a képességet a csatornában. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:635 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" "Ha rendelkezel a #csatorna,op képességgel, ez eltávolítja a jelenleg -ként bejelentkezett felhasználóról (vagy a felhasználóról, akire illeszkedik) a képességet a csatornában. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:654 msgid "That user didn't have the %L %s." msgstr "A felhasználónak nem volt a(z) %L %s." #: plugin.py:663 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" "Ha rendelkezel a #csatorna,op képességgel, ez az alapértelmezett választ a nem erővel kapcsolatos (vagyis nem {op, halfop, voice}) képességeket a megadott értékre állítja. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:681 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" "Ha rendelkezel a #csatorna,op képességgel, ez hozzáadja -et a csatorna képességeihez. csak akkor szükséges, ha az üzenet nem, a csatornában van elküldve." #: plugin.py:696 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" "Ha rendelkezel a #csatorna,op képességgel, ez eltávolítja a csatorna képességét, így a felhasználók saját képességei vagy a csatorna alapértelmezett képességei lesznek előnyben. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:712 msgid "capability" msgstr "képesség" #: plugin.py:715 msgid "I do not know about the %L %s." msgstr "Nem tudok a %L %s-ról." #: plugin.py:722 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" "Kiírja képességeit. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:734 msgid "" "[] [] []\n" "\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" "Ha rendelkezel a #csatorna,op képességgel, ez letiltja -ot -ban. Ha meg van adva, csak a megadott bővítményben lesz letiltva. Ha csak van megadva, minden parancs le lesz tiltva a megadott bővítményben. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:750 #: plugin.py:789 msgid "The %s plugin does not have a command called %s." msgstr "A %s bővítménynek nincs %s nevű parancsa." #: plugin.py:757 #: plugin.py:796 msgid "No plugin or command named %s could be found." msgstr "Nem található bővítmény vagy parancs %s néven." #: plugin.py:773 msgid "" "[] [] []\n" "\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" "Ha rendelkezel a #csatorna,op képességgel, ez engedélyezi -ot -ban ha le lett tiltva. Ha meg van adva, csak a megadott bővítményben lesz engedélyezve." #: plugin.py:810 msgid "%s was not disabled." msgstr "%s nem volt letiltva." #: plugin.py:819 msgid "" "[] [--count]\n" "\n" " Returns the nicks in . is only necessary if the\n" " message isn't sent in the channel itself. Returns only the number of\n" " nicks if --count option is provided.\n" " " msgstr "" "[] [--count]\n" "\n" "Kiírja a neveket -ban. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve. Ha a --count opció meg van adva, csak a nevek számát írja ki." #: plugin.py:831 msgid "You don't have access to that information." msgstr "Nincs hozzáférésed ehhez az információhoz." #: plugin.py:845 msgid "" "Internal message for notifying all the #channel,ops in a channel of\n" " a given situation." msgstr "Belső üzenet #csatorna,op-ok értesítésére egy adott szituációban." #: plugin.py:848 msgid "Alert to all %s ops: %s" msgstr "Riasztás minden %s operátornak: %s" #: plugin.py:850 msgid " (from %s)" msgstr "(%s-tól)" #: plugin.py:858 msgid "" "[] \n" "\n" " Sends to all the users in who have the ,op\n" " capability.\n" " " msgstr "" "[] [ ...]\n" "\n" "Ha rendelkezel a #csatorna,halfop képességgel, ez fél-operátor státuszt ad minden -nek, amit megadsz. Ha nem adsz meg -et, ez neked ad fél-operátor státuszt. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." limnoria-2020.03.17/plugins/Channel/locales/it.po0000644000175000017500000007067213634634532020762 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2012-06-09 09:33+0200\n" "Last-Translator: skizzhg \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:48 msgid "" "Determines whether the bot will always try to\n" " rejoin a channel whenever it's kicked from the channel." msgstr "Determina se il bot proverà a rientrare in un canale ogni volta che viene espulso." #: plugin.py:69 #, docstring msgid "" "[] [ ...]\n" "\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" " Imposta in inviando l'argomento fornito. \n" " è necessario solo se il messaggio non viene inviato nel canale stesso." " " #: plugin.py:76 msgid "change the mode" msgstr "modificare il mode" #: plugin.py:80 #, docstring msgid "" "[] []\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 "" "[] []\n" "\n" " Imposta di canale. Se è uguale 0 o non viene\n" " fornito, questo viene rimosso. è necessario solo se il\n" " messaggio non viene inviato nel canale stesso." " " #: plugin.py:90 msgid "change the limit" msgstr "modificare il limite" #: plugin.py:95 #, docstring msgid "" "[]\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 "" "[]\n" "\n" " Imposta +m su facendo sì che solo gli operatori e chi ha il\n" " voice possa inviare messaggi. è necessario solo se il messaggio\n" " non viene inviato nel canale stesso.\n" " " #: plugin.py:102 msgid "moderate the channel" msgstr "moderare il canale" #: plugin.py:106 #, docstring msgid "" "[]\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 "" "[]\n" "\n" " Imposta -m su facendo sì che tutti possano inviare messaggi.\n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:114 msgid "unmoderate the channel" msgstr "de-moderare il canale" #: plugin.py:118 #, docstring msgid "" "[] []\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 "" "[] []\n" "\n" " Imposta in . Se non viene specificata, rimuove\n" " quella richiesta per entrare. è necessario solo se il messaggio\n" " non viene inviato nel canale stesso.\n" " " #: plugin.py:130 msgid "change the keyword" msgstr "cambiare la password" #: plugin.py:135 #, docstring msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" " Se hai la capacità #canale,op permette di dare lo stato di operatore\n" " a tutti i specificati; se nessun è fornito lo darà a te.\n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:147 msgid "op someone" msgstr "dare l'op a qualcuno" #: plugin.py:151 #, docstring msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" " Se hai la capacità #canale,halfop permette di dare lo stato di halfop\n" " a tutti i specificati; se nessun è fornito lo darà a te.\n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:163 msgid "halfop someone" msgstr "dare l'halfop a qualcuno" #: plugin.py:184 #, docstring msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" " Se hai la capacità #canale,voice permette di dare il voice a tutti i\n" " specificati; se nessun è fornito lo darà a te. \n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:192 msgid "voice someone" msgstr "dare il voice a qualcuno" #: plugin.py:197 #, docstring msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" " Se hai la capacità #canale,op permette di rimuove i privilegi di operatore a tutti\n" " i nick specificati; se nessun nick è fornito li rimuove a chi usa il comando.\n" " " #: plugin.py:204 msgid "I cowardly refuse to deop myself. If you really want me deopped, tell me to op you and then deop me yourself." msgstr "Codardamente mi rifiuto di rimuovermi lo stato di operatore; se davvero lo vuoi, dimmi di darlo a te e poi toglimelo." #: plugin.py:212 msgid "deop someone" msgstr "rimuovere l'op a qualcuno" #: plugin.py:217 #, docstring msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" " Se hai la capacità #canale,op permette di rimuove i privilegi di halfop a tutti\n" " i nick specificati; se nessun nick è fornito li rimuove a chi usa il comando.\n" " " #: plugin.py:224 msgid "I cowardly refuse to dehalfop myself. If you really want me dehalfopped, tell me to op you and then dehalfop me yourself." msgstr "Codardamente mi rifiuto di rimuovermi lo stato di halfop; se davvero lo vuoi, dimmi di dare l'op a te e poi toglimelo." #: plugin.py:232 msgid "dehalfop someone" msgstr "rimuovere l'halfop a qualcuno" #: plugin.py:237 #, docstring msgid "" "[] [ ...]\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 "" "[] [ ...]\n" "\n" " Se hai la capacità #canale,op permette di rimuovere il voice a tutti\n" " i nick specificati; se nessun nick è fornito lo rimuove a chi usa il comando.\n" " " #: plugin.py:244 msgid "I cowardly refuse to devoice myself. If you really want me devoiced, tell me to op you and then devoice me yourself." msgstr "Codardamente mi rifiuto di rimuovermi il voice; se davvero lo vuoi, dimmi di dare l'op a te e poi toglimelo." #: plugin.py:253 #, docstring 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" " Se hai la capacità #canale,op invierà il comando \"cycle\" al bot (PART e JOIN).\n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:266 #, docstring 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" " Caccia (kick) da con . Se non è fornito,\n" " utilizza il nick di chi ha usato il comando. è necessario solo\n" " se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:274 msgid "I cowardly refuse to kick myself." msgstr "Codardamente mi rifiuto di cacciare me stesso." #: plugin.py:279 msgid "The reason you gave is longer than the allowed length for a KICK reason on this server." msgstr "Il motivo che hai dato è più lungo della lunghezza consentita da questo server." #: plugin.py:284 msgid "kick someone" msgstr "cacciare (kick) qualcuno" #: plugin.py:290 #, docstring 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" " Se hai la capacità #canale,op permette di espellere (kickban) per\n" " il numero di secondi specificati, se il valore è uguale a 0 o non viene\n" " fornito un numero, il ban sarà a tempo indeterminato. --exact si applica\n" " alla hostmask precisa, --nick solo al nick, --user al nome utente e\n" " --host al nome dell'host. È possibile utilizzare più di un'opzione;\n" " è la ragione da indicare per il kick. è necessario solo\n" " se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:309 msgid "I cowardly refuse to kickban myself." msgstr "Codardamente mi rifiuto di espellere (kickban) me stesso." #: plugin.py:316 msgid "I haven't seen %s." msgstr "Non ho mai visto %s." #: plugin.py:324 msgid "I cowardly refuse to ban myself." msgstr "Codardamente mi rifiuto di bannare stesso." #: plugin.py:351 msgid "%s has %s too, you can't ban them." msgstr "anche %s ha %s, non puoi bannarlo/a." #: plugin.py:363 msgid "kick or ban someone" msgstr "cacciare (kick) o bannare qualcuno" #: plugin.py:370 #, docstring 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" " Rimuove il ban di su . Se non è specificata,\n" " rimuove qualsiasi hostmask attualmente bannata che corrisponde alla propria.\n" " Particolarmente utile per toglierlo a sé stessi quando si viene bannati\n" " inaspettatamente o accidentalmente. è necessario solo se il\n" " messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:387 msgid "All bans on %s matching %s have been removed." msgstr "Tutti i ban su %s che corrisopndono a %s sono stati rimossi." #: plugin.py:391 msgid "No bans matching %s were found on %s." msgstr "Non è stato trovato alcun ban corrispondente a %s su %s." #: plugin.py:394 msgid "unban someone" msgstr "rimuovere il ban a qualcuno" #: plugin.py:399 #, docstring 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" " Se hai la capacità #canale,op permette di invitare in .\n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:408 msgid "invite someone" msgstr "invitare qualcuno" #: plugin.py:427 msgid "%s is already in %s." msgstr "%s è già in %s." #: plugin.py:434 msgid "There is no %s on this network." msgstr "Non c'è nessun %s su questa rete." #: plugin.py:446 #, docstring 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" " Se hai la capacità #canale,op permette di \"lobotomizzare\" il bot\n" " rendendolo silenzioso (non risponderà alle richieste fatte in canale).\n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:461 #, docstring 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" " Se hai la capacità #canale,op permette di \"de-lobotomizzare\" il bot\n" " rendendolo attivo (risponderà nuovamente alle richieste fatte in canale).\n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:476 #, docstring msgid "" "takes no arguments\n" "\n" " Returns the channels in which this bot is lobotomized.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Riporta l'elenco dei canali nei quali il bot è lobotomizzato.\n" " " #: plugin.py:491 msgid "I'm currently lobotomized in %L." msgstr "Sono attualmente lobotomizzato in %L." #: plugin.py:494 msgid "I'm not currently lobotomized in any channels that you're in." msgstr "Al momento non sono lobotomizzato in nessun canale in cui sei." #: plugin.py:501 #, docstring 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" " Se hai la capacità #canale,op applica un ban permanente all'\n" " specificata (o quella associata a ). Altri plugin possono rafforzare\n" " questo ban effettuando un ban su utenti con una hostmask corrispondente.\n" " è un argomento opzionale per specificare quando (in \"secondi\n" " a partire da subito\") scadrà il ban; se non fornito, questo non scadrà mai.\n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:521 #, docstring 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" " Se hai la capacità #canale,op permette di rimuovere il ban permanente su .\n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:533 msgid "There are no persistent bans for that hostmask." msgstr "Non ci sono ban permanenti per questa hostmask." #: plugin.py:538 #, docstring msgid "" "[]\n" "\n" " If you have the #channel,op capability, this will show you the\n" " current persistent bans on the .\n" " " msgstr "" "[]\n" "\n" " Se hai la capacità #canale,op mostra gli attuali ban permanenti su .\n" " " #: plugin.py:548 msgid "%q (expires %t)" msgstr "%q (scade il %t)" #: plugin.py:551 msgid "%q (never expires)" msgstr "%q (non scade)" #: plugin.py:555 msgid "There are no persistent bans on %s." msgstr "Non ci sono ban permanenti su %s." #: plugin.py:562 #, docstring 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" " Se hai la capacità #canale,op permette di impostare un ignore permanente\n" " su (o quella associata a ). è un argomento\n" " opzionale per specificare quando (in \"secondi a partire da subito\") scadrà\n" " l'ignore; se non fornito, questo non scadrà mai. è necessario solo\n" " se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:580 #, docstring 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" " Se hai la capacità #canale,op permette di rimuovere l'ignore permanente su .\n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:592 msgid "There are no ignores for that hostmask." msgstr "Non ci sono ignore per questa hostmask." #: plugin.py:597 #, docstring 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" " Elenca le hostmask ignorate dal bot sul dato canale. è\n" " necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:606 msgid "I'm not currently ignoring any hostmasks in %q" msgstr "Al momento non sto ignorando nessuna hostmasks in %q" #: plugin.py:617 #, docstring msgid "" "[] [ ...]\n" "\n" " If you have the #channel,op capability, this will give the\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" " Se hai la capacità #canale,op permette di dare a \n" " (o quello a cui corrisponde ) sul canale. \n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:633 #, docstring 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" " Se hai la capacità #canale,op permette di rimuovere all'utente\n" " attualmente identificato da (o quello a cui corrisponde )\n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:652 msgid "That user didn't have the %L %s." msgstr "Questo utente non ha la %L %s." #: plugin.py:661 #, docstring 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" " Se hai la capacità #canale,op permette di impostare la risposta predefinita\n" " relativa alle capacità senza potere (cioè non op, halfop, voice). \n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:679 #, docstring 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" " Se hai la capacità #canale,op permette di aggiungere la \n" " del canale per tutti gli utenti nel canale. è necessario\n" " solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:694 #, docstring 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" " Se hai la capacità #canale,op permette di rimuovere la \n" " del canale in modo che avranno la precedenza tutte quelle specifiche\n" " di ogni utente o quella predefinita del canale. è necessario\n" " solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:710 msgid "capability" msgstr "capacità" #: plugin.py:713 msgid "I do not know about the %L %s." msgstr "Non so nulla a proposito di %L %s." #: plugin.py:720 #, docstring 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" " Riporta le capacità presenti su . è necessario\n" " solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:732 #, docstring msgid "" "[] [] []\n" "\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" " Se hai la capacità #canale,op permette di disabilitare in .\n" " Se è specificato, sarà disabilitato solo per quel plugin.\n" " Se viene fornito solo , disabilita tutti i comandi del dato plugin.\n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:748 plugin.py:787 msgid "The %s plugin does not have a command called %s." msgstr "Il plugin %s non ha un comando chiamato %s." #: plugin.py:755 plugin.py:794 msgid "No plugin or command named %s could be found." msgstr "Non è stato trovato nessun plugin o comando chiamato %s." #: plugin.py:771 #, docstring msgid "" "[] [] []\n" "\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" " Se hai la capacità #canale,op permette di abilitare (se questo\n" " è stato disabilitato) in . Se è specificato, sarà\n" " abilitato solo per quel plugin. Se viene fornito solo , abilita tutti\n" " i comandi del dato plugin. è necessario solo se il messaggio non viene\n" " inviato nel canale stesso.\n" " " #: plugin.py:808 msgid "%s was not disabled." msgstr "%s non è stato disabilitato." #: plugin.py:817 #, docstring msgid "" "[] [--count]\n" "\n" " Returns the nicks in . is only necessary if the\n" " message isn't sent in the channel itself. Returns only the number of\n" " nicks if --count option is provided.\n" " " msgstr "" "[] [--count]\n" "\n" " Riporta l'elenco dei nick in . è necessario solo se\n" " il messaggio non viene inviato nel canale stesso. Se viene fornita\n" " l'opzione --count, mostra solo il numero dei nick.\n" " " #: plugin.py:829 msgid "You don't have access to that information." msgstr "Non hai accesso a questa informazione." #: plugin.py:843 #, docstring msgid "" "Internal message for notifying all the #channel,ops in a channel of\n" " a given situation." msgstr "" "Messaggio interno per notificare una data situazione a tutti gli operatori (#canale,op) in un canale.\n" #: plugin.py:846 msgid "Alert to all %s ops: %s" msgstr "Avviso a tutti gli op di %s: %s" #: plugin.py:848 msgid " (from %s)" msgstr " (da %s)" #: plugin.py:856 #, docstring msgid "" "[] \n" "\n" " Sends to all the users in who have the ,op\n" " capability.\n" " " msgstr "" "[] \n" "\n" " Invia a tutti gli utenti in che hanno la capacità ,op.\n" " " limnoria-2020.03.17/plugins/Channel/plugin.py0000644000175000017500000012563713634634532020236 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009-2012, James McCoy # 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 sys import fnmatch import time import supybot.conf as conf import supybot.ircdb as ircdb import supybot.utils as utils from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.schedule as schedule import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Channel') class Channel(callbacks.Plugin): """This plugin provides various commands for channel management, such as setting modes and channel-wide bans/ignores/capabilities. This is a core Supybot plugin that should not be removed!""" def __init__(self, irc): self.__parent = super(Channel, self) self.__parent.__init__(irc) self.invites = {} def doKick(self, irc, msg): channel = msg.channel network = irc.network if msg.args[1] == irc.nick: if self.registryValue('alwaysRejoin', channel, network): delay = self.registryValue('rejoinDelay', channel, network) networkGroup = conf.supybot.networks.get(irc.network) if delay: def f(): irc.sendMsg(networkGroup.channels.join(channel)) schedule.addEvent(f, time.time() + delay) self.log.info('Kicked from %s @ %s by %s. ' 'Rejoining after %s seconds.', channel, network, msg.prefix, delay) else: self.log.info('Kicked from %s @ %s by %s. Rejoining.', channel, network, msg.prefix) irc.sendMsg(networkGroup.channels.join(channel)) else: self.log.info('Kicked from %s @ %s by %s. Not auto-rejoining.', channel, network, msg.prefix) def _sendMsg(self, irc, msg): irc.queueMsg(msg) irc.noReply() def _sendMsgs(self, irc, nicks, f): numModes = irc.state.supported.get('modes', 1) for i in range(0, len(nicks), numModes): irc.queueMsg(f(nicks[i:i + numModes])) irc.noReply() @internationalizeDocstring def mode(self, irc, msg, args, channel, modes): """[] [ ...] Sets the mode in to , sending the arguments given. is only necessary if the message isn't sent in the channel itself. """ self._sendMsg(irc, ircmsgs.mode(channel, modes)) mode = wrap(mode, ['op', ('haveHalfop+', _('change the mode')), many('something')]) @internationalizeDocstring def limit(self, irc, msg, args, channel, limit): """[] [] Sets the channel limit to . If is 0, or isn't given, removes the channel limit. is only necessary if the message isn't sent in the channel itself. """ if limit: self._sendMsg(irc, ircmsgs.mode(channel, ['+l', limit])) else: self._sendMsg(irc, ircmsgs.mode(channel, ['-l'])) limit = wrap(limit, ['op', ('haveOp', _('change the limit')), additional('nonNegativeInt', 0)]) @internationalizeDocstring def moderate(self, irc, msg, args, channel): """[] Sets +m on , making it so only ops and voiced users can send messages to the channel. is only necessary if the message isn't sent in the channel itself. """ self._sendMsg(irc, ircmsgs.mode(channel, ['+m'])) moderate = wrap(moderate, ['op', ('haveHalfop+', _('moderate the channel'))]) @internationalizeDocstring def unmoderate(self, irc, msg, args, channel): """[] Sets -m on , making it so everyone can send messages to the channel. is only necessary if the message isn't sent in the channel itself. """ self._sendMsg(irc, ircmsgs.mode(channel, ['-m'])) unmoderate = wrap(unmoderate, ['op', ('haveHalfop+', _('unmoderate the channel'))]) @internationalizeDocstring def key(self, irc, msg, args, channel, key): """[] [] Sets the keyword in to . If is not given, removes the keyword requirement to join . is only necessary if the message isn't sent in the channel itself. """ networkGroup = conf.supybot.networks.get(irc.network) networkGroup.channels.key.get(channel).setValue(key) if key: self._sendMsg(irc, ircmsgs.mode(channel, ['+k', key])) else: self._sendMsg(irc, ircmsgs.mode(channel, ['-k'])) key = wrap(key, ['op', ('haveHalfop+', _('change the keyword')), additional('somethingWithoutSpaces', '')]) @internationalizeDocstring def op(self, irc, msg, args, channel, nicks): """[] [ ...] If you have the #channel,op capability, this will give all the s you provide ops. If you don't provide any s, this will op you. is only necessary if the message isn't sent in the channel itself. """ if not nicks: nicks = [msg.nick] def f(L): return ircmsgs.ops(channel, L) self._sendMsgs(irc, nicks, f) op = wrap(op, ['op', ('haveOp', _('op someone')), any('nickInChannel')]) @internationalizeDocstring def halfop(self, irc, msg, args, channel, nicks): """[] [ ...] If you have the #channel,halfop capability, this will give all the s you provide halfops. If you don't provide any s, this will give you halfops. is only necessary if the message isn't sent in the channel itself. """ if not nicks: nicks = [msg.nick] def f(L): return ircmsgs.halfops(channel, L) self._sendMsgs(irc, nicks, f) halfop = wrap(halfop, ['halfop', ('haveOp', _('halfop someone')), any('nickInChannel')]) def _voice(self, irc, msg, args, channel, nicks, fn): if nicks: if len(nicks) == 1 and msg.nick in nicks: capability = 'voice' else: capability = 'op' else: nicks = [msg.nick] capability = 'voice' capability = ircdb.makeChannelCapability(channel, capability) if ircdb.checkCapability(msg.prefix, capability): def f(L): return fn(channel, L) self._sendMsgs(irc, nicks, f) else: irc.errorNoCapability(capability) def voice(self, irc, msg, args, channel, nicks): """[] [ ...] If you have the #channel,voice capability, this will voice all the s you provide. If you don't provide any s, this will voice you. is only necessary if the message isn't sent in the channel itself. """ self._voice(irc, msg, args, channel, nicks, ircmsgs.voices) voice = wrap(voice, ['channel', ('haveHalfop+', _('voice someone')), any('nickInChannel')]) @internationalizeDocstring def deop(self, irc, msg, args, channel, nicks): """[] [ ...] If you have the #channel,op capability, this will remove operator privileges from all the nicks given. If no nicks are given, removes operator privileges from the person sending the message. """ if irc.nick in nicks: irc.error(_('I cowardly refuse to deop myself. If you really ' 'want me deopped, tell me to op you and then deop me ' 'yourself.'), Raise=True) if not nicks: nicks = [msg.nick] def f(L): return ircmsgs.deops(channel, L) self._sendMsgs(irc, nicks, f) deop = wrap(deop, ['op', ('haveOp', _('deop someone')), any('nickInChannel')]) @internationalizeDocstring def dehalfop(self, irc, msg, args, channel, nicks): """[] [ ...] If you have the #channel,op capability, this will remove half-operator privileges from all the nicks given. If no nicks are given, removes half-operator privileges from the person sending the message. """ if irc.nick in nicks: irc.error(_('I cowardly refuse to dehalfop myself. If you really ' 'want me dehalfopped, tell me to op you and then ' 'dehalfop me yourself.'), Raise=True) if not nicks: nicks = [msg.nick] def f(L): return ircmsgs.dehalfops(channel, L) self._sendMsgs(irc, nicks, f) dehalfop = wrap(dehalfop, ['halfop', ('haveOp', _('dehalfop someone')), any('nickInChannel')]) @internationalizeDocstring def devoice(self, irc, msg, args, channel, nicks): """[] [ ...] If you have the #channel,op capability, this will remove voice from all the nicks given. If no nicks are given, removes voice from the person sending the message. """ self._voice(irc, msg, args, channel, nicks, ircmsgs.devoices) devoice = wrap(devoice, ['channel', ('haveOp', 'devoice someone'), any('nickInChannel')]) @internationalizeDocstring def cycle(self, irc, msg, args, channel, reason): """[] [] If you have the #channel,op capability, this will cause the bot to "cycle", or PART and then JOIN the channel. is only necessary if the message isn't sent in the channel itself. If is not specified, the default part message specified in supybot.plugins.Channel.partMsg will be used. No part message will be used if neither a cycle reason nor a default part message is given. """ reason = (reason or self.registryValue("partMsg", channel, irc.network)) reason = ircutils.standardSubstitute(irc, msg, reason) self._sendMsg(irc, ircmsgs.part(channel, reason)) networkGroup = conf.supybot.networks.get(irc.network) self._sendMsg(irc, networkGroup.channels.join(channel)) cycle = wrap(cycle, ['op', additional('text')]) @internationalizeDocstring def kick(self, irc, msg, args, channel, nicks, reason): """[] [, , ...] [] Kicks (s) from for . If isn't given, uses the nick of the person making the command as the reason. is only necessary if the message isn't sent in the channel itself. """ if utils.iter.any(lambda n: ircutils.strEqual(n, irc.nick), nicks): irc.error(_('I cowardly refuse to kick myself.'), Raise=True) if not reason: reason = msg.nick kicklen = irc.state.supported.get('kicklen', sys.maxsize) if len(reason) > kicklen: irc.error(_('The reason you gave is longer than the allowed ' 'length for a KICK reason on this server.'), Raise=True) for nick in nicks: self._sendMsg(irc, ircmsgs.kick(channel, nick, reason)) kick = wrap(kick, ['op', ('haveHalfop+', _('kick someone')), commalist('nickInChannel'), additional('text')]) @internationalizeDocstring def kban(self, irc, msg, args, channel, optlist, bannedNick, expiry, reason): """[] [--{exact,nick,user,host}] [] [] If you have the #channel,op capability, this will kickban for as many seconds as you specify, or else (if you specify 0 seconds or don't specify a number of seconds) it will ban the person indefinitely. --exact bans only the exact hostmask; --nick bans just the nick; --user bans just the user, and --host bans just the host. You can combine these options as you choose. is a reason to give for the kick. is only necessary if the message isn't sent in the channel itself. """ self._ban(irc, msg, args, channel, optlist, bannedNick, expiry, reason, True) kban = wrap(kban, ['op', getopts({'exact':'', 'nick':'', 'user':'', 'host':''}), ('haveHalfop+', _('kick or ban someone')), 'nickInChannel', optional('expiry', 0), additional('text')]) @internationalizeDocstring def iban(self, irc, msg, args, channel, optlist, bannedNick, expiry): """[] [--{exact,nick,user,host}] [] If you have the #channel,op capability, this will ban for as many seconds as you specify, otherwise (if you specify 0 seconds or don't specify a number of seconds) it will ban the person indefinitely. --exact can be used to specify an exact hostmask. You can combine the exact, nick, user, and host options as you choose. is only necessary if the message isn't sent in the channel itself. """ self._ban(irc, msg, args, channel, optlist, bannedNick, expiry, None, False) iban = wrap(iban, ['op', getopts({'exact':'', 'nick':'', 'user':'', 'host':''}), ('haveHalfop+', _('ban someone')), first('nick', 'hostmask'), optional('expiry', 0)]) def _ban(self, irc, msg, args, channel, optlist, target, expiry, reason, kick): # Check that they're not trying to make us kickban ourself. if irc.isNick(target): bannedNick = target try: bannedHostmask = irc.state.nickToHostmask(target) banmaskstyle = conf.supybot.protocols.irc.banmask banmask = banmaskstyle.makeBanmask(bannedHostmask, [o[0] for o in optlist]) except KeyError: if not conf.supybot.protocols.irc.strictRfc() and \ target.startswith('$'): # Select the last part, or the whole target: bannedNick = target.split(':')[-1] banmask = bannedHostmask = target else: irc.error(format(_('I haven\'t seen %s.'), bannedNick), Raise=True) else: bannedNick = ircutils.nickFromHostmask(target) banmask = bannedHostmask = target if not irc.isNick(bannedNick): self.log.warning('%q tried to kban a non nick: %q', msg.prefix, bannedNick) raise callbacks.ArgumentError elif bannedNick == irc.nick: self.log.warning('%q tried to make me kban myself.', msg.prefix) irc.error(_('I cowardly refuse to kickban myself.')) return if not reason: reason = msg.nick capability = ircdb.makeChannelCapability(channel, 'op') # Check (again) that they're not trying to make us kickban ourself. if ircutils.hostmaskPatternEqual(banmask, irc.prefix): if ircutils.hostmaskPatternEqual(bannedHostmask, irc.prefix): self.log.warning('%q tried to make me kban myself.',msg.prefix) irc.error(_('I cowardly refuse to ban myself.')) return else: self.log.warning('Using exact hostmask since banmask would ' 'ban myself.') banmask = bannedHostmask # Now, let's actually get to it. Check to make sure they have # #channel,op and the bannee doesn't have #channel,op; or that the # bannee and the banner are both the same person. def doBan(): if irc.state.channels[channel].isOp(bannedNick): irc.queueMsg(ircmsgs.deop(channel, bannedNick)) irc.queueMsg(ircmsgs.ban(channel, banmask)) if kick: irc.queueMsg(ircmsgs.kick(channel, bannedNick, reason)) if expiry > 0: def f(): if channel in irc.state.channels and \ banmask in irc.state.channels[channel].bans: irc.queueMsg(ircmsgs.unban(channel, banmask)) schedule.addEvent(f, expiry) if bannedNick == msg.nick: doBan() elif ircdb.checkCapability(msg.prefix, capability): if ircdb.checkCapability(bannedHostmask, capability) and \ not ircdb.checkCapability(msg.prefix, 'owner'): self.log.warning('%s tried to ban %q, but both have %s', msg.prefix, bannedHostmask, capability) irc.error(format(_('%s has %s too, you can\'t ban ' 'them.'), bannedNick, capability)) else: doBan() else: self.log.warning('%q attempted kban without %s', msg.prefix, capability) irc.errorNoCapability(capability) @internationalizeDocstring def unban(self, irc, msg, args, channel, hostmask): """[] [] Unbans on . If is not given, unbans any hostmask currently banned on that matches your current hostmask. Especially useful for unbanning yourself when you get unexpectedly (or accidentally) banned from the channel. is only necessary if the message isn't sent in the channel itself. """ if hostmask == '--all': bans = irc.state.channels[channel].bans self._sendMsg(irc, ircmsgs.unbans(channel, bans)) elif hostmask: self._sendMsg(irc, ircmsgs.unban(channel, hostmask)) else: bans = [] for banmask in irc.state.channels[channel].bans: if ircutils.hostmaskPatternEqual(banmask, msg.prefix): bans.append(banmask) if bans: irc.queueMsg(ircmsgs.unbans(channel, bans)) irc.replySuccess(format(_('All bans on %s matching %s ' 'have been removed.'), channel, msg.prefix)) else: irc.error(_('No bans matching %s were found on %s.') % (msg.prefix, channel)) unban = wrap(unban, ['op', ('haveHalfop+', _('unban someone')), additional( first('hostmask', ('literal', '--all')))]) @internationalizeDocstring def listbans(self, irc, msg, args, channel): """[] List all bans on the channel. If is not given, it defaults to the current channel.""" irc.replies(irc.state.channels[channel].bans or [_('No bans.')]) listbans = wrap(listbans, ['channel']) @internationalizeDocstring def invite(self, irc, msg, args, channel, nick): """[] If you have the #channel,op capability, this will invite to join . is only necessary if the message isn't sent in the channel itself. """ nick = nick or msg.nick self._sendMsg(irc, ircmsgs.invite(nick, channel)) self.invites[(irc.getRealIrc(), ircutils.toLower(nick))] = irc invite = wrap(invite, ['op', ('haveHalfop+', _('invite someone')), additional('nick')]) def do341(self, irc, msg): (foo, nick, channel) = msg.args nick = ircutils.toLower(nick) replyIrc = self.invites.pop((irc, nick), None) if replyIrc is not None: self.log.info('Inviting %s to %s by command of %s.', nick, channel, replyIrc.msg.prefix) replyIrc.replySuccess() else: self.log.info('Inviting %s to %s.', nick, channel) def do443(self, irc, msg): (foo, nick, channel, foo) = msg.args nick = ircutils.toLower(nick) replyIrc = self.invites.pop((irc, nick), None) if replyIrc is not None: replyIrc.error(format(_('%s is already in %s.'), nick, channel)) def do401(self, irc, msg): nick = msg.args[1] nick = ircutils.toLower(nick) replyIrc = self.invites.pop((irc, nick), None) if replyIrc is not None: replyIrc.error(format(_('There is no %s on this network.'), nick)) def do504(self, irc, msg): nick = msg.args[1] nick = ircutils.toLower(nick) replyIrc = self.invites.pop((irc, nick), None) if replyIrc is not None: replyIrc.error(format('There is no %s on this server.', nick)) class lobotomy(callbacks.Commands): @internationalizeDocstring def add(self, irc, msg, args, channel): """[] If you have the #channel,op capability, this will "lobotomize" the bot, making it silent and unanswering to all requests made in the channel. is only necessary if the message isn't sent in the channel itself. """ c = ircdb.channels.getChannel(channel) c.lobotomized = True ircdb.channels.setChannel(channel, c) irc.replySuccess() add = wrap(add, ['op']) @internationalizeDocstring def remove(self, irc, msg, args, channel): """[] If you have the #channel,op capability, this will unlobotomize the bot, making it respond to requests made in the channel again. is only necessary if the message isn't sent in the channel itself. """ c = ircdb.channels.getChannel(channel) c.lobotomized = False ircdb.channels.setChannel(channel, c) irc.replySuccess() remove = wrap(remove, ['op']) @internationalizeDocstring def list(self, irc, msg, args): """takes no arguments Returns the channels in which this bot is lobotomized. """ L = [] for (channel, c) in ircdb.channels.items(): if c.lobotomized: chancap = ircdb.makeChannelCapability(channel, 'op') if ircdb.checkCapability(msg.prefix, 'admin') or \ ircdb.checkCapability(msg.prefix, chancap) or \ (channel in irc.state.channels and \ msg.nick in irc.state.channels[channel].users): L.append(channel) if L: L.sort() s = format(_('I\'m currently lobotomized in %L.'), L) irc.reply(s) else: irc.reply(_('I\'m not currently lobotomized in any channels ' 'that you\'re in.')) list = wrap(list) class ban(callbacks.Commands): def hostmask(self, irc, msg, args, channel, banmask): """[] Bans the from the .""" irc.queueMsg(ircmsgs.ban(channel, banmask)) hostmask = wrap(hostmask, ['op', ('haveHalfop+', _('ban someone')), 'text']) @internationalizeDocstring def add(self, irc, msg, args, channel, banmask, expires): """[] [] If you have the #channel,op capability, this will effect a persistent ban from interacting with the bot on the given (or the current hostmask associated with ). Other plugins may enforce this ban by actually banning users with matching hostmasks when they join. is an optional argument specifying when (in "seconds from now") the ban should expire; if none is given, the ban will never automatically expire. is only necessary if the message isn't sent in the channel itself. """ c = ircdb.channels.getChannel(channel) c.addBan(banmask, expires) ircdb.channels.setChannel(channel, c) irc.replySuccess() add = wrap(add, ['op', first('hostmask', 'banmask'), additional('expiry', 0)]) @internationalizeDocstring def remove(self, irc, msg, args, channel, banmask): """[] If you have the #channel,op capability, this will remove the persistent ban on . is only necessary if the message isn't sent in the channel itself. """ c = ircdb.channels.getChannel(channel) try: c.removeBan(banmask) ircdb.channels.setChannel(channel, c) irc.replySuccess() except KeyError: irc.error(_('There are no persistent bans for that hostmask.')) remove = wrap(remove, ['op', 'hostmask']) @internationalizeDocstring def list(self, irc, msg, args, channel, mask): """[] [] If you have the #channel,op capability, this will show you the current persistent bans on the that match the given mask, if any (returns all of them otherwise). Note that you can use * as a wildcard on masks and \\* to match actual * in masks """ all_bans = ircdb.channels.getChannel(channel).bans if mask: mask = mask.replace(r'\*', '[*]') filtered_bans = fnmatch.filter(all_bans, mask) else: filtered_bans = all_bans if filtered_bans: bans = [] for ban in filtered_bans: if all_bans[ban]: bans.append(format(_('%q (expires %t)'), ban, all_bans[ban])) else: bans.append(format(_('%q (never expires)'), ban, all_bans[ban])) irc.reply(format('%L', bans)) else: irc.reply(format(_('There are no persistent bans on %s.'), channel)) list = wrap(list, ['op', optional('somethingWithoutSpaces')]) class ignore(callbacks.Commands): @internationalizeDocstring def add(self, irc, msg, args, channel, banmask, expires): """[] [] If you have the #channel,op capability, this will set a persistent ignore on or the hostmask currently associated with . is an optional argument specifying when (in "seconds from now") the ignore will expire; if it isn't given, the ignore will never automatically expire. is only necessary if the message isn't sent in the channel itself. """ c = ircdb.channels.getChannel(channel) c.addIgnore(banmask, expires) ircdb.channels.setChannel(channel, c) irc.replySuccess() add = wrap(add, ['op', 'banmask', additional('expiry', 0)]) @internationalizeDocstring def remove(self, irc, msg, args, channel, banmask): """[] If you have the #channel,op capability, this will remove the persistent ignore on in the channel. is only necessary if the message isn't sent in the channel itself. """ c = ircdb.channels.getChannel(channel) try: c.removeIgnore(banmask) ircdb.channels.setChannel(channel, c) irc.replySuccess() except KeyError: irc.error(_('There are no ignores for that hostmask.')) remove = wrap(remove, ['op', 'banmask']) @internationalizeDocstring def list(self, irc, msg, args, channel): """[] Lists the hostmasks that the bot is ignoring on the given channel. is only necessary if the message isn't sent in the channel itself. """ # XXX Add the expirations. c = ircdb.channels.getChannel(channel) if len(c.ignores) == 0: s = format(_('I\'m not currently ignoring any hostmasks in ' '%q'), channel) irc.reply(s) else: L = sorted(c.ignores) irc.reply(utils.str.commaAndify(list(map(repr, L)))) list = wrap(list, ['op']) class capability(callbacks.Commands): @internationalizeDocstring def add(self, irc, msg, args, channel, user, capabilities): """[] [ ...] If you have the #channel,op capability, this will give the (or the user to whom maps) the capability in the channel. is only necessary if the message isn't sent in the channel itself. """ for c in capabilities.split(): c = ircdb.makeChannelCapability(channel, c) user.addCapability(c) ircdb.users.setUser(user) irc.replySuccess() add = wrap(add, ['op', 'otherUser', 'capability']) @internationalizeDocstring def remove(self, irc, msg, args, channel, user, capabilities): """[] [ ...] If you have the #channel,op capability, this will take from the user currently identified as (or the user to whom maps) the capability in the channel. is only necessary if the message isn't sent in the channel itself. """ fail = [] for c in capabilities.split(): cap = ircdb.makeChannelCapability(channel, c) try: user.removeCapability(cap) except KeyError: fail.append(c) ircdb.users.setUser(user) if fail: s = 'capability' if len(fail) > 1: s = utils.str.pluralize(s) irc.error(format(_('That user didn\'t have the %L %s.'), fail, s), Raise=True) irc.replySuccess() remove = wrap(remove, ['op', 'otherUser', 'capability']) # XXX This needs to be fix0red to be like Owner.defaultcapability. Or # something else. This is a horrible interface. @internationalizeDocstring def setdefault(self, irc, msg, args, channel, v): """[] {True|False} If you have the #channel,op capability, this will set the default response to non-power-related (that is, not {op, halfop, voice}) capabilities to be the value you give. is only necessary if the message isn't sent in the channel itself. """ c = ircdb.channels.getChannel(channel) if v: c.setDefaultCapability(True) else: c.setDefaultCapability(False) ircdb.channels.setChannel(channel, c) irc.replySuccess() setdefault = wrap(setdefault, ['op', 'boolean']) @internationalizeDocstring def set(self, irc, msg, args, channel, capabilities): """[] [ ...] If you have the #channel,op capability, this will add the channel capability for all users in the channel. is only necessary if the message isn't sent in the channel itself. """ chan = ircdb.channels.getChannel(channel) for c in capabilities: chan.addCapability(c) ircdb.channels.setChannel(channel, chan) irc.replySuccess() set = wrap(set, ['op', many('capability')]) @internationalizeDocstring def unset(self, irc, msg, args, channel, capabilities): """[] [ ...] If you have the #channel,op capability, this will unset the channel capability so each user's specific capability or the channel default capability will take precedence. is only necessary if the message isn't sent in the channel itself. """ chan = ircdb.channels.getChannel(channel) fail = [] for c in capabilities: try: chan.removeCapability(c) except KeyError: fail.append(c) ircdb.channels.setChannel(channel, chan) if fail: s = _('capability') if len(fail) > 1: s = utils.str.pluralize(s) irc.error(format(_('I do not know about the %L %s.'), fail, s), Raise=True) irc.replySuccess() unset = wrap(unset, ['op', many('capability')]) @internationalizeDocstring def list(self, irc, msg, args, channel): """[] Returns the capabilities present on the . is only necessary if the message isn't sent in the channel itself. """ c = ircdb.channels.getChannel(channel) L = sorted(c.capabilities) irc.reply(' '.join(L)) list = wrap(list, ['channel']) @internationalizeDocstring def disable(self, irc, msg, args, channel, plugin, command): """[] [] [] If you have the #channel,op capability, this will disable the in . If is provided, will be disabled only for that plugin. If only is provided, all commands in the given plugin will be disabled. is only necessary if the message isn't sent in the channel itself. """ chan = ircdb.channels.getChannel(channel) failMsg = '' if plugin: s = '-%s' % plugin.name() if command: if plugin.isCommand(command): s = '-%s.%s' % (plugin.name(), command) else: failMsg = format(_('The %s plugin does not have a command ' 'called %s.'), plugin.name(), command) elif command: # findCallbackForCommand if list(filter(None, irc.findCallbacksForArgs([command]))): s = '-%s' % command else: failMsg = format(_('No plugin or command named %s could be ' 'found.'), command) else: raise callbacks.ArgumentError if failMsg: irc.error(failMsg) else: chan.addCapability(s) ircdb.channels.setChannel(channel, chan) irc.replySuccess() disable = wrap(disable, ['op', optional(('plugin', False)), additional('commandName')]) @internationalizeDocstring def enable(self, irc, msg, args, channel, plugin, command): """[] [] [] If you have the #channel,op capability, this will enable the in if it has been disabled. If is provided, will be enabled only for that plugin. If only is provided, all commands in the given plugin will be enabled. is only necessary if the message isn't sent in the channel itself. """ chan = ircdb.channels.getChannel(channel) failMsg = '' if plugin: s = '-%s' % plugin.name() if command: if plugin.isCommand(command): s = '-%s.%s' % (plugin.name(), command) else: failMsg = format(_('The %s plugin does not have a command ' 'called %s.'), plugin.name(), command) elif command: # findCallbackForCommand if list(filter(None, irc.findCallbacksForArgs([command]))): s = '-%s' % command else: failMsg = format(_('No plugin or command named %s could be ' 'found.'), command) else: raise callbacks.ArgumentError if failMsg: irc.error(failMsg) else: fail = [] try: chan.removeCapability(s) except KeyError: fail.append(s) ircdb.channels.setChannel(channel, chan) if fail: irc.error(format(_('%s was not disabled.'), s[1:])) else: irc.replySuccess() enable = wrap(enable, ['op', optional(('plugin', False)), additional('commandName')]) @internationalizeDocstring 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. 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. Someone is allowed if # any of these are true: # * the channel is not secret (mode +s), # * the request is sent to the channel itself (FIXME: what about # channels without +n?), # * the requester is op, # * the request is not sent to a channel (or else everyone in the # channel would see the response) and the requester is in the # channel themselves capability = ircdb.makeChannelCapability(channel, 'op') if 's' in irc.state.channels[channel].modes and \ msg.channel != channel and \ not ircdb.checkCapability(msg.prefix, capability) and \ (msg.channel or \ msg.nick not in irc.state.channels[channel].users): irc.error(_('You don\'t have access to that information.'), Raise=True) L = list(irc.state.channels[channel].users) keys = [option for (option, arg) in optlist] if 'count' not in keys: utils.sortBy(str.lower, L) private = self.registryValue("nicksInPrivate", channel, irc.network) irc.reply(utils.str.commaAndify(L), private=private) else: irc.reply(str(len(L))) nicks = wrap(nicks, ['inChannel', getopts({'count':''})]) @internationalizeDocstring def alertOps(self, irc, channel, s, frm=None): """Internal message for notifying all the #channel,ops in a channel of a given situation.""" capability = ircdb.makeChannelCapability(channel, 'op') s = format(_('Alert to all %s ops: %s'), channel, s) if frm is not None: s += format(_(' (from %s)'), frm) for nick in irc.state.channels[channel].users: if ircdb.checkCapability(msg.prefix, capability): irc.reply(s, to=nick, private=True) irc.replySuccess() @internationalizeDocstring def alert(self, irc, msg, args, channel, text): """[] Sends to all the users in who have the ,op capability. """ self.alertOps(irc, channel, text, frm=msg.nick) alert = wrap(alert, ['inChannel', 'text']) @internationalizeDocstring def part(self, irc, msg, args, channel, reason): """[] [] Tells the bot to part the list of channels you give it. is only necessary if you want the bot to part a channel other than the current channel. If is specified, use it as the part message. Otherwise, the default part message specified in supybot.plugins.Channel.partMsg will be used. No part message will be used if no default is configured. """ channel = channel or msg.channel if not channel: irc.error(Raise=True) capability = ircdb.makeChannelCapability(channel, 'op') if not ircdb.checkCapabilities(msg.prefix, [capability, 'admin']): irc.errorNoCapability(capability, Raise=True) try: network = conf.supybot.networks.get(irc.network) network.channels().remove(channel) except KeyError: pass if channel not in irc.state.channels: irc.error(_('I\'m not in %s.') % channel, Raise=True) reason = (reason or self.registryValue("partMsg", channel, irc.network)) reason = ircutils.standardSubstitute(irc, msg, reason) irc.queueMsg(ircmsgs.part(channel, reason)) if msg.nick in irc.state.channels[channel].users: irc.noReply() else: irc.replySuccess() part = wrap(part, [optional('validChannel'), additional('text')]) Class = Channel # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Channel/test.py0000644000175000017500000003171713634634532017712 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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 * import supybot.conf as conf import supybot.ircdb as ircdb import supybot.ircmsgs as ircmsgs class ChannelTestCase(ChannelPluginTestCase): plugins = ('Channel', 'User') def setUp(self): super(ChannelTestCase, self).setUp() self.irc.state.channels[self.channel].addUser('foo') self.irc.state.channels[self.channel].addUser('bar') def testLobotomies(self): self.assertRegexp('lobotomy list', 'not.*any') ## def testCapabilities(self): ## self.prefix = 'foo!bar@baz' ## self.irc.feedMsg(ircmsgs.privmsg(self.irc.nick, 'register foo bar', ## prefix=self.prefix)) ## u = ircdb.users.getUser(0) ## u.addCapability('%s.op' % self.channel) ## ircdb.users.setUser(u) ## self.assertNotError(' ') ## self.assertResponse('user capabilities foo', '[]') ## self.assertNotError('channel addcapability foo op') ## self.assertRegexp('channel capabilities foo', 'op') ## self.assertNotError('channel removecapability foo op') ## self.assertResponse('user capabilities foo', '[]') def testCapabilities(self): self.assertNotError('channel capability list') self.assertNotError('channel capability set -foo') self.assertNotError('channel capability unset -foo') self.assertError('channel capability unset -foo') self.assertNotError('channel capability set -foo bar baz') self.assertRegexp('channel capability list', 'baz') self.assertNotError('channel capability unset -foo baz') self.assertError('channel capability unset baz') def testEnableDisable(self): self.assertNotRegexp('channel capability list', '-Channel') self.assertError('channel enable channel') self.assertNotError('channel disable channel') self.assertRegexp('channel capability list', '-Channel') self.assertNotError('channel enable channel') self.assertNotRegexp('channel capability list', '-Channel') self.assertNotError('channel disable channel nicks') self.assertRegexp('channel capability list', '-Channel.nicks') self.assertNotError('channel enable channel nicks') self.assertNotRegexp('channel capability list', '-Channel.nicks') self.assertNotRegexp('channel capability list', 'nicks') self.assertNotError('channel disable nicks') self.assertRegexp('channel capability list', 'nicks') self.assertNotError('channel enable nicks') self.assertError('channel disable invalidPlugin') self.assertError('channel disable channel invalidCommand') def testUnban(self): self.assertError('unban foo!bar@baz') self.irc.feedMsg(ircmsgs.op(self.channel, self.nick)) m = self.getMsg('unban foo!bar@baz') self.assertEqual(m.command, 'MODE') self.assertEqual(m.args, (self.channel, '-b', 'foo!bar@baz')) self.assertNoResponse(' ', 2) def testErrorsWithoutOps(self): for s in 'op deop halfop dehalfop voice devoice kick invite'.split(): self.assertError('%s foo' % s) self.irc.feedMsg(ircmsgs.op(self.channel, self.nick)) self.assertNotError('%s foo' % s) self.irc.feedMsg(ircmsgs.deop(self.channel, self.nick)) def testWontDeItself(self): for s in 'deop dehalfop'.split(): self.irc.feedMsg(ircmsgs.op(self.channel, self.nick)) self.assertError('%s %s' % (s, self.nick)) def testCanDevoiceSelf(self): self.irc.feedMsg(ircmsgs.op(self.channel, self.nick)) self.assertNotError('devoice %s' % self.nick) def testOp(self): self.assertError('op') self.irc.feedMsg(ircmsgs.op(self.channel, self.nick)) self.assertNotError('op') m = self.getMsg('op foo') self.assertTrue(m.command == 'MODE' and m.args == (self.channel, '+o', 'foo')) m = self.getMsg('op foo bar') self.assertTrue(m.command == 'MODE' and m.args == (self.channel, '+o', 'foo')) m = self.irc.takeMsg() self.assertTrue(m.command == 'MODE' and m.args == (self.channel, '+o', 'bar')) self.irc.state.supported['MODES'] = 2 m = self.getMsg('op foo bar') try: self.assertTrue(m.command == 'MODE' and m.args == (self.channel, '+oo', 'foo', 'bar')) finally: self.irc.state.supported['MODES'] = 1 def testHalfOp(self): self.assertError('halfop') self.irc.feedMsg(ircmsgs.op(self.channel, self.nick)) self.assertNotError('halfop') m = self.getMsg('halfop foo') self.assertTrue(m.command == 'MODE' and m.args == (self.channel, '+h', 'foo')) m = self.getMsg('halfop foo bar') self.assertTrue(m.command == 'MODE' and m.args == (self.channel, '+h', 'foo')) m = self.irc.takeMsg() self.assertTrue(m.command == 'MODE' and m.args == (self.channel, '+h', 'bar')) def testVoice(self): self.assertError('voice') self.irc.feedMsg(ircmsgs.op(self.channel, self.nick)) self.assertNotError('voice') m = self.getMsg('voice foo') self.assertTrue(m.command == 'MODE' and m.args == (self.channel, '+v', 'foo')) m = self.getMsg('voice foo bar') self.assertTrue(m.command == 'MODE' and m.args == (self.channel, '+v', 'foo')) m = self.irc.takeMsg() self.assertTrue(m.command == 'MODE' and m.args == (self.channel, '+v', 'bar')) def assertKban(self, query, hostmask, **kwargs): m = self.getMsg(query, **kwargs) self.assertEqual(m, ircmsgs.ban(self.channel, hostmask)) m = self.getMsg(' ') self.assertEqual(m.command, 'KICK') def assertBan(self, query, hostmask, **kwargs): m = self.getMsg(query, **kwargs) self.assertEqual(m, ircmsgs.ban(self.channel, hostmask)) def testIban(self): self.irc.feedMsg(ircmsgs.join(self.channel, prefix='foobar!user@host.domain.tld')) self.assertError('iban foo!bar@baz') self.irc.feedMsg(ircmsgs.op(self.channel, self.irc.nick)) self.assertBan('iban foo!bar@baz', 'foo!bar@baz') self.assertBan('iban foobar', 'foobar!user@host.domain.tld') conf.supybot.protocols.irc.strictRfc.setValue(True) self.assertError('iban $a:nyuszika7h') self.assertError('unban $a:nyuszika7h') conf.supybot.protocols.irc.strictRfc.setValue(False) self.assertBan('iban $a:nyuszika7h', '$a:nyuszika7h') self.assertNotError('unban $a:nyuszika7h') ## def testKban(self): ## self.irc.prefix = 'something!else@somehwere.else' ## self.irc.nick = 'something' ## self.irc.feedMsg(ircmsgs.join(self.channel, ## prefix='foobar!user@host.domain.tld')) ## self.assertError('kban foobar') ## self.irc.feedMsg(ircmsgs.op(self.channel, self.irc.nick)) ## self.assertError('kban foobar -1') ## self.assertKban('kban foobar', '*!*@*.domain.tld') ## self.assertKban('kban --exact foobar', 'foobar!user@host.domain.tld') ## self.assertKban('kban --host foobar', '*!*@host.domain.tld') ## self.assertKban('kban --user foobar', '*!user@*') ## self.assertKban('kban --nick foobar', 'foobar!*@*') ## self.assertKban('kban --nick --user foobar', 'foobar!user@*') ## self.assertKban('kban --nick --host foobar', ## 'foobar!*@host.domain.tld') ## self.assertKban('kban --user --host foobar', '*!user@host.domain.tld') ## self.assertKban('kban --nick --user --host foobar', ## 'foobar!user@host.domain.tld') ## self.assertNotRegexp('kban adlkfajsdlfkjsd', 'KeyError') ## self.assertNotRegexp('kban foobar time', 'ValueError') ## self.assertError('kban %s' % self.irc.nick) def testBan(self): with conf.supybot.protocols.irc.banmask.context(['exact']): self.assertNotError('ban add foo!bar@baz') self.assertNotError('ban remove foo!bar@baz') orig = conf.supybot.protocols.irc.strictRfc() with conf.supybot.protocols.irc.strictRfc.context(True): # something wonky is going on here. irc.error (src/Channel.py|449) # is being called but the assert is failing self.assertError('ban add not!a.hostmask') self.assertNotRegexp('ban add not!a.hostmask', 'KeyError') self.assertError('ban add $a:nyuszika7h') self.assertError('ban remove $a:nyuszika7h') conf.supybot.protocols.irc.strictRfc.setValue(False) self.assertNotError('ban add $a:nyuszika7h') self.assertNotError('ban remove $a:nyuszika7h') def testBanList(self): self.assertNotError('ban add foo!bar@baz') self.assertNotError('ban add foobar!*@baz') self.assertNotError('ban add foobar!qux@baz') self.assertRegexp('ban list', r'.*foo!bar@baz.*') self.assertRegexp('ban list', r'.*foobar!\*@baz.*') self.assertRegexp('ban list', r'.*foobar!qux@baz.*') self.assertNotRegexp('ban list foobar!*@baz', r'.*foo!bar@baz.*') self.assertRegexp('ban list foobar!*@baz', r'.*foobar!\*@baz.*') self.assertRegexp('ban list foobar!*@baz', r'.*foobar!qux@baz.*') self.assertResponse('ban list foobar!\\*@baz', '"foobar!*@baz" (never expires)') def testIgnore(self): orig = conf.supybot.protocols.irc.banmask() def ignore(given, expect=None): if expect is None: expect = given self.assertNotError('channel ignore add %s' % given) self.assertResponse('channel ignore list', "'%s'" % expect) self.assertNotError('channel ignore remove %s' % expect) self.assertRegexp('channel ignore list', 'not currently') ignore('foo!bar@baz', '*!*@baz') ignore('foo!*@*') with conf.supybot.protocols.irc.banmask.context(['exact']): ignore('foo!bar@baz') ignore('foo!*@*') self.assertError('ban add not!a.hostmask') def testNicks(self): self.assertResponse('channel nicks', 'bar, foo, and test') self.assertResponse('channel nicks --count', '3') def testPart(self): def getAfterJoinMessages(): m = self.irc.takeMsg() self.assertEqual(m.command, 'MODE') m = self.irc.takeMsg() self.assertEqual(m.command, 'MODE') m = self.irc.takeMsg() self.assertEqual(m.command, 'WHO') self.assertError('part #foo') self.assertRegexp('part #foo', 'not in') self.irc.feedMsg(ircmsgs.join('#foo', prefix=self.prefix)) getAfterJoinMessages() m = self.getMsg('part #foo') self.assertEqual(m.command, 'PART') self.irc.feedMsg(ircmsgs.join('#foo', prefix=self.prefix)) getAfterJoinMessages() m = self.getMsg('part #foo reason') self.assertEqual(m.command, 'PART') self.assertEqual(m.args[0], '#foo') self.assertEqual(m.args[1], 'reason') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/ChannelLogger/0000755000175000017500000000000013634634547017516 5ustar valval00000000000000limnoria-2020.03.17/plugins/ChannelLogger/__init__.py0000644000175000017500000000466713634634532021636 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Logs each channel to its own individual logfile. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/ChannelLogger/config.py0000644000175000017500000001250413634634532021331 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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 from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('ChannelLogger') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('ChannelLogger', True) ChannelLogger = conf.registerPlugin('ChannelLogger') conf.registerChannelValue(ChannelLogger, 'enable', registry.Boolean(True, _("""Determines whether logging is enabled."""))) conf.registerGlobalValue(ChannelLogger, 'flushImmediately', registry.Boolean(True, _("""Determines whether channel logfiles will be flushed anytime they're written to, rather than being buffered by the operating system."""))) conf.registerChannelValue(ChannelLogger, 'showJoinParts', registry.Boolean(True, _("""Determines wether joins and parts are logged"""))) conf.registerChannelValue(ChannelLogger, 'stripFormatting', registry.Boolean(True, _("""Determines whether formatting characters (such as bolding, color, etc.) are removed when writing the logs to disk."""))) conf.registerChannelValue(ChannelLogger, 'timestamp', registry.Boolean(True, _("""Determines whether the logs for this channel are timestamped with the timestamp in supybot.log.timestampFormat."""))) conf.registerChannelValue(ChannelLogger, 'noLogPrefix', registry.String('[nolog]', _("""Determines what string a message should be prefixed with in order not to be logged. If you don't want any such prefix, just set it to the empty string."""))) conf.registerChannelValue(ChannelLogger, 'rotateLogs', registry.Boolean(False, _("""Determines whether the bot will automatically rotate the logs for this channel. The bot will rotate logs when the timestamp for the log changes. The timestamp is set according to the 'filenameTimestamp' configuration variable."""))) conf.registerChannelValue(ChannelLogger, 'filenameTimestamp', registry.String('%Y-%m-%d', _("""Determines how to represent the timestamp used for the filename in rotated logs. When this timestamp changes, the old logfiles will be closed and a new one started. The format characters for the timestamp are in the time.strftime docs at python.org. In order for your logs to be rotated, you'll also have to enable supybot.plugins.ChannelLogger.rotateLogs."""))) conf.registerGlobalValue(ChannelLogger, 'directories', registry.Boolean(True, _("""Determines whether the bot will partition its channel logs into separate directories based on different criteria."""))) conf.registerGlobalValue(ChannelLogger.directories, 'network', registry.Boolean(True, _("""Determines whether the bot will use a network directory if using directories."""))) conf.registerGlobalValue(ChannelLogger.directories, 'channel', registry.Boolean(True, _("""Determines whether the bot will use a channel directory if using directories."""))) conf.registerGlobalValue(ChannelLogger.directories, 'timestamp', registry.Boolean(False, _("""Determines whether the bot will use a timestamp (determined by supybot.plugins.ChannelLogger.directories.timestamp.format) if using directories."""))) conf.registerGlobalValue(ChannelLogger.directories.timestamp, 'format', registry.String('%B', _("""Determines what timestamp format will be used in the directory structure for channel logs if supybot.plugins.ChannelLogger.directories.timestamp is True."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/ChannelLogger/locales/0000755000175000017500000000000013634634547021140 5ustar valval00000000000000limnoria-2020.03.17/plugins/ChannelLogger/locales/fi.po0000644000175000017500000001171213634634532022072 0ustar valval00000000000000# ChannelLogger plugin in Limnoria. # Copyright (C) 2011, 2012 Limnoria # Mikaela Suomalainen , 2011, 2012. # msgid "" msgstr "" "Project-Id-Version: Supybot Channellogger\n" "POT-Creation-Date: 2014-12-20 11:59+EET\n" "PO-Revision-Date: 2014-12-20 12:13+0200\n" "Last-Translator: Mikaela Suomalainen \n" "Language-Team: suomi <>\n" "Language: fi_FI\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: \n" "X-Generator: Poedit 1.6.10\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 wether joins and parts are logged" msgstr "Määrittää kirjoitetaanko liittymiset ja poistumiset lokiin" #: config.py:54 msgid "" "Determines whether formatting characters (such\n" " as bolding, color, etc.) are removed when writing the logs to disk." msgstr "" "Määrittää pitääkö muotoilumerkit (kuten\n" "korostukset, väri, jne.) poistaa kun lokeja kirjoitetaan levylle." #: config.py:57 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:60 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" " ei tallenneta lokiin. Jos et halua\n" " merkkiketjua, aseta se tyhjäksi merkkiketjuksi." #: config.py:64 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' asetusarvon mukaan." #: config.py:69 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, esitetään. 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:77 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:80 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:83 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:86 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:90 msgid "" "Determines what timestamp format will be used in\n" " the directory structure for channel logs if\n" " supybot.plugins.ChannelLogger.directories.timestamp is True." msgstr "" "Määrittää mitä aikaleimamuotoa käytetään\n" " hakemistorakenteessa kanavalokeille jos\n" " supybot.plugins.ChannelLogger.directories.timestamp on True." #: plugin.py:58 msgid "This plugin allows the bot to log channel conversations to disk." msgstr "Tämä plugin sallii botin tallentaa kanavan keskustelut levylle." limnoria-2020.03.17/plugins/ChannelLogger/locales/fr.po0000644000175000017500000001127313634634532022105 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2013-03-02 19:04+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Limnoria \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" #: config.py:46 msgid "Determines whether logging is enabled." msgstr "Détermine si les logs sont activés." #: 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 "" "Détermine si le fichier de logs sera enregistré à chaque fois que l'on y " "écrit plutôt que d'être mis en buffer par le système d'exploitation." #: config.py:52 msgid "Determines wether joins and parts are logged" msgstr "Détermine si les arrivées et les départs sont loggués." #: config.py:54 msgid "" "Determines whether formatting characters (such\n" " as bolding, color, etc.) are removed when writing the logs to disk." msgstr "" "Détermine si les caractères de formattage (comme le gras, la couleurs) sont " "supprimés lors de l'écriture des logs sur le disque." #: config.py:57 msgid "" "Determines whether the logs for this channel are\n" " timestamped with the timestamp in supybot.log.timestampFormat." msgstr "" "Détermine si les logs de ce canal sont 'timestampés' avec le format dans " "supybot.log.timestampFormat" #: config.py:60 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 "" "Détermine par quelle chaîne un message doit être préfixé pour ne pas être " "loggué. Vous ne voulez probablement pas d'un tel préfixe, et simplement " "définir une chaîne vide." #: config.py:64 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 "" "Détermine si le bot fera automatiquement une rotation des logs pour ce " "canal. Le bot fera tourner les logs lorsque leur timestamp changera. Le " "timestamp est défini par la variable de configuration 'filenameTimestamp'." #: config.py:69 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 "" "Détermine comment représenter le timestamp utilisé pour les noms de fichiers " "de logs 'tournants'. Lorsque ce timestamp change, l'ancien fichier de logs " "sera fermé, et un nouveau sera ouvert. Le format du timestamp est le même " "que celui de time.strftime (documentation disponible sur python.org). Pour " "avoir des logs 'tournant', vous devez activer supybot.plugins.ChannelLogger." "rotateLogs." #: config.py:77 msgid "" "Determines whether the bot will partition its\n" " channel logs into separate directories based on different criteria." msgstr "" "Détermine si le bot partitionnera les logs de canaux dans des répertoires " "séparés, en fonction de différents critères." #: config.py:80 msgid "" "Determines whether the bot will use a network\n" " directory if using directories." msgstr "" "Détermine si le bot utilisera un répertoire par réseau, si il utilise des " "répertoires." #: config.py:83 msgid "" "Determines whether the bot will use a channel\n" " directory if using directories." msgstr "" "Détermine si le bot utilisera un répertoire par canal, si il utilise des " "répertoires." #: config.py:86 msgid "" "Determines whether the bot will use a timestamp\n" " (determined by supybot.plugins.ChannelLogger.directories.timestamp." "format)\n" " if using directories." msgstr "" "Détermine si le bot utilisera un timestamp (déterminé par supybot.plugins." "ChannelLogger.directories.timestamp.format), si il utilise des répertoires." #: config.py:90 msgid "" "Determines what timestamp format will be used in\n" " the directory structure for channel logs if\n" " supybot.plugins.ChannelLogger.directories.timestamp is True." msgstr "" "Détermine quel format de timestamp sera utilisé dans la structure de " "répertoires pour les logs de canaux si supybot.plugins.ChannelLogger." "directories.timestamp est True." limnoria-2020.03.17/plugins/ChannelLogger/locales/hu.po0000644000175000017500000001135113634634532022107 0ustar valval00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: Limnoria ChannelLogger\n" "POT-Creation-Date: 2011-08-10 11:27+CEST\n" "PO-Revision-Date: 2011-08-19 16:21+0200\n" "Last-Translator: nyuszika7h \n" "Language-Team: \n" "Language: \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" #: config.py:46 msgid "Determines whether logging is enabled." msgstr "Meghatározza, hogy a naplózás engedélyezve van-e." #: 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 "Meghatározza, hogy a csatorna naplófájlok mentve legyenek-e, amikor írva vannak, ahelyett, hogy az operációs rendszer pufferelje őket." #: config.py:52 msgid "" "Determines whether formatting characters (such\n" " as bolding, color, etc.) are removed when writing the logs to disk." msgstr "Meghatározza, hogy milyen formázási karakterek (olyan, mint félkövér, szín stb.) vannak eltávolítva a naplófájlok lemezre írásakor." #: config.py:55 msgid "" "Determines whether the logs for this channel are\n" " timestamped with the timestamp in supybot.log.timestampFormat." msgstr "Meghatározza, hogy a csatorna naplójai el legyenek-e látva a supybot.log.timestampFormat-ban lévő időbélyegzővel." #: 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 "Meghatározza, hogy milyen karakterlánccal kell kezdődnie egy üzenetnek, hogy ne legyen naplózva. Ha nem szeretnél semmi ilyen előtagot, csak állítsd üres karakterláncra." #: 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 "Meghatározza, hogy a bot automatikusan elforgassa-e a csatorna naplófájljait. A bot elforgatja a naplófájlokat, amikor a naplófájl időbélyegzője megváltozik. Az időbélyegző a 'filenameTimestamp' konfigurációs változó szerint van beállítva." #: 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 "Meghatározza, hogyan legyen az elforgatott naplófájlok fájlnevéhez használt időbélyegző ábrázolva. Amikor ez az időbélyegző megváltozik, a régi naplófájlok le lesznek zárva és újak lesznek nyitva. A formázási karakterek az időbélyegzőhez a time.strftime-ban vannak a python.org dokumentációiban. Ahhoz, hogy a naplófájljaid el legyenek forgatva, engedélyezned kell a supybot.plugins.ChannelLogger.rotateLogs-t is." #: config.py:75 msgid "" "Determines whether the bot will partition its\n" " channel logs into separate directories based on different criteria." msgstr "Meghatározza, hogy a bot szétválassza-e a csatorna naplóit különálló könyvtárakba különböző kritériumok alapján." #: config.py:78 msgid "" "Determines whether the bot will use a network\n" " directory if using directories." msgstr "Meghatározza, hogy a bot használjon-e egy könyvtárat a hálózatnak, ha használ könyvtárakat." #: config.py:81 msgid "" "Determines whether the bot will use a channel\n" " directory if using directories." msgstr "Meghatározza, hogy a bot használjon-e egy könyvtárat a csatornának, ha használ könyvtárakat." #: 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 "Meghatározza, hogy a bot használjon-e egy időbályegzőt (a supybot.plugins.ChannelLogger.directories.timestamp.format által meghatározott) ha használ könyvtárakat." #: config.py:88 msgid "" "Determines what timestamp format will be used in\n" " the directory structure for channel logs if\n" " supybot.plugins.ChannelLogger.directories.timestamp is True." msgstr "Meghatározza, milyen időbélyegző formátum lesz használva a könyvtárstruktúrában a csatorna naplófájlokhoz, ha a supybot.plugins.ChannelLogger.directories.timestamp True." limnoria-2020.03.17/plugins/ChannelLogger/locales/it.po0000644000175000017500000001064113634634532022110 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-19 16:57+0200\n" "Last-Translator: skizzhg \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:46 msgid "Determines whether logging is enabled." msgstr "Determina se i log sono abilitati." #: 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 "" "Determina se i file di log del canale verranno salvati ogni volta che sono\n" " scritti piuttosto che tenuti in memoria dal sistema." #: config.py:52 msgid "" "Determines whether formatting characters (such\n" " as bolding, color, etc.) are removed when writing the logs to disk." msgstr "" "Determina se la formattazione dei caratteri (come grassetto, colori, ecc.)\n" " viene rimossa una volta che i log sono scritti sul disco." #: config.py:55 msgid "" "Determines whether the logs for this channel are\n" " timestamped with the timestamp in supybot.log.timestampFormat." msgstr "" "Determina se i log per questo canale riportano data e ora (timestamp)\n" " con il formato indicato in 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 "" "Determina con quale stringa debba essere prefissato un messaggio per non\n" " essere registrato. Se non si vuole alcun prefisso, impostare una stringa vuota.\n" #: 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 "" "Determina se il bot ruoterà automaticamente i log di questo canale.\n" " Verranno ruotati quando cambiano data e ora; il timestamp\n" " è impostato in base alla variabile \"filenameTimestamp\"." #: 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 "" "Determina come rappresentare il timestamp usato per il nome del file da ruotare.\n" " Quando questo cambia, il vecchio file di log verrà chiuso e ne inizierà uno\n" " nuovo; il formato è lo stesso di time.strftime (documentazione su python.org).\n" " Affinché i log vengano ruotati, è inoltre necessario abilitare 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 "" "Determina se il bot suddividerà i log del canale in directory separate in base a criteri differenti.\n" #: config.py:78 msgid "" "Determines whether the bot will use a network\n" " directory if using directories." msgstr "" "Determina se, in caso si utilizzino directory, il bot userà una directory per network.\n" #: config.py:81 msgid "" "Determines whether the bot will use a channel\n" " directory if using directories." msgstr "" "Determina se, in caso si utilizzino directory, il bot userà una directory per canale.\n" #: 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 "" "Determina se, in caso si utilizzino directory, il bot userà un timestamp\n" " (definito da supybot.plugins.ChannelLogger.directories.timestamp.format).\n" #: config.py:88 msgid "" "Determines what timestamp format will be used in\n" " the directory structure for channel logs if\n" " supybot.plugins.ChannelLogger.directories.timestamp is True." msgstr "" "Determina quale formato di timestamp sarà utilizzato nella struttura della directory\n" " dei log del canale se supybot.plugins.ChannelLogger.directories.timestamp è impostata a True." limnoria-2020.03.17/plugins/ChannelLogger/plugin.py0000644000175000017500000002612513634634532021366 0ustar valval00000000000000### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2009-2010, James McCoy # 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 os import sys import time import supybot.conf as conf import supybot.world as world import supybot.ircdb as ircdb import supybot.utils as utils import supybot.irclib as irclib import supybot.utils.minisix as minisix import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.registry as registry import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('ChannelLogger') if minisix.PY2: from io import open class FakeLog(object): def flush(self): return def close(self): return def write(self, s): return class ChannelLogger(callbacks.Plugin): """This plugin allows the bot to log channel conversations to disk.""" noIgnore = True def __init__(self, irc): self.__parent = super(ChannelLogger, self) self.__parent.__init__(irc) self.logs = {} self.flusher = self.flush world.flushers.append(self.flusher) def die(self): for log in self._logs(): log.close() world.flushers = [x for x in world.flushers if x is not self.flusher] def reset(self): for log in self._logs(): log.close() self.logs.clear() def _logs(self): for logs in self.logs.values(): for log in logs.values(): yield log def flush(self): self.checkLogNames() for log in self._logs(): try: log.flush() except ValueError as e: if e.args[0] != 'I/O operation on a closed file': self.log.exception('Odd exception:') def logNameTimestamp(self, network, channel): format = self.registryValue('filenameTimestamp', channel, network) return time.strftime(format) def getLogName(self, network, channel): if self.registryValue('rotateLogs', channel, network): name = '%s.%s.log' % (channel, self.logNameTimestamp(network, channel)) else: name = '%s.log' % channel return utils.file.sanitizeName(name) def getLogDir(self, irc, channel): channel = self.normalizeChannel(irc, channel) logDir = conf.supybot.directories.log.dirize(self.name()) if self.registryValue('directories'): if self.registryValue('directories.network'): logDir = os.path.join(logDir, irc.network) if self.registryValue('directories.channel'): logDir = os.path.join(logDir, utils.file.sanitizeName(channel)) if self.registryValue('directories.timestamp'): format = self.registryValue('directories.timestamp.format') timeDir =time.strftime(format) logDir = os.path.join(logDir, timeDir) if not os.path.exists(logDir): os.makedirs(logDir) return logDir def checkLogNames(self): for (irc, logs) in self.logs.items(): for (channel, log) in list(logs.items()): if self.registryValue('rotateLogs', channel, irc.network): name = self.getLogName(irc.network, channel) if name != os.path.basename(log.name): log.close() del logs[channel] def getLog(self, irc, channel): self.checkLogNames() try: logs = self.logs[irc] except KeyError: logs = ircutils.IrcDict() self.logs[irc] = logs if channel in logs: return logs[channel] else: try: name = self.getLogName(irc.network, channel) logDir = self.getLogDir(irc, channel) log = open(os.path.join(logDir, name), encoding='utf-8', mode='a') logs[channel] = log return log except IOError: self.log.exception('Error opening log:') return FakeLog() def timestamp(self, log): format = conf.supybot.log.timestampFormat() if format: string = time.strftime(format) + ' ' if minisix.PY2: string = string.decode('utf8', 'ignore') log.write(string) def normalizeChannel(self, irc, channel): return ircutils.toLower(channel) def doLog(self, irc, channel, s, *args): if not self.registryValue('enable', channel, irc.network): return s = format(s, *args) channel = self.normalizeChannel(irc, channel) log = self.getLog(irc, channel) if self.registryValue('timestamp', channel, irc.network): self.timestamp(log) if self.registryValue('stripFormatting', channel, irc.network): s = ircutils.stripFormatting(s) if minisix.PY2: s = s.decode('utf8', 'ignore') log.write(s) if self.registryValue('flushImmediately'): log.flush() def doPrivmsg(self, irc, msg): (recipients, text) = msg.args for channel in recipients.split(','): if irc.isChannel(channel): noLogPrefix = self.registryValue('noLogPrefix', channel, irc.network) cap = ircdb.makeChannelCapability(channel, 'logChannelMessages') try: logChannelMessages = ircdb.checkCapability(msg.prefix, cap, ignoreOwner=True) except KeyError: logChannelMessages = True nick = msg.nick or irc.nick if msg.tagged('ChannelLogger__relayed'): (nick, text) = text.split(' ', 1) nick = nick[1:-1] msg.args = (recipients, text) if (noLogPrefix and text.startswith(noLogPrefix)) or \ not logChannelMessages: text = '-= THIS MESSAGE NOT LOGGED =-' if ircmsgs.isAction(msg): self.doLog(irc, channel, '* %s %s\n', nick, ircmsgs.unAction(msg)) else: self.doLog(irc, channel, '<%s> %s\n', nick, text) def doNotice(self, irc, msg): (recipients, text) = msg.args for channel in recipients.split(','): if irc.isChannel(channel): self.doLog(irc, channel, '-%s- %s\n', msg.nick, text) def doNick(self, irc, msg): oldNick = msg.nick newNick = msg.args[0] for channel in msg.tagged('channels'): self.doLog(irc, channel, '*** %s is now known as %s\n', oldNick, newNick) def doInvite(self, irc, msg): (target, channel) = msg.args self.doLog(irc, channel, '*** %s <%s> invited %s to %s\n', msg.nick, msg.prefix, target, channel) def doJoin(self, irc, msg): for channel in msg.args[0].split(','): if(self.registryValue('showJoinParts', channel, irc.network)): self.doLog(irc, channel, '*** %s <%s> has joined %s\n', msg.nick, msg.prefix, channel) def doKick(self, irc, msg): if len(msg.args) == 3: (channel, target, kickmsg) = msg.args else: (channel, target) = msg.args kickmsg = '' if kickmsg: self.doLog(irc, channel, '*** %s was kicked by %s (%s)\n', target, msg.nick, kickmsg) else: self.doLog(irc, channel, '*** %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(','): if(self.registryValue('showJoinParts', channel, irc.network)): self.doLog(irc, channel, '*** %s <%s> has left %s%s\n', msg.nick, msg.prefix, channel, reason) def doMode(self, irc, msg): channel = msg.args[0] if irc.isChannel(channel) and msg.args[1:]: self.doLog(irc, channel, '*** %s sets mode: %s %s\n', msg.nick or msg.prefix, msg.args[1], ' '.join(msg.args[2:])) def doTopic(self, irc, msg): if len(msg.args) == 1: return # It's an empty TOPIC just to get the current topic. channel = msg.args[0] self.doLog(irc, channel, '*** %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 = "" for channel in msg.tagged('channels'): if(self.registryValue('showJoinParts', channel, irc.network)): self.doLog(irc, channel, '*** %s <%s> has quit IRC%s\n', msg.nick, msg.prefix, reason) def outFilter(self, irc, msg): # Gotta catch my own messages *somehow* :) # Let's try this little trick... if msg.command in ('PRIVMSG', 'NOTICE'): # Other messages should be sent back to us. m = ircmsgs.IrcMsg(msg=msg, prefix=irc.prefix) if msg.tagged('relayedMsg'): m.tag('ChannelLogger__relayed') self(irc, m) return msg Class = ChannelLogger # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/ChannelLogger/test.py0000644000175000017500000000377213634634532021052 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * class ChannelLoggerTestCase(PluginTestCase): plugins = ('ChannelLogger',) def testLogDir(self): self.assertEqual( self.irc.getCallback('ChannelLogger').getLogName('test', '#foo'), '#foo.log') self.assertEqual( self.irc.getCallback('ChannelLogger').getLogName('test', '#f/../oo'), '#f..oo.log') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/ChannelStats/0000755000175000017500000000000013634634547017375 5ustar valval00000000000000limnoria-2020.03.17/plugins/ChannelStats/__init__.py0000644000175000017500000000511613634634532021503 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Silently listens to every message received on a channel and keeps statistics concerning joins, parts, and various other commands in addition to tracking statistics about smileys, actions, characters, and words. """ 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__ = "" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/ChannelStats/config.py0000644000175000017500000000642613634634532021216 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import re import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('ChannelStats') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('ChannelStats', True) class Smileys(registry.Value): def set(self, s): L = s.split() self.setValue(L) def setValue(self, v): self.s = ' '.join(v) self.value = re.compile('|'.join(map(re.escape, v))) def __str__(self): return self.s ChannelStats = conf.registerPlugin('ChannelStats') conf.registerChannelValue(ChannelStats, 'selfStats', registry.Boolean(True, _("""Determines whether the bot will keep channel statistics on itself, possibly skewing the channel stats (especially in cases where the bot is relaying between channels on a network)."""))) conf.registerChannelValue(ChannelStats, 'smileys', Smileys(':) ;) ;] :-) :-D :D :P :p (= =)'.split(), _("""Determines what words (i.e., pieces of text with no spaces in them) are considered 'smileys' for the purposes of stats-keeping."""))) conf.registerChannelValue(ChannelStats, 'frowns', Smileys(':| :-/ :-\\ :\\ :/ :( :-( :\'('.split(), _("""Determines what words (i.e., pieces of text with no spaces in them) are considered 'frowns' for the purposes of stats-keeping."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/ChannelStats/locales/0000755000175000017500000000000013634634547021017 5ustar valval00000000000000limnoria-2020.03.17/plugins/ChannelStats/locales/fi.po0000644000175000017500000001435413634634532021756 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Supybot ChannelStats\n" "POT-Creation-Date: 2014-12-20 11:59+EET\n" "PO-Revision-Date: \n" "Last-Translator: Mikaela Suomalainen \n" "Language-Team: \n" "Language: fi_FI\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 1.6.10\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 #, fuzzy 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:167 msgid "" "This plugin keeps stats of the channel and returns them with\n" " the command 'channelstats'." msgstr "" "Tämä plugini säilyttää tilastoja kanavasta ja palauttaa ne komennolla\n" " 'channelstats'." #: plugin.py:248 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:264 msgid "I couldn't find you in my user database." msgstr "Minä en voi löytää sinua käyttäjä tietokannastani." #: plugin.py:277 #, 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:284 msgid "character" msgstr "merkki" #: plugin.py:285 plugin.py:379 msgid "word" msgstr "sana" #: plugin.py:286 plugin.py:380 msgid "smiley" msgstr "hymiö" #: plugin.py:287 plugin.py:381 msgid "frown" msgstr "surio" #: plugin.py:289 plugin.py:382 msgid "was an ACTION" msgstr "oli TOIMINTO" #: plugin.py:290 plugin.py:383 msgid "were ACTIONs" msgstr "olivat TOIMINTOja" #: plugin.py:292 plugin.py:293 plugin.py:294 plugin.py:295 plugin.py:296 #: plugin.py:297 plugin.py:298 msgid "time" msgstr "aika" #: plugin.py:301 msgid "I have no stats for that %s in %s." msgstr "Minulla ei ole tilastoja %s:stä %s:ään." #: plugin.py:310 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:324 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:328 #, fuzzy msgid "You can't use lambda in this command." msgstr "Et voi käyttää lambdaa tässä komennossa." #: plugin.py:342 msgid "stat variable" msgstr "aloita muuttuja" #: plugin.py:358 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:364 msgid "I am not in %s." msgstr "En ole kanavalla %s." #: plugin.py:366 msgid "You must be in %s to use this command." msgstr "Sinun on oltava kanavalla %s käyttääksesi tätä komentoa." #: plugin.py:373 #, 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:384 msgid "join" msgstr "liity" #: plugin.py:385 msgid "part" msgstr "poistu" #: plugin.py:386 msgid "quit" msgstr "poistu" #: plugin.py:387 msgid "kick" msgstr "potki" #: plugin.py:388 msgid "mode" msgstr "tila" #: plugin.py:388 plugin.py:389 msgid "change" msgstr "vaihdos" #: plugin.py:389 msgid "topic" msgstr "aihe" #: plugin.py:391 plugin.py:392 msgid "user" msgstr "käyttäjä" #: plugin.py:395 msgid "I've never been on %s." msgstr "En ole ikinä ollut %s:llä." limnoria-2020.03.17/plugins/ChannelStats/locales/fr.po0000644000175000017500000001446213634634532021767 0ustar valval00000000000000# French translations for supybot-i18n package # Traductions françaises du paquet supybot-i18n. # Copyright (C) 2010 THE supybot-i18n'S COPYRIGHT HOLDER # This file is distributed under the same license as the supybot-i package. # ProgVal , 2010. # msgid "" msgstr "" "Project-Id-Version: supybot-i18n\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2014-01-22 13:46+CET\n" "PO-Revision-Date: 2014-01-22 13:47+0100\n" "Last-Translator: \n" "Language-Team: French\n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Generator: Poedit 1.5.4\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 "" "Détermine si le bot se prendre en compte dans les statistiques du canal, ce " "qui peut les fausser 'particulièrement dans le cas où le bot relaye entre " "des canaux)" #: 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 "" "Détermine quels mots (c'est à dire, les morceaux de texte sans espace) sont " "considérés comme smileys par les stats." #: 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 "" "Détermine quels mots (c'est à dire, les morceaux de texte sans espace) sont " "considérés comme sadleys par les stats." #: 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 "" "[] [nom]\n" "\n" " Retourne les statistiques pour sur le . n'est " "nécessaire que si le message n'est pas envoyé sur le canal lui-même. Si " " n'est pas donné, il correspond par défaut à l'utilisateur envoyant la " "commande" #: plugin.py:262 msgid "I couldn't find you in my user database." msgstr "Je ne peux vous trouver dans ma base de données." #: plugin.py:275 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 a envoyé %n ; un total de %n, %n, %n, et %n ; %s de ces messages %s. %s " "est arrivé %n fois, est parti %n fois, a quitté %n fois, a kické %n fois, a " "été kické %n fois, a changé le topic %n fois, et a changé les modes %n fois." #: plugin.py:282 msgid "character" msgstr "caractère" #: plugin.py:283 plugin.py:377 msgid "word" msgstr "mot" #: plugin.py:284 plugin.py:378 msgid "smiley" msgstr "smiley" #: plugin.py:285 plugin.py:379 msgid "frown" msgstr "sadley" #: plugin.py:287 plugin.py:380 msgid "was an ACTION" msgstr "était une action" #: plugin.py:288 plugin.py:381 msgid "were ACTIONs" msgstr "étaient des ACTIONs" #: plugin.py:290 plugin.py:291 plugin.py:292 plugin.py:293 plugin.py:294 #: plugin.py:295 plugin.py:296 msgid "time" msgstr "" #: plugin.py:299 msgid "I have no stats for that %s in %s." msgstr "Je n'ai pas de statistiques pour %s sur %s." #: plugin.py:308 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" "Retourne le rang des utilisateurs en fonction de l'expression de " "statistiques données. Les variables valides dans l'expression de " "statistiques sont : 'msgs', 'chars', 'words', 'smileys', 'frowns', " "'actions', 'joins', 'parts', 'quits', 'kicks', 'kicked', 'topics', et " "'modes'. Toute expression mathématiques simple utilisant ces variables est " "autorisée." #: plugin.py:322 msgid "" "There's really no reason why you should have underscores or brackets in your " "mathematical expression. Please remove them." msgstr "" "Il n'y a aucune raison pour que vous ayez des underscores ou des crochets " "dans vos expressions mathématiques. Veuillez les supprimer." #: plugin.py:326 msgid "You can't use lambda in this command." msgstr "Vous ne pouvez utiliser lambda dans cette commande." #: plugin.py:340 msgid "stat variable" msgstr "Variable statistique" #: plugin.py:356 msgid "" "[]\n" "\n" " Returns the statistics for . is only necessary " "if\n" " the message isn't sent on the channel itself.\n" " " msgstr "" "[canal]\n" "\n" "Retourne les statistiques pour le . n'est nécessaire que si " "le message n'est pas envoyé sur le canal lui-même." #: plugin.py:362 msgid "I am not in %s." msgstr "Je ne suis pas dans %s." #: plugin.py:364 msgid "You must be in %s to use this command." msgstr "Vous devez être dans %s pour utiliser cette commande." #: plugin.py:371 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 "" "Sur %s il y a eu%v %i messages, contenant %i caractères, %n, %n, et %n ; %i " "de ces messages %s. Il y a eu %n, %n, %n, %n, %n, et %n. Il y a%v " "actuellement %n et le record du canal est de %n." #: plugin.py:382 msgid "join" msgstr "arrivée" #: plugin.py:383 msgid "part" msgstr "départ" #: plugin.py:384 msgid "quit" msgstr "quit" #: plugin.py:385 msgid "kick" msgstr "kick" #: plugin.py:386 msgid "mode" msgstr "mode" #: plugin.py:386 plugin.py:387 msgid "change" msgstr "changement" #: plugin.py:387 msgid "topic" msgstr "topic" #: plugin.py:389 plugin.py:390 msgid "user" msgstr "utilisateur" #: plugin.py:393 msgid "I've never been on %s." msgstr "Je n'ai jamais été sur %s." limnoria-2020.03.17/plugins/ChannelStats/locales/it.po0000644000175000017500000001347013634634532021772 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-07-05 13:41+0200\n" "Last-Translator: skizzhg \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\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 "" "Determina se il bot terrà statistiche del canale, probabilmente alterandole\n" " (in particolare in caso faccia l'inoltro dei messaggi attraverso i canali).\n" #: 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 "" "Determina quali parole (ovvero parti di testo senza spazi) sono considerate\n" " faccine sorridenti per le statistiche." #: 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 "" "Determina quali parole (ovvero parti di testo senza spazi) sono considerate\n" " faccine tristi per le statistiche." #: plugin.py:246 #, docstring 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" " Riporta le statistiche per su . è necessario\n" " solo se il messaggio non viene inviato nel canale stesso. Se \n" " non è specificato, passa all'utente che ha dato il comando.\n" " " #: plugin.py:259 msgid "I couldn't find you in my user database." msgstr "Non ti trovo nel mio database utenti." #: plugin.py:272 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 ha inviato %n; un totale di %n, %n, %n, e %n; %s di quei messaggi %s. %s è entrato %n volte, è uscito %n volte, si è disconnesso %n volte, ha espulso qualcuno %n volte, è stato espulso %n volte, ha modificato il topic %n volte ed ha cambiato il mode %n volte." #: plugin.py:279 msgid "character" msgstr "carattere" #: plugin.py:280 plugin.py:363 msgid "word" msgstr "parola" #: plugin.py:281 plugin.py:364 msgid "smiley" msgstr "faccina sorridente" #: plugin.py:282 plugin.py:365 msgid "frown" msgstr "faccina triste" #: plugin.py:284 plugin.py:366 msgid "was an ACTION" msgstr "è stata un'azione (ACTION)" #: plugin.py:285 plugin.py:367 msgid "were ACTIONs" msgstr "sono state azioni (ACTION)" #: plugin.py:287 plugin.py:288 plugin.py:289 plugin.py:290 plugin.py:291 #: plugin.py:292 plugin.py:293 msgid "time" msgstr "volta" #: plugin.py:296 msgid "I have no stats for that %s in %s." msgstr "Non ho statistiche per %s in %s." #: plugin.py:304 #, docstring 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" " Riporta la classifica degli utenti in base all'espresisone fornita.\n" " Le variabili valide sono: \"msgs\", \"chars\", \"words\", \"smileys\",\n" " \"frowns\", \"actions\", \"joins\", \"parts\", \"quits\", \"kicks\",\n" " \"kicked\", \"topics\" e \"modes\". È permessa qualsiasi espressione\n" " matematica che utilizzi queste variabili.\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 "Non v'è alcuna ragione di usare underscore o parentesi nelle espressioni matematiche; ti invito a rimuoverli." #: plugin.py:319 msgid "You can't use lambda in this command." msgstr "Non è possibile usare lambda in questo comando." #: plugin.py:333 msgid "stat variable" msgstr "variabile di statistica" #: plugin.py:349 #, docstring 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" " Riporta le statistiche di . è necessario\n" " solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:357 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 "In %s ci sono%v stati %i messaggi, contenenti %i caratteri, %n, %n, e %n; %i di questi messaggi %s. Ci sono stati %n, %n, %n, %n, %n, e %n. Attualmente ci sono%v %n e il canale ha raggiunto il picco di %n." #: plugin.py:368 msgid "join" msgstr "join" #: plugin.py:369 msgid "part" msgstr "part" #: plugin.py:370 msgid "quit" msgstr "quit" #: plugin.py:371 msgid "kick" msgstr "kick" #: plugin.py:372 msgid "mode" msgstr "mode" #: plugin.py:372 plugin.py:373 msgid "change" msgstr "modifiche" #: plugin.py:373 msgid "topic" msgstr "topic" #: plugin.py:375 plugin.py:376 msgid "user" msgstr "utente" #: plugin.py:379 msgid "I've never been on %s." msgstr "Non sono mai stato su %s." limnoria-2020.03.17/plugins/ChannelStats/plugin.py0000644000175000017500000003541313634634532021245 0ustar valval00000000000000### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2009-2010, James McCoy # 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 re import math import types import supybot.log as log import supybot.conf as conf from supybot.i18n import PluginInternationalization, internationalizeDocstring import supybot.utils as utils import supybot.world as world import supybot.ircdb as ircdb from supybot.commands import * import supybot.irclib as irclib import supybot.ircmsgs as ircmsgs import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.utils.math_evaluator import safe_eval, InvalidNode _ = PluginInternationalization('ChannelStats') class ChannelStat(irclib.IrcCommandDispatcher): _values = ['actions', 'chars', 'frowns', 'joins', 'kicks','modes', 'msgs', 'parts', 'quits', 'smileys', 'topics', 'words', 'users'] def __init__(self, actions=0, chars=0, frowns=0, joins=0, kicks=0, modes=0, msgs=0, parts=0, quits=0, smileys=0, topics=0, words=0, users=0): self.actions = actions self.chars = chars self.frowns = frowns self.joins = joins self.kicks = kicks self.modes = modes self.msgs = msgs self.parts = parts self.quits = quits self.smileys = smileys self.topics = topics self.words = words self.users = users def values(self): return [getattr(self, s) for s in self._values] def addMsg(self, msg): self.msgs += 1 method = self.dispatchCommand(msg.command, msg.args) if method is not None: method(msg) def doPayload(self, channel, payload): channel = plugins.getChannel(channel) self.chars += len(payload) self.words += len(payload.split()) fRe = conf.supybot.plugins.ChannelStats.get('frowns').get(channel)() sRe =conf.supybot.plugins.ChannelStats.get('smileys').get(channel)() self.frowns += len(fRe.findall(payload)) self.smileys += len(sRe.findall(payload)) def doPrivmsg(self, msg): isAction = ircmsgs.isAction(msg) if ircmsgs.isCtcp(msg) and not isAction: return self.doPayload(*msg.args) if isAction: self.actions += 1 def doTopic(self, msg): self.doPayload(*msg.args) self.topics += 1 def doKick(self, msg): self.kicks += 1 def doPart(self, msg): if len(msg.args) == 2: self.doPayload(*msg.args) self.parts += 1 def doJoin(self, msg): if len(msg.args) == 2: self.doPayload(*msg.args) self.joins += 1 # Handle max-users in the plugin since we need an irc object def doMode(self, msg): self.modes += 1 # doQuit is handled by the plugin. class UserStat(ChannelStat): _values = ['kicked'] + ChannelStat._values def __init__(self, kicked=0, *args): ChannelStat.__init__(self, *args) self.kicked = kicked def doKick(self, msg): self.doPayload(msg.args[0], msg.args[2]) self.kicks += 1 class StatsDB(plugins.ChannelUserDB): def __init__(self, *args, **kwargs): plugins.ChannelUserDB.__init__(self, *args, **kwargs) def serialize(self, v): return v.values() def deserialize(self, channel, id, L): L = list(map(int, L)) if id == 'channelStats': return ChannelStat(*L) else: return UserStat(*L) def addMsg(self, irc, msg, id=None): if msg.channel: channel = plugins.getChannel(msg.channel) if (channel, 'channelStats') not in self: self[channel, 'channelStats'] = ChannelStat() self[channel, 'channelStats'].addMsg(msg) try: if id is None: id = ircdb.users.getUserId(msg.prefix) except KeyError: return if (channel, id) not in self: self[channel, id] = UserStat() self[channel, id].addMsg(msg) def getChannelStats(self, channel): return self[channel, 'channelStats'] def getUserStats(self, channel, id): return self[channel, id] filename = conf.supybot.directories.data.dirize('ChannelStats.db') class ChannelStats(callbacks.Plugin): """This plugin keeps stats of the channel and returns them with the command 'channelstats'.""" noIgnore = True def __init__(self, irc): self.__parent = super(ChannelStats, self) self.__parent.__init__(irc) self.outFiltering = False self.db = StatsDB(filename) self._flush = self.db.flush world.flushers.append(self._flush) def die(self): world.flushers.remove(self._flush) self.db.close() self.__parent.die() def __call__(self, irc, msg): self.db.addMsg(irc, msg) super(ChannelStats, self).__call__(irc, msg) def outFilter(self, irc, msg): if msg.command == 'PRIVMSG': if msg.channel: if self.registryValue('selfStats', msg.channel, irc.network): try: self.outFiltering = True self.db.addMsg(irc, msg, 0) finally: self.outFiltering = False return msg def _setUsers(self, irc, channel): if (channel, 'channelStats') not in self.db: self.db[channel, 'channelStats'] = ChannelStat() oldUsers = self.db[channel, 'channelStats'].users newUsers = len(irc.state.channels[channel].users) self.db[channel, 'channelStats'].users = max(oldUsers, newUsers) def doJoin(self, irc, msg): self._setUsers(irc, msg.args[0]) def do366(self, irc, msg): self._setUsers(irc, msg.args[1]) def doQuit(self, irc, msg): try: id = ircdb.users.getUserId(msg.prefix) except KeyError: id = None for channel in msg.tagged('channels'): if (channel, 'channelStats') not in self.db: self.db[channel, 'channelStats'] = ChannelStat() self.db[channel, 'channelStats'].quits += 1 if id is not None: if (channel, id) not in self.db: self.db[channel, id] = UserStat() self.db[channel, id].quits += 1 def doKick(self, irc, msg): (channel, nick, _) = msg.args hostmask = irc.state.nickToHostmask(nick) try: id = ircdb.users.getUserId(hostmask) except KeyError: return if (channel, id) not in self.db: self.db[channel, id] = UserStat() self.db.channels[channel][id].kicked += 1 @internationalizeDocstring def stats(self, irc, msg, args, channel, name): """[] [] Returns the statistics for on . is only necessary if the message isn't sent on the channel itself. If isn't given, it defaults to the user sending the command. """ if msg.nick not in irc.state.channels[channel].users: irc.error(format('You must be in %s to use this command.', channel)) return if name and ircutils.strEqual(name, irc.nick): id = 0 elif not name: try: id = ircdb.users.getUserId(msg.prefix) name = ircdb.users.getUser(id).name except KeyError: irc.error(_('I couldn\'t find you in my user database.')) return elif not ircdb.users.hasUser(name): try: hostmask = irc.state.nickToHostmask(name) id = ircdb.users.getUserId(hostmask) except KeyError: irc.errorNoUser() return else: id = ircdb.users.getUserId(name) try: stats = self.db.getUserStats(channel, id) s = format(_('%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.'), name, (stats.msgs, 'message'), (stats.chars, _('character')), (stats.words, _('word')), (stats.smileys, _('smiley')), (stats.frowns, _('frown')), stats.actions, stats.actions == 1 and _('was an ACTION') or _('were ACTIONs'), name, (stats.joins, _('time')), (stats.parts, _('time')), (stats.quits, _('time')), (stats.kicks, _('time')), (stats.kicked, _('time')), (stats.topics, _('time')), (stats.modes, _('time'))) irc.reply(s) except KeyError: irc.error(format(_('I have no stats for that %s in %s.'), name, channel)) stats = wrap(stats, ['channeldb', additional('something')]) @internationalizeDocstring def rank(self, irc, msg, args, channel, expr): """[] Returns the ranking of users according to the given stat expression. Valid variables in the stat expression include 'msgs', 'chars', 'words', 'smileys', 'frowns', 'actions', 'joins', 'parts', 'quits', 'kicks', 'kicked', 'topics', and 'modes'. Any simple mathematical expression involving those variables is permitted. """ if msg.nick not in irc.state.channels[channel].users: irc.error(format('You must be in %s to use this command.', channel)) return expr = expr.lower() users = [] for ((c, id), stats) in self.db.items(): if ircutils.strEqual(c, channel) and \ (id == 0 or ircdb.users.hasUser(id)): e = {} for attr in stats._values: e[attr] = float(getattr(stats, attr)) try: v = safe_eval(expr, allow_ints=True, variables=e) except ZeroDivisionError: v = float('inf') except NameError as e: irc.errorInvalid(_('stat variable'), str(e)) except InvalidNode as e: irc.error(_('Invalid syntax: %s') % e.args[0], Raise=True) except Exception as e: irc.error(utils.exnToString(e), Raise=True) else: v = float(v) if id == 0: users.append((v, irc.nick)) else: users.append((v, ircdb.users.getUser(id).name)) users.sort() users.reverse() s = utils.str.commaAndify(['#%s %s (%.3g)' % (i+1, u, v) for (i, (v, u)) in enumerate(users)]) irc.reply(s) rank = wrap(rank, ['channeldb', 'text']) @internationalizeDocstring def channelstats(self, irc, msg, args, channel): """[] Returns the statistics for . is only necessary if the message isn't sent on the channel itself. """ if channel not in irc.state.channels: irc.error(_('I am not in %s.') % channel, Raise=True) elif msg.nick not in irc.state.channels[channel].users: irc.error(_('You must be in %s to use this command.') % channel, Raise=True) try: channeldb = conf.supybot.databases.plugins.channelSpecific. \ getChannelLink(channel) stats = self.db.getChannelStats(channeldb) curUsers = len(irc.state.channels[channel].users) s = format(_('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.'), channel, stats.msgs, stats.msgs, stats.chars, (stats.words, _('word')), (stats.smileys, _('smiley')), (stats.frowns, _('frown')), stats.actions, stats.actions == 1 and _('was an ACTION') or _('were ACTIONs'), (stats.joins, _('join')), (stats.parts, _('part')), (stats.quits, _('quit')), (stats.kicks, _('kick')), (stats.modes, _('mode'), _('change')), (stats.topics, _('topic'), _('change')), curUsers, (curUsers, _('user')), (stats.users, _('user'))) irc.reply(s) except KeyError: irc.error(format(_('I\'ve never been on %s.'), channel)) channelstats = wrap(channelstats, ['channel']) Class = ChannelStats # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/ChannelStats/test.py0000644000175000017500000001133213634634532020720 0ustar valval00000000000000### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2010, James McCoy # 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 * import supybot.ircdb as ircdb class ChannelStatsTestCase(ChannelPluginTestCase): plugins = ('ChannelStats', 'User') def setUp(self): ChannelPluginTestCase.setUp(self) self.prefix = 'foo!bar@baz' self.nick = 'foo' self.irc.feedMsg(ircmsgs.privmsg(self.irc.nick, 'register foo bar', prefix=self.prefix)) _ = self.irc.takeMsg() chanop = ircdb.makeChannelCapability(self.channel, 'op') ircdb.users.getUser(self.nick).addCapability(chanop) def test(self): self.irc.feedMsg(ircmsgs.join(self.channel, prefix=self.prefix)) self.assertNotError('channelstats') self.assertNotError('channelstats') self.assertNotError('channelstats') def testStats(self): self.assertError('channelstats stats %s' % self.nick) self.irc.feedMsg(ircmsgs.join(self.channel, prefix=self.prefix)) self.assertNotError('channelstats stats %s' % self.nick) self.assertNotError('channelstats stats %s' % self.nick.upper()) self.assertNotError('channelstats stats') self.assertRegexp('channelstats stats', self.nick) def testSelfStats(self): self.irc.feedMsg(ircmsgs.join(self.channel, prefix=self.prefix)) self.assertError('channelstats stats %s' % self.irc.nick) self.assertNotError('channelstats stats %s' % self.irc.nick) self.assertNotError('channelstats stats %s' % self.irc.nick) self.assertNotError('channelstats stats %s' % self.irc.nick.upper()) self.assertRegexp('channelstats rank chars', self.irc.nick) u = ircdb.users.getUser(self.prefix) u.addCapability(ircdb.makeChannelCapability(self.channel, 'op')) ircdb.users.setUser(u) try: conf.supybot.plugins.ChannelStats.selfStats.setValue(False) m1 = self.getMsg('channelstats stats %s' % self.irc.nick) m2 = self.getMsg('channelstats stats %s' % self.irc.nick) self.assertEqual(m1.args[1], m2.args[1]) finally: conf.supybot.plugins.ChannelStats.selfStats.setValue(True) def testNoKeyErrorStats(self): self.irc.feedMsg(ircmsgs.join(self.channel, prefix=self.prefix)) self.assertNotRegexp('stats sweede', 'KeyError') def testRank(self): self.irc.feedMsg(ircmsgs.join(self.channel, prefix=self.prefix)) self.assertError('channelstats stats %s' % self.irc.nick) self.assertNotError('channelstats stats %s' % self.irc.nick) self.assertNotError('channelstats stats %s' % self.irc.nick) self.assertNotError('channelstats stats %s' % self.irc.nick.upper()) self.assertNotError('channelstats stats %s' % self.nick) self.assertNotError('channelstats stats %s' % self.nick.upper()) self.assertNotError('channelstats stats') self.assertNotError('channelstats rank chars / msgs') self.assertNotError('channelstats rank kicks/kicked') # Tests inf self.assertNotError('channelstats rank log(msgs)') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Conditional/0000755000175000017500000000000013634634547017251 5ustar valval00000000000000limnoria-2020.03.17/plugins/Conditional/__init__.py0000644000175000017500000000531413634634532021357 0ustar valval00000000000000### # 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. ### """ Contains numerous conditional commands (such as 'if', 'and', and 'or'), which can be used on their own or with another plugin. """ 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__ = "0.1" __author__ = supybot.Author('Daniel Folkinshteyn', 'nanotube', 'nanotube@users.sourceforge.net') __maintainer__ = supybot.authors.limnoria_core # 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' from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Conditional/config.py0000644000175000017500000000523713634634532021071 0ustar valval00000000000000### # 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 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 themself 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: limnoria-2020.03.17/plugins/Conditional/locales/0000755000175000017500000000000013634634547020673 5ustar valval00000000000000limnoria-2020.03.17/plugins/Conditional/locales/fi.po0000644000175000017500000001746413634634532021637 0ustar valval00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen , 2011. # msgid "" msgstr "" "Project-Id-Version: Conditional plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 14:04+EET\n" "PO-Revision-Date: 2014-12-20 14:32+0200\n" "Last-Translator: Mikaela Suomalainen \n" "Language-Team: \n" "Language: fi\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-Generator: Poedit 1.6.10\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: plugin.py:57 #, fuzzy msgid "" "This plugin provides logic operators and other commands that\n" " enable you to run commands only if a condition is true. Useful for " "nested\n" " commands and scripting." msgstr "" "Tämä plugini tarjoaa loogiset operaattorit ja muita komentoja, jotka " "sallivat\n" " sinun suorittaa komentoja vain ehdon ollessa true. Hyödyllinen " "sisäkkäisille\n" " komennoille ja skriptaukselle." #: plugin.py:65 msgid "Run a command from message, as if command was sent over IRC." msgstr "Suorita komento viestistä, kuin komento olisi lähetetty IRC:stä." #: plugin.py:74 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" " Suorittaa if kehittyy todeksi, suorittaa " "\n" " jos se kehittyy epätodeksi.\n" "\n" " Käytä loogisia toimintoja, jotka on määritetty tässä laajennuksessa " "ja komentojen putkittamista\n" " eduksesi tässä.\n" " " #: plugin.py:91 msgid "" " [ ... ]\n" "\n" " Returns true if all conditions supplied evaluate to true.\n" " " msgstr "" " [ ... ]\n" " \n" "Palauttaa kaikki edellytykset jotka tukevat arviotumista todeksi.\n" " " #: plugin.py:103 msgid "" " [ ... ]\n" "\n" " Returns true if any one of conditions supplied evaluates to true.\n" " " msgstr "" " [ ... ]\n" " \n" " Palauttaa toden, jos yksikin edellytys tukee arvioimista todeksi.\n" " " #: plugin.py:115 msgid "" " [ ... ]\n" "\n" " Returns true if only one of conditions supplied evaluates to true.\n" " " msgstr "" " [ ... ]\n" " \n" "Palauttaa toden, jos vain yksi edellytys tukee arvioimista todeksi.\n" " " #: plugin.py:127 msgid "" " \n" "\n" " Does a string comparison on and .\n" " Returns true if they are equal.\n" " " msgstr "" " \n" " \n" " Tekee ketju vertailun ja . \n" " Palauttaa toden jos ne ovat yhtäläisia.\n" " " #: plugin.py:140 msgid "" " \n" "\n" " Does a string comparison on and .\n" " Returns true if they are not equal.\n" " " msgstr "" " \n" " \n" " Tekee ketju vertailun ja . \n" " Palauttaa toden, jos ne eivät ole yhtäläisia.\n" " " #: plugin.py:153 msgid "" " \n" "\n" " Does a string comparison on and .\n" " Returns true if is greater than .\n" " " msgstr "" " \n" " \n" " Tekee ketju vertailun ja . \n" " Palauttaa toden jos on suurempi kuin .\n" " " #: plugin.py:166 msgid "" " \n" "\n" " Does a string comparison on and .\n" " Returns true if is greater than or equal to .\n" " " msgstr "" " \n" " \n" " Tekee ketju vertailun ja . \n" " Palauttaa toden jos on suurempi tai yhtäläinen .\n" " " #: plugin.py:179 msgid "" " \n" "\n" " Does a string comparison on and .\n" " Returns true if is less than .\n" " " msgstr "" " \n" " \n" "Tekee ketju vertailun ja . \n" "Palauttaa toden jos on vähemmän kuin .\n" " " #: plugin.py:192 msgid "" " \n" "\n" " Does a string comparison on and .\n" " Returns true if is less than or equal to .\n" " " msgstr "" " \n" " \n" " Tekee ketju vertailun ja . \n" " Palauttaa toden jos on vähemmän tai yhtäläinen .\n" " " #: plugin.py:205 msgid "" "[--case-insensitive] \n" "\n" " Determines if is a substring of .\n" " Returns true if is contained in .\n" "\n" " Will only match case if --case-insensitive is not given.\n" " " msgstr "" "[--case-insensitive] \n" "\n" " Selvittää onko osa .\n" " Palauttaa true, jos sisältyy .\n" "\n" " Kirjainkoko merkitsee, mikäli --case-insensitive ei ole annettu.\n" " " #: plugin.py:225 msgid "" " \n" "\n" " Does a numeric comparison on and .\n" " Returns true if they are equal.\n" " " msgstr "" " \n" " \n" "Tekee numerollisen vertailun ja välillä. \n" "Palauttaa toden, jos ne ovat yhtäläisiä.\n" " " #: plugin.py:238 msgid "" " \n" "\n" " Does a numeric comparison on and .\n" " Returns true if they are not equal.\n" " " msgstr "" " \n" " \n" " Tekee numerollisen vertailun ja . \n" " Palauttaa toden, jos ne eivät ole yhtäläisiä.\n" " " #: plugin.py:251 msgid "" " \n" "\n" " Does a numeric comparison on and .\n" " Returns true if is greater than .\n" " " msgstr "" " \n" " \n" " Tekee numerollisen vertailun ja . \n" " Palauttaa toden jos on suurempi kuin .\n" " " #: plugin.py:264 msgid "" " \n" "\n" " Does a numeric comparison on and .\n" " Returns true if is greater than or equal to .\n" " " msgstr "" " \n" " \n" " Tekee numerollisen vertailun ja . \n" " Palauttaa toden, jos on suurempi tai yhtäläinen .\n" " " #: plugin.py:277 msgid "" " \n" "\n" " Does a numeric comparison on and .\n" " Returns true if is less than .\n" " " msgstr "" " \n" " \n" " Tekee numerollisen vertailun ja . \n" " Palauttaa toden jos on vähemmän kuin .\n" " " #: plugin.py:290 msgid "" " \n" "\n" " Does a numeric comparison on and .\n" " Returns true if is less than or equal to .\n" " " msgstr "" " \n" " \n" " Tekee numerollisen vertailun ja . \n" " Palauttaa toden jos on vähemmän tai yhtäläinen .\n" " " #~ msgid "" #~ "Add the help for 'plugin help Conditional' here\n" #~ " This should describe *how* to use this plugin." #~ msgstr "" #~ "Lisää ohje 'plugin help Conditional' tähän.\n" #~ " Tämän pitäisi kuvata *kuinka* tätä lisäosaa käytetään." limnoria-2020.03.17/plugins/Conditional/locales/fr.po0000644000175000017500000001542513634634532021643 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-01-22 13:46+CET\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: Limnoria \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" "Language: fr\n" #: plugin.py:57 msgid "" "Add the help for 'plugin help Conditional' here\n" " This should describe *how* to use this plugin." msgstr "" #: plugin.py:64 msgid "Run a command from message, as if command was sent over IRC." msgstr "." #: plugin.py:73 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:90 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:102 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:114 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:126 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:139 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:152 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:165 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:178 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:191 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:204 msgid "" "[--case-insensitive] \n" "\n" " Determines if is a substring of .\n" " Returns true if is contained in .\n" "\n" " Will only match case if --case-insensitive is not given.\n" " " msgstr "" "[--case-insensitive] <é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>.Ne prendra la casse en compte que si " "--case-insensitive n’est pas donné." #: 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 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>" limnoria-2020.03.17/plugins/Conditional/locales/it.po0000644000175000017500000001630113634634532021642 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-07-08 09:05+0200\n" "Last-Translator: skizzhg \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: 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:71 #, docstring msgid "Run a command from message, as if command was sent over IRC." msgstr "" #: plugin.py:80 #, 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 "" " \n" "\n" " Esegue se analizzata è vera, esegue se falsa.\n" "\n" " Utilizza gli altri operatori logici presenti in questo plugin e i comandi nidificati a tuo vantaggio.\n" " " #: plugin.py:97 #, docstring msgid "" " [ ... ]\n" "\n" " Returns true if all conditions supplied evaluate to true.\n" " " msgstr "" " [ ... ]\n" "\n" " Restituisce True se tutte le condizioni fornite sono vere.\n" " " #: plugin.py:109 #, docstring msgid "" " [ ... ]\n" "\n" " Returns true if any one of conditions supplied evaluates to true.\n" " " msgstr "" " [ ... ]\n" "\n" " Restituisce True se almeno una delle condizioni fornite è vera.\n" " " #: plugin.py:121 #, docstring msgid "" " [ ... ]\n" "\n" " Returns true if only one of conditions supplied evaluates to true.\n" " " msgstr "" " [ ... ]\n" "\n" " Restituisce True se solo una delle condizioni fornite è vera.\n" " " #: plugin.py:133 #, docstring msgid "" " \n" "\n" " Does a string comparison on and .\n" " Returns true if they are equal.\n" " " msgstr "" " \n" "\n" " Effettua una comparazione di stringhe tra ed .\n" " Restituisce True se sono uguali.\n" " " #: plugin.py:146 #, docstring msgid "" " \n" "\n" " Does a string comparison on and .\n" " Returns true if they are not equal.\n" " " msgstr "" " \n" "\n" " Effettua una comparazione di stringhe tra ed .\n" " Restituisce True se non sono uguali.\n" " " #: plugin.py:159 #, docstring msgid "" " \n" "\n" " Does a string comparison on and .\n" " Returns true if is greater than .\n" " " msgstr "" " \n" "\n" " Effettua una comparazione di stringhe tra ed .\n" " Restituisce True se è maggiore di .\n" " " #: plugin.py:172 #, docstring msgid "" " \n" "\n" " Does a string comparison on and .\n" " Returns true if is greater than or equal to .\n" " " msgstr "" " \n" "\n" " Effettua una comparazione di stringhe tra ed .\n" " Restituisce True se è maggiore o uguale a .\n" " " #: plugin.py:185 #, docstring msgid "" " \n" "\n" " Does a string comparison on and .\n" " Returns true if is less than .\n" " " msgstr "" " \n" "\n" " Effettua una comparazione di stringhe tra ed .\n" " Restituisce True se è minore di .\n" " " #: plugin.py:198 #, docstring msgid "" " \n" "\n" " Does a string comparison on and .\n" " Returns true if is less than or equal to .\n" " " msgstr "" " \n" "\n" " Effettua una comparazione di stringhe tra ed .\n" " Restituisce True se è minore o uguale a .\n" " " #: plugin.py:211 #, docstring msgid "" " \n" "\n" " Determines if is a substring of .\n" " Returns true if is contained in .\n" " " msgstr "" " \n" "\n" " Determina se è una sottostringa di .\n" " Restituisce True se è contenuto in .\n" " " #: plugin.py:224 #, docstring msgid "" " \n" "\n" " Does a numeric comparison on and .\n" " Returns true if they are equal.\n" " " msgstr "" " \n" "\n" " Effettua una comparazione numerica tra ed .\n" " Restituisce True se sono uguali.\n" " " #: plugin.py:237 #, docstring msgid "" " \n" "\n" " Does a numeric comparison on and .\n" " Returns true if they are not equal.\n" " " msgstr "" " \n" "\n" " Effettua una comparazione numerica tra ed .\n" " Restituisce True se non sono uguali.\n" " " #: plugin.py:250 #, docstring msgid "" " \n" "\n" " Does a numeric comparison on and .\n" " Returns true if is greater than .\n" " " msgstr "" " \n" "\n" " Effettua una comparazione numerica tra ed .\n" " Restituisce True se è maggiore di .\n" " " #: plugin.py:263 #, docstring msgid "" " \n" "\n" " Does a numeric comparison on and .\n" " Returns true if is greater than or equal to .\n" " " msgstr "" " \n" "\n" " Effettua una comparazione numerica tra ed .\n" " Restituisce True se è maggiore o uguale a .\n" " " #: plugin.py:276 #, docstring msgid "" " \n" "\n" " Does a numeric comparison on and .\n" " Returns true if is less than .\n" " " msgstr "" " \n" "\n" " Effettua una comparazione numerica tra ed .\n" " Restituisce True se è minore di .\n" " " #: plugin.py:289 #, docstring msgid "" " \n" "\n" " Does a numeric comparison on and .\n" " Returns true if is less than or equal to .\n" " " msgstr "" " \n" "\n" " Effettua una comparazione numerica tra ed .\n" " Restituisce True se è minore o uguale a .\n" " " limnoria-2020.03.17/plugins/Conditional/plugin.py0000644000175000017500000002604213634634532021117 0ustar valval00000000000000### # 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 import supybot.plugins as plugins import supybot.commands as commands import supybot.ircutils as ircutils import supybot.callbacks as callbacks import re import collections 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 first = commands.first many = commands.many wrap = commands.wrap getopts = commands.getopts boolean_or_int = first('int', 'boolean') class Conditional(callbacks.Plugin): """This plugin provides logic operators and other commands that enable you to run commands only if a condition is true. Useful for nested commands and scripting.""" 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, channel=msg.channel, network=irc.network) try: self.Proxy(irc.irc, msg, tokens) except Exception as e: self.log.exception('Uncaught exception in requested function:') @internationalizeDocstring 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) cif = wrap(cif, [boolean_or_int, 'something', 'something']) @internationalizeDocstring 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_or_int),]) @internationalizeDocstring 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_or_int),]) @internationalizeDocstring 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_or_int),]) @internationalizeDocstring 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']) @internationalizeDocstring 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']) @internationalizeDocstring 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']) @internationalizeDocstring 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']) @internationalizeDocstring 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']) @internationalizeDocstring 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']) @internationalizeDocstring def match(self, irc, msg, args, optlist, item1, item2): """[--case-insensitive] Determines if is a substring of . Returns true if is contained in . Will only match case if --case-insensitive is not given. """ optlist = dict(optlist) if 'case-insensitive' in optlist: item1 = item1.lower() item2 = item2.lower() if item2.find(item1) != -1: irc.reply('true') else: irc.reply('false') match = wrap(match, [getopts({'case-insensitive': ''}), 'something', 'something']) @internationalizeDocstring 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']) @internationalizeDocstring 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']) @internationalizeDocstring def ngt(self, irc, msg, args, item1, item2): """ Does a numeric comparison on and . Returns true if is greater than . """ if item1 > item2: irc.reply('true') else: irc.reply('false') ngt = wrap(ngt, ['float', 'float']) @internationalizeDocstring 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']) @internationalizeDocstring 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']) @internationalizeDocstring 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']) def cerror(self, irc, msg, args, testcommand): """ Runs and returns true if it raises an error; false otherwise. """ tokens = callbacks.tokenize(testcommand, channel=msg.channel, network=irc.network) InvalidCommand = collections.namedtuple('InvalidCommand', 'command') replies = [] errors = [] class ErrorReportingProxy(self.Proxy): def reply(self2, s, *args, **kwargs): replies.append(s) def error(self2, s, Raise=False, *args, **kwargs): errors.append(s) if Raise: raise ArgumentError def _callInvalidCommands(self2): errors.append(InvalidCommand(self2.args)) def evalArgs(self2): # We don't want the replies in the nested command to # be stored here. super(ErrorReportingProxy, self2).evalArgs(withClass=self.Proxy) try: ErrorReportingProxy(irc.irc, msg, tokens) except callbacks.ArgumentError as e: pass # TODO: do something with the results if errors: irc.reply('true') else: irc.reply('false') cerror = wrap(cerror, ['something']) Condition = internationalizeDocstring(Conditional) Class = Conditional # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Conditional/test.py0000644000175000017500000001446513634634532020606 0ustar valval00000000000000### # 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') self.assertRegexp('cif 0 "echo nay" "echo yay"', 'yay') self.assertRegexp('cif 1 "echo yay" "echo nay"', 'yay') self.assertRegexp('cif 4 "echo yay" "echo nay"', 'yay') self.assertRegexp('cif -1 "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.assertRegexp('match Bla moobLafoo', 'false') self.assertRegexp('match --case-insensitive Bla moobLafoo', 'true') 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') def testIferror(self): self.assertResponse('cerror "echo hi"', 'false') self.assertResponse('cerror "foobarbaz"', 'true') self.assertResponse('cerror "help foobarbaz"', 'true') # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Config/0000755000175000017500000000000013634634547016213 5ustar valval00000000000000limnoria-2020.03.17/plugins/Config/__init__.py0000644000175000017500000000435113634634532020321 0ustar valval00000000000000### # Copyright (c) 2004-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Handles configuration of the bot while it is running. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload reload(plugin) # In case we're being reloaded. if world.testing: from . import test Class = plugin.Class configure = config.configure limnoria-2020.03.17/plugins/Config/config.py0000644000175000017500000000466413634634532020036 0ustar valval00000000000000### # Copyright (c) 2004-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Config') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Config', True) Config = conf.registerPlugin('Config') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Config, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Config/locales/0000755000175000017500000000000013634634547017635 5ustar valval00000000000000limnoria-2020.03.17/plugins/Config/locales/de.po0000644000175000017500000001300713634634532020560 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-10-27 00:20+0100\n" "Last-Translator: Florian Besser \n" "Language: de\n" "Language-Team: German \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: German\n" #: plugin.py:103 msgid "configuration variable" msgstr "Konfigurationsvariable" #: plugin.py:109 msgid "settable configuration variable" msgstr "setzbare Konfigurationsvariable" #: 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" "Gibt die Konfigurationsvariablen aus die unter der gegeben Konfiguraions verfügbar sind. Falls eine Variable mehrere Werte unter ihr wird '@' Zeichen vorangestellt. Falls eine Variable ein 'Kanalwert' wird ein '#' Zeichen vorangestellt, es ist dann möglich diese Variable für jeden Kanal, mit dem 'channel' Befehl, separat zu setzen." #: plugin.py:148 msgid "There don't seem to be any values in %s." msgstr "Es scheint so als würde es keine Werte in %s geben." #: plugin.py:154 msgid "" "\n" "\n" " Searches for in the current configuration variables.\n" " " msgstr "" "\n" "\n" "Sucht nach in den momentanen Konfigurationsvariablen." #: plugin.py:167 msgid "There were no matching configuration variables." msgstr "Keine passende Konfigurationsvariable gefunden." #: plugin.py:174 msgid "Global: %s; %s: %s" msgstr "Global: %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 "Diese Registierungsvariable hat keinen Wert. Benutze den list Befehl um zu sehen welche Variablen in dieser Gruppe verfügbar sind." #: 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" "Falls angegeben ist wird die Kanal Konfigurationsvariable für auf gesetzt für . Andererseits wird der momentane Wert der Kanal Konfigurationsvariable ausgegben. wird nur benötigt wenn die Nachricht nicht im Kanal selbst gesendet wird." #: plugin.py:207 msgid "That configuration variable is not a channel-specific configuration variable." msgstr "Diese Konfigurationsvariable ist keine kanalspezifische Konfigurationsvariable." #: 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" "Falls angegeben wird, wird der Wert von auf gesetzt. Wenn nicht, wird der momentane Wert von ausgegben> Du kannst möglicherweise das vornstehende \"supybot.\" im Namen weglassen, falls du das möchtest." #: plugin.py:234 msgid "" "\n" "\n" " Returns the description of the configuration variable .\n" " " msgstr "" "\n" "\n" "Gibt die Beschreibung der Konfigurationsvariable aus." #: plugin.py:242 msgid " (Current value: %s)" msgstr " (Momentaner Wert: %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 "Diese Konfigurationsgruppe extistiert, es scheint aber so als wäre keine Hilfe verfügbar. Probiere \"config list\" um zu sehen ob es Werte darunter gibt." #: plugin.py:249 msgid "%s has no help." msgstr "%s hat keine Hilfe." #: plugin.py:254 msgid "" "\n" "\n" " Returns the default value of the configuration variable .\n" " " msgstr "" "\n" "\n" "Gibt den Standartwert der Konfigurationsvariable aus." #: plugin.py:264 msgid "" "takes no arguments\n" "\n" " Reloads the various configuration files (user database, channel\n" " database, registry, etc.).\n" " " msgstr "" "hat kein Argument\n" "\n" "Läd verschiedene Konfigurationsdateien neu (Benuter Datenbank, Kanal Datenbank, Registrierung, etc)." #: 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" "Exportier die öffentlichen Variablen deiner Konfiguration nach . Falls du deine Konfiguration jemandem zeigen möchtest, er aber keine Dinge wie Passwörter, etc. sehen soll, wird dieser Befehl eine \"bereinigte\" Konfiguration exportieren die für die Öffentlichkeit geeignet ist." limnoria-2020.03.17/plugins/Config/locales/fi.po0000644000175000017500000001405013634634532020565 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Finnish translation of Config plugin in Supybot\n" "POT-Creation-Date: 2014-12-20 13:30+EET\n" "PO-Revision-Date: \n" "Last-Translator: Mikaela Suomalainen \n" "Language-Team: \n" "Language: fi_FI\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 1.6.10\n" #: plugin.py:103 msgid "configuration variable" msgstr "asetusarvo" #: plugin.py:109 msgid "settable configuration variable" msgstr "asetettava asetusarvo" #: plugin.py:114 msgid "" "Provides access to the Supybot configuration. This is\n" " a core Supybot plugin that should not be removed!" msgstr "" "Tarjoaa pääsyn Supybotin asetuksiin. Tämä on Supybotin ydin plugini, jota ei " "pitäisi poistaa!" #: plugin.py:138 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:150 msgid "There don't seem to be any values in %s." msgstr "%s:ssä ei näytä olevan yhtään asetusarvoja." #: plugin.py:156 msgid "" "\n" "\n" " Searches for in the current configuration variables.\n" " " msgstr "" "\n" "\n" "Etsii nykyisistä asetus arvoista.\n" " " #: plugin.py:169 msgid "There were no matching configuration variables." msgstr "Täsmääviä asetusarvoja ei löytynyt." #: plugin.py:176 msgid "Global: %s; %s: %s" msgstr "Globaali: %s; %s: %s" #: plugin.py:187 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:202 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:209 msgid "" "That configuration variable is not a channel-specific configuration variable." msgstr "Tällä asetusarvolla ei ole kanava kohtaista asetusarvoa." #: plugin.py:222 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:236 msgid "" "\n" "\n" " Returns the description of the configuration variable .\n" " " msgstr "" "\n" "\n" " Palauttaa asetusarvon kuvauksen .\n" " " #: plugin.py:250 msgid " (Current global value: %s; current channel value: %s)" msgstr "" "(Nykyinen globaali asetusarvo: %s; nykyinen kanavakohtainen asetusarvo: %s)" #: plugin.py:254 plugin.py:256 msgid " (Current value: %s)" msgstr " (Nykyinen arvo: %s)" #: plugin.py:259 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:263 msgid "%s has no help." msgstr "%s:llä ei ole ohjetta." #: plugin.py:268 msgid "" "\n" "\n" " Returns the default value of the configuration variable .\n" " " msgstr "" "\n" "\n" "Palauttaa asetusarvon oletusarvon .\n" " " #: plugin.py:278 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:289 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" " " #: plugin.py:303 msgid "" "\n" "\n" " Resets the configuration variable to its default value.\n" " " msgstr "" "\n" "\n" " Palauttaa asetusarvon oletukseksi.\n" " " limnoria-2020.03.17/plugins/Config/locales/fr.po0000644000175000017500000001335613634634532020606 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2013-03-03 19:39+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Limnoria \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" #: plugin.py:103 msgid "configuration variable" msgstr "variable de configuration" #: plugin.py:109 msgid "settable configuration variable" msgstr "variable de configuration modifiable" #: 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" "Retourne les variables de configuration qui sont dans le de " "configuration. Si la variable a des sous-variables, elle sera précédée par " "le signe '@'. Si une variable est une 'ChannelValue', elle sera précédée par " "le signe '#'. Plus d'informations : http://supybot.fr.cr/Configuration" #: plugin.py:148 msgid "There don't seem to be any values in %s." msgstr "Il semble n'y avoir aucune valeur dans %s." #: plugin.py:154 msgid "" "\n" "\n" " Searches for in the current configuration variables.\n" " " msgstr "" "\n" "\n" "Recherche le dans les variables de configuration." #: plugin.py:167 msgid "There were no matching configuration variables." msgstr "Il n'y a aucune variable de configuration correspondante." #: plugin.py:174 msgid "Global: %s; %s: %s" msgstr "Globale : %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 "" "Cette variable de registre n'a aucune valeur. Utilisez la commande 'list' " "sur ce plugin pour voir quelles variables sont disponibles pour ce groupe." #: 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" "Si la est donnée, défini la variable de configuration à " " sur le . Sinon, retourne la configuration actuelle du " " pour . n'est nécessaire que si le message n'est pas " "envoyé sur le canal lui-même." #: plugin.py:207 msgid "" "That configuration variable is not a channel-specific configuration variable." msgstr "" "Cette variable de configuration n'est pas une variable spécifique à un canal." #: 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" "Si la est donnée, défini la variable de configuration à " ". Sinon, retourne la configuration actuelle de . Vous pouvez " "omettre le 'supybot.' au début du ." #: plugin.py:234 msgid "" "\n" "\n" " Returns the description of the configuration variable .\n" " " msgstr "" "\n" "\n" "Retourne la description de la variable de configuration ." #: plugin.py:242 msgid " (Current value: %s)" msgstr " (Valeur courante : %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 "" "Ce groupe de configuration existe mais semble ne pas avoir d'aide. Essayez " "\"config list %s\" pour voir si il a des valeurs filles." #: plugin.py:249 msgid "%s has no help." msgstr "%s n'a pas d'aide." #: plugin.py:254 msgid "" "\n" "\n" " Returns the default value of the configuration variable .\n" " " msgstr "" "\n" "\n" "Retourne la valeur par défaut de la variable de configuration ." #: plugin.py:264 msgid "" "takes no arguments\n" "\n" " Reloads the various configuration files (user database, channel\n" " database, registry, etc.).\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Recharge les différents fichiers de configuration (base d'utilisateurs, base " "de canaux, registre, etc.)." #: 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" "Exporte les variables de configuration publiques dans le fichier\n" "\n" " Resets the configuration variable to its default value.\n" " " msgstr "" "\n" "\n" "Réinitialise la variable à sa valeur par défaut." limnoria-2020.03.17/plugins/Config/locales/hu.po0000644000175000017500000001332013634634532020602 0ustar valval00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: Limnoria Config\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-07-30 22:16+0100\n" "Last-Translator: nyuszika7h \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" #: plugin.py:103 msgid "configuration variable" msgstr "konfigurációs változó" #: plugin.py:109 msgid "settable configuration variable" msgstr "beállítható konfigurációs változó" #: plugin.py:136 #, docstring 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" "Kiírja a megadott konfigurációs alatt elérhető konfigurációs változókat. Ha egy változó alatt vannak értékek, megelőzi egy '@' jel. Ha egy változó egy 'ChannelValue', tehát külön konfigurálható mindegyik csatornához a 'channel' parancs használatával ebben a bővítményben, megelőzi egy '#' jel." #: plugin.py:148 msgid "There don't seem to be any values in %s." msgstr "Nincs semmilyen érték %s-ban." #: plugin.py:154 #, docstring msgid "" "\n" "\n" " Searches for in the current configuration variables.\n" " " msgstr "" "\n" "\n" "-ra keres az aktuális konfigurációs változók között." #: plugin.py:167 msgid "There were no matching configuration variables." msgstr "Nincsenek illeszkedő konfigurációs változók." #: plugin.py:174 msgid "Global: %s; %s: %s" msgstr "Globális: %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 "Ennek az adatbázis-bejegyzésnek nincs értéke. Használd a list parancsot ebben a bővítményben, hogy lásd, milyen változók érhetők el ebben a csoportban." #: plugin.py:200 #, docstring 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 "" "[] [<érték>]\n" "\n" "Ha <érték> meg van adva, a csatorna konfigurációs változóját <érték>-re állítja -ban. Egyébként kiírja a jelenlegi csatorna konfigurációs változáját. csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:207 msgid "That configuration variable is not a channel-specific configuration variable." msgstr "Ez a konfigurációs változó nem egy csatorna-függő konfigurációs változó." #: plugin.py:220 #, docstring 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 "" " [<érték>]\n" "\n" "Ha <érték> meg van adva, értékét <érték>-re állítja. Egyébként kiírja jelenlegi értékét. Kihagyhatod a megelőző \"supybot.\"-ot a névből, ha úgy szeretnéd." #: plugin.py:234 #, docstring msgid "" "\n" "\n" " Returns the description of the configuration variable .\n" " " msgstr "" "\n" "\n" "Kiírja a konfigurációs változó leírását." #: plugin.py:242 msgid " (Current value: %s)" msgstr " (Jelenlegi érték: %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 "Ez a konfigurációs csoport létezik, de úgy tűnik, nincs segítsége. Próbáld meg a \"config list %s\"-t, hogy lásd, vannak-e gyerek értékei." #: plugin.py:249 msgid "%s has no help." msgstr "%s-nak nincs segítsége." #: plugin.py:254 #, docstring msgid "" "\n" "\n" " Returns the default value of the configuration variable .\n" " " msgstr "" "\n" "\n" "Kiírja a konfigurációs változó alapértelmezett értékét." #: plugin.py:264 #, docstring msgid "" "takes no arguments\n" "\n" " Reloads the various configuration files (user database, channel\n" " database, registry, etc.).\n" " " msgstr "" "paraméter nélküli\n" "\n" "Újratölti a különféle konfigurációs fájlokat (felhasználó-adatbázis, csatorna-adatbázis, beállításjegyzék stb.)." #: plugin.py:275 #, docstring 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" "Exportálja a konfigurációd publikus változóit -be. Ha meg szeretnéd mutatni valakinek a konfigurációs fájlodat, de nem szeretnéd, hogy az az ember láthassa az olyan dolgokat mint jelszavak stb., ez a parancs egy \"higiénikus\" konfigurációs fájlt exportál, ami megfelelő nyilvános megmutatásra." limnoria-2020.03.17/plugins/Config/locales/it.po0000644000175000017500000001325613634634532020612 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-28 12:33+0200\n" "Last-Translator: skizzhg \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugin.py:103 msgid "configuration variable" msgstr "variabile di configurazione" #: plugin.py:109 msgid "settable configuration variable" msgstr "variabile di configurazione impostabile" #: plugin.py:136 #, docstring 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" " Riporta le variabili di configurazione disponibili per il dato \n" " di configurazione. Se una variabile ha delle sottovariabili, sarà preceduta\n" " dal simbolo \"@\". Se una variabile è 'ChannelValue', ovvero può essere\n" " configurata separatamente per ciascun canale tramite il comando \"channel\"\n" " di questo plugin, sarà precedutà dal simbolo \"#\".\n" " " #: plugin.py:148 msgid "There don't seem to be any values in %s." msgstr "Non sembra esserci alcun valore in %s." #: plugin.py:154 #, docstring msgid "" "\n" "\n" " Searches for in the current configuration variables.\n" " " msgstr "" "\n" "\n" " Cerca nelle variabili di configurazione.\n" " " #: plugin.py:167 msgid "There were no matching configuration variables." msgstr "Non c'è nessuna variabile di configurazione corrispondente." #: plugin.py:174 msgid "Global: %s; %s: %s" msgstr "Globale: %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 "Questa variabile di registro non ha un valore. Utilizzare il comando \"list\" in questo plugin per vedere quali variabili sono disponibili per questo gruppo." #: plugin.py:200 #, docstring 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" " Se è fornito, imposta la variabile di configurazione \n" " a per . Altrimenti restituisce l'attuale valore di\n" " configurazione di . è necessario solo se il messaggio\n" " non viene inviato nel canale stesso." #: plugin.py:207 msgid "That configuration variable is not a channel-specific configuration variable." msgstr "Questa variabile di configurazione non è specifica di un canale." #: plugin.py:220 #, docstring 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" " Se è fornito, imposta il valore di a . Altrimenti riporta\n" " l'attuale valore di . È possibile omettere \"supybot.\" prima del nome.\n" " " #: plugin.py:234 #, docstring msgid "" "\n" "\n" " Returns the description of the configuration variable .\n" " " msgstr "" "\n" "\n" " Riporta la descrizione della variabile di configurazione .\n" " " #: plugin.py:242 msgid " (Current value: %s)" msgstr " (Valore attuale: %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 "Questo gruppo di configurazione esiste ma sembra non avere un help. Prova \"config list %s\" per vedere se ha dei sottovalori." #: plugin.py:249 msgid "%s has no help." msgstr "%s non ha un help." #: plugin.py:254 #, docstring msgid "" "\n" "\n" " Returns the default value of the configuration variable .\n" " " msgstr "" "\n" "\n" " Riporta il valore predefinito della variabile di configurazione .\n" " " #: plugin.py:264 #, docstring msgid "" "takes no arguments\n" "\n" " Reloads the various configuration files (user database, channel\n" " database, registry, etc.).\n" " " msgstr "" "non necessita argomenti\n" "\n" " Ricarica i file di configurazione (database utenti, database canale, registro, ecc.).\n" " " #: plugin.py:275 #, docstring 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" " Esporta le variabili di configurazione in . Se vuoi\n" " mostrare a qualcuno il tuo file di configurazione evitando di\n" " rivelare dati sensibili come le password, questo comando creerà\n" " un file \"pulito\" adatto ad essere mostrato pubblicamente.\n" " " limnoria-2020.03.17/plugins/Config/plugin.py0000644000175000017500000004305013634634532020057 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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 os import signal import supybot.log as log import supybot.conf as conf import supybot.utils as utils import supybot.world as world import supybot.ircdb as ircdb from supybot.commands import * from supybot.utils.iter import all import supybot.ircutils as ircutils import supybot.registry as registry import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Config') ### # Now, to setup the registry. ### def getWrapper(name): parts = registry.split(name) if not parts or parts[0] not in ('supybot', 'users'): raise registry.InvalidRegistryName(name) group = getattr(conf, parts.pop(0)) while parts: part = parts.pop(0) if group.__hasattr__(part): group = group.get(part) else: # We'll raise registry.InvalidRegistryName here so # that we have a useful error message for the user. raise registry.InvalidRegistryName(name) return group def getCapability(irc, name): capability = 'owner' # Default to requiring the owner capability. if not name.startswith('supybot') and not name.startswith('users'): name = 'supybot.' + name parts = registry.split(name) group = getattr(conf, parts.pop(0)) while parts: part = parts.pop(0) group = group.get(part) if not getattr(group, '_opSettable', True): return 'owner' if irc.isChannel(part): # If a registry value has a channel in it, it requires a # 'channel,op' capability, or so we assume. We'll see if we're # proven wrong. capability = ircdb.makeChannelCapability(part, 'op') ### Do more later, for specific capabilities/sections. return capability def isReadOnly(name): """Prevents changing certain config variables to gain shell access via a vulnerable IRC network.""" parts = registry.split(name.lower()) if parts[0] != 'supybot': parts.insert(0, 'supybot') if parts == ['supybot', 'commands', 'allowshell'] and \ not conf.supybot.commands.allowShell(): # allow setting supybot.commands.allowShell from True to False, # but not from False to True. # Otherwise an IRC network could overwrite it. return True elif parts[0:2] == ['supybot', 'directories'] and \ not conf.supybot.commands.allowShell(): # Setting plugins directory allows for arbitrary code execution if # an attacker can both use the IRC network to MITM and upload files # on the server (eg. with a web CMS). # Setting other directories allows writing data at arbitrary # locations. return True else: return False def _reload(): ircdb.users.reload() ircdb.ignores.reload() ircdb.channels.reload() registry.open_registry(world.registryFilename) def _hupHandler(sig, frame): log.info('Received SIGHUP, reloading configuration.') _reload() if os.name == 'posix': signal.signal(signal.SIGHUP, _hupHandler) def getConfigVar(irc, msg, args, state): name = args[0] if name.startswith('conf.'): name = name[5:] if not name.startswith('supybot') and not name.startswith('users'): name = 'supybot.' + name try: group = getWrapper(name) state.args.append(group) del args[0] except registry.InvalidRegistryName as e: state.errorInvalid(_('configuration variable'), str(e)) addConverter('configVar', getConfigVar) def getSettableConfigVar(irc, msg, args, state): getConfigVar(irc, msg, args, state) if not hasattr(state.args[-1], 'set'): state.errorInvalid(_('settable configuration variable'), state.args[-1]._name) addConverter('settableConfigVar', getSettableConfigVar) class Config(callbacks.Plugin): """Provides access to the Supybot configuration. This is a core Supybot plugin that should not be removed!""" def callCommand(self, command, irc, msg, *args, **kwargs): try: super(Config, self).callCommand(command, irc, msg, *args, **kwargs) except registry.InvalidRegistryValue as e: irc.error(str(e)) def _list(self, irc, group): L = [] for (vname, v) in group._children.items(): if getattr(group, '_networkValue', False) and \ vname.startswith(':'): # Skip pseudo-children that are network names continue if getattr(group, '_channelValue', False) and \ irc.isChannel(vname): # Skip pseudo-children that are channel names continue if getattr(v, '_networkValue', False): vname = ':' + vname if getattr(v, '_channelValue', False): vname = '#' + vname if v._added and not all(irc.isChannel, v._added): vname = '@' + vname L.append(vname) utils.sortBy(str.lower, L) return L @internationalizeDocstring def list(self, irc, msg, args, group): """ Returns the configuration variables available under the given configuration . If a variable has values under it, it is preceded by an '@' sign. If a variable is a 'ChannelValue', that is, it can be separately configured for each channel using the 'channel' command in this plugin, it is preceded by an '#' sign. """ L = self._list(irc, group) if L: irc.reply(format('%L', sorted(L))) else: irc.error(_('There don\'t seem to be any values in %s.') % group._name) list = wrap(list, ['configVar']) @internationalizeDocstring def search(self, irc, msg, args, word): """ Searches for in the current configuration variables. """ L = [] for (name, x) in conf.supybot.getValues(getChildren=True): if word in name.lower(): possibleChannel = registry.split(name)[-1] if not irc.isChannel(possibleChannel): L.append(name) if L: irc.reply(format('%L', L)) else: irc.reply(_('There were no matching configuration variables.')) search = wrap(search, ['lowered']) # XXX compose with withoutSpaces? def _getValue(self, irc, msg, group, network=None, channel=None, addGlobal=False): global_group = group global_value = str(group) or ' ' group = group.getSpecific( network=network.network, channel=channel, check=False) value = str(group) or ' ' if addGlobal and not irc.nested: if global_group._channelValue and channel: # TODO: also show the network value when relevant value = _( 'Global: %(global_value)s; ' '%(channel_name)s @ %(network_name)s: %(channel_value)s') % { 'global_value': global_value, 'channel_name': msg.channel, 'network_name': irc.network, 'channel_value': value, } elif global_group._networkValue and network: value = _( 'Global: %(global_value)s; ' '%(network_name)s: %(network_value)s') % { 'global_value': global_value, 'network_name': irc.network, 'network_value': value, } if hasattr(global_group, 'value'): if not global_group._private: return (value, None) else: capability = getCapability(irc, group._name) if ircdb.checkCapability(msg.prefix, capability): return (value, True) else: irc.errorNoCapability(capability, Raise=True) else: irc.error(_('That registry variable has no value. Use the list ' 'command in this plugin to see what variables are ' 'available in this group.'), Raise=True) def _setValue(self, irc, msg, group, value): if isReadOnly(group._name): irc.error(_("This configuration variable is not writeable " "via IRC. To change it you have to: 1) use the 'flush' command 2) edit " "the config file 3) use the 'config reload' command."), Raise=True) capability = getCapability(irc, group._name) if ircdb.checkCapability(msg.prefix, capability): # I think callCommand catches exceptions here. Should it? group.set(value) else: irc.errorNoCapability(capability, Raise=True) @internationalizeDocstring def channel(self, irc, msg, args, network, channels, group, value): """[] [] [] If is given, sets the channel configuration variable for to for on the . Otherwise, returns the current channel configuration value of . is only necessary if the message isn't sent in the channel itself. More than one channel may be given at once by separating them with commas. defaults to the current network.""" if not group._channelValue: irc.error(_('That configuration variable is not a channel-specific ' 'configuration variable.')) return if value is not None: for channel in channels: assert irc.isChannel(channel) # Sets the non-network-specific value, for forward # compatibility, ie. this will work even if the owner rolls # back Limnoria to an older version. # It's also an easy way to support plugins which are not # network-aware. self._setValue(irc, msg, group.get(channel), value) if network != '*': # Set the network-specific value self._setValue(irc, msg, group.get(':' + network.network).get(channel), value) irc.replySuccess() else: if network == '*': network = None values = [] private = None for channel in channels: (value, private_value) = \ self._getValue(irc, msg, group, network, channel) values.append((channel, value)) if private_value: private = True if len(channels) > 1: irc.reply('; '.join(['%s: %s' % (channel, value) for (channel, value) in values]), private=private) else: irc.reply(values[0][1], private=private) channel = wrap(channel, [optional(first(('literal', '*'), 'networkIrc')), 'channels', 'settableConfigVar', additional('text')]) def network(self, irc, msg, args, network, group, value): """[] [] If is given, sets the network configuration variable for to for . Otherwise, returns the current network configuration value of . defaults to the current network.""" if not group._networkValue: irc.error(_('That configuration variable is not a network-specific ' 'configuration variable.')) return if value is not None: self._setValue(irc, msg, group.get(':' + network.network), value) irc.replySuccess() else: values = [] private = None (value, private) = \ self._getValue(irc, msg, group, network) irc.reply(value, private=private) network = wrap(network, ['networkIrc', 'settableConfigVar', additional('text')]) @internationalizeDocstring def config(self, irc, msg, args, group, value): """ [] If is given, sets the value of to . Otherwise, returns the current value of . You may omit the leading "supybot." in the name if you so choose. """ if value is not None: self._setValue(irc, msg, group, value) irc.replySuccess() else: (value, private) = self._getValue( irc, msg, group, network=irc, channel=msg.channel, addGlobal=group._channelValue or group._networkValue) irc.reply(value, private=private) config = wrap(config, ['settableConfigVar', additional('text')]) @internationalizeDocstring def help(self, irc, msg, args, group): """ Returns the description of the configuration variable . """ if hasattr(group, '_help'): s = group.help() if s: if hasattr(group, 'value') and not group._private: if msg.channel and \ msg.channel in group._children: globvalue = str(group) chanvalue = str(group.get(msg.channel)) if chanvalue != globvalue: s += _(' (Current global value: %s; ' 'current channel value: %s)') % \ (globvalue, chanvalue) else: s += _(' (Current value: %s)') % group else: s += _(' (Current value: %s)') % group irc.reply(s) else: irc.reply(_('That configuration group exists, but seems to ' 'have no help. Try "config list %s" to see if it ' 'has any children values.') % group._name) else: irc.error(_('%s has no help.') % group._name) help = wrap(help, ['configVar']) @internationalizeDocstring def default(self, irc, msg, args, group): """ Returns the default value of the configuration variable . """ v = group.__class__(group._default, '') irc.reply(str(v)) default = wrap(default, ['settableConfigVar']) @internationalizeDocstring def reload(self, irc, msg, args): """takes no arguments Reloads the various configuration files (user database, channel database, registry, etc.). """ _reload() # This was factored out for SIGHUP handling. irc.replySuccess() reload = wrap(reload, [('checkCapability', 'owner')]) @internationalizeDocstring def export(self, irc, msg, args, filename): """ Exports the public variables of your configuration to . If you want to show someone your configuration file, but you don't want that person to be able to see things like passwords, etc., this command will export a "sanitized" configuration file suitable for showing publicly. """ if not conf.supybot.commands.allowShell(): # Disallow writing arbitrary files irc.error('This command is not available, because ' 'supybot.commands.allowShell is False.', Raise=True) registry.close(conf.supybot, filename, private=False) irc.replySuccess() export = wrap(export, [('checkCapability', 'owner'), 'filename']) @internationalizeDocstring def setdefault(self, irc, msg, args, group): """ Resets the configuration variable to its default value. """ v = str(group.__class__(group._default, '')) self._setValue(irc, msg, group, v) irc.replySuccess() setdefault = wrap(setdefault, ['settableConfigVar']) Class = Config # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Config/test.py0000644000175000017500000003123313634634532017540 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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 random from supybot.test import * import supybot.conf as conf _letters = 'abcdefghijklmnopqrstuvwxyz' def random_string(): return ''.join(random.choice(_letters) for _ in range(16)) class ConfigTestCase(ChannelPluginTestCase): # We add utilities so there's something in supybot.plugins. plugins = ('Config', 'User', 'Utilities') prefix1 = 'somethingElse!user@host1.tld' prefix2 = 'EvensomethingElse!user@host2.tld' prefix3 = 'Completely!Different@host3.tld__no_testcap__' def testGet(self): self.assertNotRegexp('config get supybot.reply', r'registry\.Group') self.assertResponse('config supybot.protocols.irc.throttleTime', '0.0') def testList(self): self.assertError('config list asldfkj') self.assertError('config list supybot.asdfkjsldf') self.assertNotError('config list supybot') self.assertRegexp('config list supybot.replies', ', #:errorOwner, ') self.assertRegexp('config list supybot', r'@plugins.*@replies.*@reply') def testListExcludes(self): """Checks that 'config list' excludes pseudo-children of network-specific and channel-specific variables.""" self.assertNotError( 'config channel #zpojfejf supybot.replies.error foo') self.assertRegexp('config list supybot.replies.error', "There don't seem to be any values") def testHelp(self): self.assertError('config help alsdkfj') self.assertError('config help supybot.alsdkfj') self.assertNotError('config help supybot') # We tell the user to list. self.assertNotError('config help supybot.plugins') self.assertNotError('config help supybot.replies.success') self.assertNotError('config help replies.success') def testHelpDoesNotAssertionError(self): self.assertNotRegexp('config help ' # Cont'd. 'supybot.commands.defaultPlugins.help', 'AssertionError') def testHelpExhaustively(self): L = conf.supybot.getValues(getChildren=True) for (name, v) in L: self.assertNotError('config help %s' % name) def testSearch(self): self.assertNotError('config search chars') self.assertNotError('config channel reply.whenAddressedBy.chars @') self.assertNotRegexp('config search chars', self.channel) def testDefault(self): self.assertNotError('config default ' 'supybot.replies.genericNoCapability') def testConfigErrors(self): self.assertRegexp('config supybot.replies.', 'not a valid') self.assertRegexp('config supybot.repl', 'not a valid') self.assertRegexp('config supybot.reply.withNickPrefix 123', 'True or False.*, not \'123\'.') self.assertRegexp('config supybot.replies foo', 'settable') def testReadOnly(self): old_plugins_dirs = conf.supybot.directories.plugins() try: self.assertResponse('config supybot.commands.allowShell', 'True') self.assertNotError('config supybot.directories.plugins dir1') self.assertNotError('config supybot.commands.allowShell True') self.assertResponse('config supybot.commands.allowShell', 'True') self.assertResponse('config supybot.directories.plugins', 'dir1') self.assertNotError('config supybot.commands.allowShell False') self.assertResponse('config supybot.commands.allowShell', 'False') self.assertRegexp('config supybot.directories.plugins dir2', 'Error.*not writeable') self.assertResponse('config supybot.directories.plugins', 'dir1') self.assertRegexp('config supybot.commands.allowShell True', 'Error.*not writeable') self.assertResponse('config supybot.commands.allowShell', 'False') self.assertRegexp('config commands.allowShell True', 'Error.*not writeable') self.assertResponse('config supybot.commands.allowShell', 'False') self.assertRegexp('config COMMANDS.ALLOWSHELL True', 'Error.*not writeable') self.assertResponse('config supybot.commands.allowShell', 'False') finally: conf.supybot.commands.allowShell.setValue(True) conf.supybot.directories.plugins.setValue(old_plugins_dirs) def testOpEditable(self): var_name = 'testOpEditable' + random_string() conf.registerChannelValue(conf.supybot.plugins.Config, var_name, registry.Integer(0, 'help')) self.assertNotError('register bar passwd', frm=self.prefix3, private=True) self.assertRegexp('whoami', 'bar', frm=self.prefix3) ircdb.users.getUser('bar').addCapability(self.channel + ',op') self.assertRegexp('config plugins.Config.%s 1' % var_name, '^Completely: Error: ', frm=self.prefix3) self.assertResponse('config plugins.Config.%s' % var_name, 'Global: 0; #test @ test: 0') self.assertNotRegexp('config channel plugins.Config.%s 1' % var_name, '^Completely: Error: ', frm=self.prefix3) self.assertResponse('config plugins.Config.%s' % var_name, 'Global: 0; #test @ test: 1') def testOpNonEditable(self): var_name = 'testOpNonEditable' + random_string() conf.registerChannelValue(conf.supybot.plugins.Config, var_name, registry.Integer(0, 'help'), opSettable=False) self.assertNotError('register bar passwd', frm=self.prefix3, private=True) self.assertRegexp('whoami', 'bar', frm=self.prefix3) ircdb.users.getUser('bar').addCapability(self.channel + ',op') self.assertRegexp('config plugins.Config.%s 1' % var_name, '^Completely: Error: ', frm=self.prefix3) self.assertResponse('config plugins.Config.%s' % var_name, 'Global: 0; #test @ test: 0') self.assertRegexp('config channel plugins.Config.%s 1' % var_name, '^Completely: Error: ', frm=self.prefix3) self.assertResponse('config plugins.Config.%s' % var_name, 'Global: 0; #test @ test: 0') self.assertNotRegexp('config channel plugins.Config.%s 1' % var_name, '^Completely: Error: ') self.assertResponse('config plugins.Config.%s' % var_name, 'Global: 0; #test @ test: 1') def testChannel(self): self.assertResponse('config reply.whenAddressedBy.strings ^', 'The operation succeeded.') self.assertResponse('config channel reply.whenAddressedBy.strings @', 'The operation succeeded.') self.assertResponse('config channel reply.whenAddressedBy.strings', '@') self.assertNotError('config channel reply.whenAddressedBy.strings $') self.assertResponse('config channel #testchan1 reply.whenAddressedBy.strings', '^') self.assertResponse('config channel #testchan2 reply.whenAddressedBy.strings', '^') self.assertNotError('config channel #testchan1,#testchan2 reply.whenAddressedBy.strings .') self.assertResponse('config channel reply.whenAddressedBy.strings', '$') self.assertResponse('config channel #testchan1 reply.whenAddressedBy.strings', '.') self.assertResponse('config channel #testchan2 reply.whenAddressedBy.strings', '.') def testNetwork(self): getTestIrc('testnet1') getTestIrc('testnet2') self.assertResponse('config reply.whenAddressedBy.strings ^', 'The operation succeeded.') self.assertResponse('config network reply.whenAddressedBy.strings @', 'The operation succeeded.') self.assertResponse('config network reply.whenAddressedBy.strings', '@') self.assertNotError('config network reply.whenAddressedBy.strings $') self.assertResponse('config network testnet1 reply.whenAddressedBy.strings', '^') self.assertResponse('config network testnet2 reply.whenAddressedBy.strings', '^') self.assertResponse('config network reply.whenAddressedBy.strings', '$') self.assertResponse('config network testnet1 reply.whenAddressedBy.strings', '^') self.assertResponse('config network testnet2 reply.whenAddressedBy.strings', '^') self.assertNotError('config network testnet1 reply.whenAddressedBy.strings =') self.assertResponse('config network testnet1 reply.whenAddressedBy.strings', '=') self.assertResponse('config network testnet2 reply.whenAddressedBy.strings', '^') def testChannelNetwork(self): irc = self.irc irc1 = getTestIrc('testnet1') irc2 = getTestIrc('testnet2') irc3 = getTestIrc('testnet3') conf.supybot.reply.whenAddressedBy.strings.get('#test')._wasSet = False # 1. Set global self.assertResponse('config reply.whenAddressedBy.strings ^', 'The operation succeeded.') # 2. Set for current net + #testchan1 self.assertResponse('config channel #testchan1 reply.whenAddressedBy.strings @', 'The operation succeeded.') # Exact match for #2: self.assertResponse('config channel #testchan1 reply.whenAddressedBy.strings', '@') # 3: Set for #testchan1 for all nets: self.assertNotError('config channel * #testchan1 reply.whenAddressedBy.strings $') # Still exact match for #2: self.assertResponse('config channel #testchan1 reply.whenAddressedBy.strings', '@') # Inherit from *: self.assertResponse('config channel testnet1 #testchan1 reply.whenAddressedBy.strings', '$') self.assertResponse('config channel testnet2 #testchan1 reply.whenAddressedBy.strings', '$') # 4: Set for testnet1 for #testchan1 and #testchan2: self.assertNotError('config channel testnet1 #testchan1,#testchan2 reply.whenAddressedBy.strings .') # 5: Set for testnet2 for #testchan1: self.assertNotError('config channel testnet2 #testchan1 reply.whenAddressedBy.strings :') # Inherit from global value (nothing was set of current net or current # chan): (old_channel, self.channel) = (self.channel, '#iejofjfozifk') try: self.assertResponse('config channel reply.whenAddressedBy.strings', '^') finally: self.channel = old_channel # Still exact match for #2: self.assertResponse('config channel #testchan1 reply.whenAddressedBy.strings', '@') self.assertResponse('config channel %s #testchan1 reply.whenAddressedBy.strings' % irc.network, '@') # Exact match for #4: self.assertResponse('config channel testnet1 #testchan1 reply.whenAddressedBy.strings', '.') self.assertResponse('config channel testnet1 #testchan2 reply.whenAddressedBy.strings', '.') # Inherit from #5, which set for #testchan1 on all nets self.assertResponse('config channel testnet3 #testchan1 reply.whenAddressedBy.strings', ':') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Ctcp/0000755000175000017500000000000013634634547015677 5ustar valval00000000000000limnoria-2020.03.17/plugins/Ctcp/__init__.py0000644000175000017500000000451013634634532020002 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Handles standard CTCP responses to PING, TIME, SOURCE, VERSION, USERINFO, and FINGER. """ import supybot import supybot.world as world __version__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Ctcp/config.py0000644000175000017500000000703013634634532017510 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Ctcp') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Ctcp', True) ### # Ctcp plugin configuration variables. ### Ctcp = conf.registerPlugin('Ctcp') conf.registerGlobalValue(Ctcp, 'versionWait', registry.PositiveInteger(10, """Determines how many seconds the bot will wait after getting a version command (not a CTCP VERSION, but an actual call of the command in this plugin named "version") before replying with the results it has collected.""")) conf.registerGlobalValue(Ctcp, 'userinfo', registry.String('', """Determines what will be sent when a USERINFO query is received.""")) ### # supybot.abuse configuration variables. ### conf.registerGlobalValue(conf.supybot.abuse.flood, 'ctcp', registry.Boolean(True, """Determines whether the bot will defend itself against CTCP flooding.""")) conf.registerGlobalValue(conf.supybot.abuse.flood.ctcp, 'maximum', registry.PositiveInteger(5, """Determines how many CTCP messages (not including actions) the bot will reply to from a given user in a minute. If a user sends more than this many CTCP messages in a 60 second period, the bot will ignore CTCP messages from this user for supybot.abuse.flood.ctcp.punishment seconds.""")) conf.registerGlobalValue(conf.supybot.abuse.flood.ctcp, 'punishment', registry.PositiveInteger(300, """Determines how many seconds the bot will ignore CTCP messages from users who flood it with CTCP messages.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Ctcp/locales/0000755000175000017500000000000013634634547017321 5ustar valval00000000000000limnoria-2020.03.17/plugins/Ctcp/locales/de.po0000644000175000017500000000324513634634532020247 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-10-29 19:22+0100\n" "Last-Translator: Florian Besser \n" "Language-Team: Germen \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" #: plugin.py:77 msgid "\001PING ?(.*)\001" msgstr "\001PING ?(.*)\001" #: plugin.py:86 msgid "\001VERSION\001" msgstr "\001VERSION\001" #: plugin.py:91 msgid "\001USERINFO\001" msgstr "\001USERINFO\001" #: plugin.py:96 msgid "\001TIME\001" msgstr "\001TIME\001" #: plugin.py:101 msgid "\001FINGER\001" msgstr "\001FINGER\001" #: plugin.py:104 msgid "Supybot, the best Python IRC bot in existence!" msgstr "Supybot ist der beste Python IRC Bot den es gibt!" #: plugin.py:107 msgid "\001SOURCE\001" msgstr "\001SOURCE\001" #: 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" "Sendet CTCP VERSION an , gibt die verschiedenen Versions Zeichenketten zurück. Es wartet 10 Sekunden bevor die Versionen die zu diesem Zeitpunkt emfpangen wurden zurückgegeben werden. Falls --nicks angegeben wird, werden die Versions Zeichenketten an die Nicks geknüpft;wenn nicht werden nur die Versions Zeichenketten zurückgegeben." limnoria-2020.03.17/plugins/Ctcp/locales/fi.po0000644000175000017500000000343713634634532020260 0ustar valval00000000000000# Ctcp plugin in Limnoria. # Copyright (C) 2011-2014 Limnoria # Mikaela Suomalainen , 2011-2014 # msgid "" msgstr "" "Project-Id-Version: \n" "POT-Creation-Date: 2014-03-22 12:41+EET\n" "PO-Revision-Date: 2014-03-22 16:16+0200\n" "Last-Translator: Mikaela 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-Generator: Poedit 1.5.4\n" #: plugin.py:81 msgid "^PING(?: (.+))?$" msgstr "^PING(?: (.+))?$" #: plugin.py:90 msgid "^VERSION$" msgstr "^VERSION$" #: plugin.py:95 msgid "^USERINFO$" msgstr "^USERINFO$" #: plugin.py:100 msgid "^TIME$" msgstr "^TIME$" #: plugin.py:105 msgid "^FINGER$" msgstr "^FINGER$" #: plugin.py:108 msgid "Supybot, the best Python IRC bot in existence!" msgstr "Supybot, paras Pythonilla toteutettu IRC-botti, joka on olemassa!" #: plugin.py:111 msgid "^SOURCE$" msgstr "^SOURCE$" #: plugin.py:127 #, fuzzy 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" " Lähettää CTCP VERSION , palauttaen \n" " palaavat versioketjut. Se odottaa 10 sekuntia ennen kuin palauttaa\n" " versiot, jotka vastaanotettiin tuolloin. Jos --nicks on annettu, " "nimimerkit\n" " liitetään versioketjuihin; muutoin vain versioketjut\n" " annetaan.\n" " " limnoria-2020.03.17/plugins/Ctcp/locales/fr.po0000644000175000017500000000325013634634532020262 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: \n" "Last-Translator: Mikaela Suomalainen \n" "Language-Team: Limnoria \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:77 msgid "\001PING ?(.*)\001" msgstr "\001PING ?(.*)\001" #: plugin.py:86 msgid "\001VERSION\001" msgstr "\001VERSION\001" #: plugin.py:91 msgid "\001USERINFO\001" msgstr "\001USERINFO\001" #: plugin.py:96 msgid "\001TIME\001" msgstr "\001TIME\001" #: plugin.py:101 msgid "\001FINGER\001" msgstr "\001FINGER\001" #: 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 "\001SOURCE\001" msgstr "\001SOURCE\001" #: 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." limnoria-2020.03.17/plugins/Ctcp/locales/hu.po0000644000175000017500000000336513634634532020276 0ustar valval00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: Limnoria Ctcp\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-07-31 12:12+CEST\n" "Last-Translator: nyuszika7h \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" #: plugin.py:77 #, docstring msgid "\001PING ?(.*)\001" msgstr "\001PING ?(.*)\001" #: plugin.py:86 #, docstring msgid "\001VERSION\001" msgstr "\001VERSON\001" #: plugin.py:91 #, docstring msgid "\001USERINFO\001" msgstr "\001USERINFO\001" #: plugin.py:96 #, docstring msgid "\001TIME\001" msgstr "\001TIME\001" #: plugin.py:101 #, docstring msgid "\001FINGER\001" msgstr "\001FINGER\001" #: plugin.py:104 msgid "Supybot, the best Python IRC bot in existence!" msgstr "Supybot, a legjobb létező Python IRC bot!" #: plugin.py:107 #, docstring msgid "\001SOURCE\001" msgstr "\001SOURCE\001" #: 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 "" "[ [--nicks]\n" "\n" "Küld egy CTCP VERSION-t -ra, kiírva a különféle kapott verziókat. 10 másodpercig vár a kapott verziók kiírása előtt. Ha --nicks meg van adva, a nevek össze vannak kapcsolva a verziókkal; egyébként csak a verziók vannak kiírva." limnoria-2020.03.17/plugins/Ctcp/locales/it.po0000644000175000017500000000317413634634532020274 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-12 13:43+0200\n" "Last-Translator: skizzhg \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\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 "Supybot, il miglior bot IRC in Python esistente!" #: 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 "" "[] [--nicks]\n" "\n" " Invia un CTCP VERSION a restituendo le varie stringhe\n" " ricevute. Attende 10 secondi prima di mostrare le versioni ottenute\n" " fino a quel momento. Se --nicks è specificato, i nick sono associati\n" " alle stringhe di versione; altrimenti vengono fornite solo le stringhe.\n" " " limnoria-2020.03.17/plugins/Ctcp/plugin.py0000644000175000017500000001540513634634532017546 0ustar valval00000000000000### # Copyright (c) 2002-2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import time import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.schedule as schedule import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Ctcp') class Ctcp(callbacks.PluginRegexp): """Provides replies to common CTCPs (version, time, etc.), and a command to fetch version responses from channels.""" public = False regexps = ('ctcpPing', 'ctcpVersion', 'ctcpUserinfo', 'ctcpTime', 'ctcpFinger', 'ctcpSource') def __init__(self, irc): self.__parent = super(Ctcp, self) self.__parent.__init__(irc) self.ignores = ircutils.IrcDict() self.floods = ircutils.FloodQueue(conf.supybot.abuse.flood.interval()) conf.supybot.abuse.flood.interval.addCallback(self.setFloodQueueTimeout) def setFloodQueueTimeout(self, *args, **kwargs): self.floods.timeout = conf.supybot.abuse.flood.interval() def callCommand(self, command, irc, msg, *args, **kwargs): if conf.supybot.abuse.flood.ctcp(): now = time.time() for (ignore, expiration) in self.ignores.items(): if expiration < now: del self.ignores[ignore] elif ircutils.hostmaskPatternEqual(ignore, msg.prefix): return self.floods.enqueue(msg) max = conf.supybot.abuse.flood.ctcp.maximum() if self.floods.len(msg) > max: expires = conf.supybot.abuse.flood.ctcp.punishment() self.log.warning('Apparent CTCP flood from %s, ' 'ignoring CTCP messages for %s seconds.', msg.prefix, expires) ignoreMask = '*!%s@%s' % (msg.user, msg.host) self.ignores[ignoreMask] = now + expires return self.__parent.callCommand(command, irc, msg, *args, **kwargs) def _reply(self, irc, msg, s): s = '\x01%s\x01' % s irc.reply(s, notice=True, private=True, to=msg.nick, stripCtcp=False) def ctcpPing(self, irc, msg, match): "^\x01PING(?: (.+))?\x01$" self.log.info('Received CTCP PING from %s', msg.prefix) payload = match.group(1) if payload: self._reply(irc, msg, 'PING %s' % match.group(1)) else: self._reply(irc, msg, 'PING') def ctcpVersion(self, irc, msg, match): "^\x01VERSION\x01$" self.log.info('Received CTCP VERSION from %s', msg.prefix) self._reply(irc, msg, 'VERSION Limnoria %s' % conf.version) def ctcpUserinfo(self, irc, msg, match): "^\x01USERINFO\x01$" self.log.info('Received CTCP USERINFO from %s', msg.prefix) self._reply(irc, msg, 'USERINFO %s' % self.registryValue('userinfo')) def ctcpTime(self, irc, msg, match): "^\x01TIME\x01$" self.log.info('Received CTCP TIME from %s', msg.prefix) self._reply(irc, msg, 'TIME %s' % time.ctime()) def ctcpFinger(self, irc, msg, match): "^\x01FINGER\x01$" self.log.info('Received CTCP FINGER from %s', msg.prefix) self._reply(irc, msg, 'FINGER ' + _('Supybot, the best Python IRC bot in existence!')) def ctcpSource(self, irc, msg, match): "^\x01SOURCE\x01$" self.log.info('Received CTCP SOURCE from %s', msg.prefix) self._reply(irc, msg, 'SOURCE https://github.com/ProgVal/Limnoria') def doNotice(self, irc, msg): if ircmsgs.isCtcp(msg): try: (version, payload) = msg.args[1][1:-1].split(None, 1) except ValueError: return if version == 'VERSION': self.versions.setdefault(payload, []).append(msg.nick) @internationalizeDocstring def version(self, irc, msg, args, channel, optlist): """[] [--nicks] Sends a CTCP VERSION to , returning the various version strings returned. It waits for 10 seconds before returning the versions received at that point. If --nicks is given, nicks are associated with the version strings; otherwise, only the version strings are given. """ self.versions = ircutils.IrcDict() nicks = False for (option, arg) in optlist: if option == 'nicks': nicks = True irc.queueMsg(ircmsgs.privmsg(channel, '\x01VERSION\x01')) def doReply(): if self.versions: L = [] for (reply, nickslist) in self.versions.items(): if nicks: L.append(format('%L responded with %q', nickslist, reply)) else: L.append(reply) irc.reply(format('%L', L)) else: irc.reply('I received no version responses.') wait = self.registryValue('versionWait') schedule.addEvent(doReply, time.time()+wait) version = wrap(version, ['channel', getopts({'nicks':''})]) Class = Ctcp # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Ctcp/test.py0000644000175000017500000000331513634634532017224 0ustar valval00000000000000### # Copyright (c) 2002-2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * class CtcpTestCase(PluginTestCase): plugins = ('Ctcp',) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Debug/0000755000175000017500000000000013634634547016034 5ustar valval00000000000000limnoria-2020.03.17/plugins/Debug/__init__.py0000644000175000017500000000435013634634532020141 0ustar valval00000000000000### # Copyright (c) 2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ This is for developers debugging their plugins; it provides an eval command as well as some other useful commands. """ import supybot import supybot.world as world from sys import version_info __version__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload reload(plugin) # In case we're being reloaded. if world.testing: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Debug/config.py0000644000175000017500000000446313634634532017654 0ustar valval00000000000000### # Copyright (c) 2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### 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 themself 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('Debug', True) Debug = conf.registerPlugin('Debug') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Debug, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Debug/plugin.py0000644000175000017500000001607413634634532017706 0ustar valval00000000000000#!/usr/bin/python ### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ This is for debugging purposes only and you shouldn't load this it unless a Supybot developer requests you to debug some issue. """ # Import supybot for easier access to the module namespace import supybot import supybot.plugins as plugins import gc import os import sys try: import exceptions except ImportError: # Python 3 import builtins class exceptions: """Pseudo-module""" pass for (key, value) in list(exceptions.__dict__.items()): if isinstance(value, type) and issubclass(value, Exception): exceptions[key] = value import supybot.conf as conf import supybot.utils as utils import supybot.ircdb as ircdb from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.callbacks as callbacks def getTracer(fd): def tracer(frame, event, _): if event == 'call': code = frame.f_code fd.write('%s: %s\n' % (code.co_filename, code.co_name)) return tracer def checkAllowShell(irc): if not conf.supybot.commands.allowShell(): irc.error('This command is not available, because ' 'supybot.commands.allowShell is False.', Raise=True) class Debug(callbacks.Privmsg): """This plugin provides debugging abilities for Supybot. It should not be loaded with a default installation.""" capability = 'owner' def __init__(self, irc): # Setup exec command. self.__parent = super(Debug, self) self.__parent.__init__(irc) setattr(self.__class__, 'exec', self.__class__._exec) def callCommand(self, name, irc, msg, *args, **kwargs): if ircdb.checkCapability(msg.prefix, self.capability): self.__parent.callCommand(name, irc, msg, *args, **kwargs) else: irc.errorNoCapability(self.capability) _evalEnv = {'_': None, '__': None, '___': None, } _evalEnv.update(globals()) def eval(self, irc, msg, args, s): """ Evaluates (which should be a Python expression) and returns its value. If an exception is raised, reports the exception (and logs the traceback to the bot's logfile). """ checkAllowShell(irc) try: self._evalEnv.update(locals()) x = eval(s, self._evalEnv, self._evalEnv) self._evalEnv['___'] = self._evalEnv['__'] self._evalEnv['__'] = self._evalEnv['_'] self._evalEnv['_'] = x irc.reply(repr(x)) except SyntaxError as e: irc.reply(format('%s: %q', utils.exnToString(e), s)) eval = wrap(eval, ['text']) def _exec(self, irc, msg, args, s): """ Execs . Returns success if it didn't raise any exceptions. """ checkAllowShell(irc) exec(s) irc.replySuccess() _exec = wrap(_exec, ['text']) def simpleeval(self, irc, msg, args, text): """ Evaluates the given expression. """ checkAllowShell(irc) try: irc.reply(repr(eval(text))) except Exception as e: irc.reply(utils.exnToString(e)) simpleeval = wrap(simpleeval, ['text']) def exn(self, irc, msg, args, name): """ Raises the exception matching . """ checkAllowShell(irc) # Just to be safe, but probably not needed. if isinstance(__builtins__, dict): exn = __builtins__[name] else: exn = getattr(__builtins__, name) raise exn(msg.prefix) exn = wrap(exn, ['text']) def sendquote(self, irc, msg, args, text): """ Sends (not queues) the raw IRC message given. """ msg = ircmsgs.IrcMsg(text) irc.sendMsg(msg) sendquote = wrap(sendquote, ['text']) def settrace(self, irc, msg, args, filename): """[] Starts tracing function calls to . If is not given, sys.stdout is used. This causes much output. """ checkAllowShell(irc) if filename: fd = open(filename, 'a') else: fd = sys.stdout sys.settrace(getTracer(fd)) irc.replySuccess() settrace = wrap(settrace, [additional('filename')]) def unsettrace(self, irc, msg, args): """takes no arguments Stops tracing function calls on stdout. """ checkAllowShell(irc) sys.settrace(None) irc.replySuccess() unsettrace = wrap(unsettrace) def channeldb(self, irc, msg, args, channel): """[] Returns the result of the channeldb converter. """ irc.reply(channel) channeldb = wrap(channeldb, ['channeldb']) def collect(self, irc, msg, args, times): """[] Does gc collections, returning the number of objects collected each time. defaults to 1. """ L = [] while times: L.append(gc.collect()) times -= 1 irc.reply(format('%L', list(map(str, L)))) collect = wrap(collect, [additional('positiveInt', 1)]) def environ(self, irc, msg, args): """takes no arguments Returns the environment of the supybot process. """ checkAllowShell(irc) # possibly some secret data in the env irc.reply(repr(os.environ)) environ = wrap(environ) Class = Debug # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Debug/test.py0000644000175000017500000000505413634634532017363 0ustar valval00000000000000### # Copyright (c) 2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * class DebugTestCase(PluginTestCase): plugins = ('Debug',) def testShellForbidden(self): self.assertResponse('debug eval 1+2', '3') self.assertResponse('debug simpleeval 1+2', '3') self.assertResponse('debug exec irc.reply(1+2)', '3') while self.irc.takeMsg(): pass self.assertNotError('debug environ') with conf.supybot.commands.allowShell.context(False): self.assertRegexp('debug eval 1+2', 'Error:.*not available.*supybot.commands.allowShell') self.assertRegexp('debug simpleeval 1+2', 'Error:.*not available.*supybot.commands.allowShell') self.assertRegexp('debug exec irc.reply(1+2)', 'Error:.*not available.*supybot.commands.allowShell') self.assertRegexp('debug environ', 'Error:.*not available.*supybot.commands.allowShell') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Dict/0000755000175000017500000000000013634634547015671 5ustar valval00000000000000limnoria-2020.03.17/plugins/Dict/__init__.py0000644000175000017500000000452313634634532020000 0ustar valval00000000000000### # Copyright (c) 2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Commands that use the dictd protocol to define word. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload from .local import dictclient reload(plugin) # In case we're being reloaded. reload(dictclient) if world.testing: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Dict/config.py0000644000175000017500000000542113634634532017504 0ustar valval00000000000000### # Copyright (c) 2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Dict') def configure(advanced): from supybot.questions import output, expect, anything, something, yn conf.registerPlugin('Dict', True) output(_('The default dictd server is dict.org.')) if yn(_('Would you like to specify a different dictd server?')): server = something('What server?') conf.supybot.plugins.Dict.server.set(server) Dict = conf.registerPlugin('Dict') conf.registerGlobalValue(Dict, 'server', registry.String('dict.org', _("""Determines what server the bot will retrieve definitions from."""))) conf.registerChannelValue(Dict, 'default', registry.String('*', _("""Determines the default dictionary the bot will ask for definitions in. If this value is '*' (without the quotes) the bot will use all dictionaries to define words."""))) conf.registerChannelValue(Dict, 'showDictName', registry.Boolean(True, _("""Determines whether the bot will show which dictionaries responded to a query, if the selected dictionary is '*'. """))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Dict/local/0000755000175000017500000000000013634634547016763 5ustar valval00000000000000limnoria-2020.03.17/plugins/Dict/local/__init__.py0000644000175000017500000000007213634634532021065 0ustar valval00000000000000# Stub so local is a module, used for third-party modules limnoria-2020.03.17/plugins/Dict/local/dictclient.py0000644000175000017500000003002413634634532021450 0ustar valval00000000000000# Client for the DICT protocol (RFC2229) # # Copyright (C) 2002 John Goerzen # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import socket, re version = '1.0' def dequote(s): """Will remove single or double quotes from the start and end of a string and return the result.""" return s.strip("'\"") def enquote(str): """This function will put a string in double quotes, properly escaping any existing double quotes with a backslash. It will return the result.""" return '"' + str.replace('"', "\\\"") + '"' class Connection: """This class is used to establish a connection to a database server. You will usually use this as the first call into the dictclient library. Instantiating it takes two optional arguments: a hostname (a string) and a port (an int). The hostname defaults to localhost and the port to 2628, the port specified in RFC.""" def __init__(self, hostname = 'localhost', port = 2628): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect((hostname, port)) self.rfile = self.sock.makefile("rb") self.wfile = self.sock.makefile("wb", 0) self.saveconnectioninfo() def getresultcode(self): """Generic function to get a result code. It will return a list consisting of two items: the integer result code and the text following. You will not usually use this function directly.""" line = self.rfile.readline().decode('utf8').strip() if line.startswith('['): return [None, line] code, text = line.split(' ', 1) return [int(code), text] def get200result(self): """Used when expecting a single line of text -- a 200-class result. Returns [intcode, remaindertext]""" code, text = self.getresultcode() if code < 200 or code >= 300: raise Exception("Got '%s' when 200-class response expected" % \ line) return [code, text] def get100block(self): """Used when expecting multiple lines of text -- gets the block part only. Does not get any codes or anything! Returns a string.""" data = [] while True: line = self.rfile.readline().decode('utf8').strip() if line == '.': break data.append(line) return "\n".join(data) def get100result(self): """Used when expecting multiple lines of text, terminated by a period and a 200 code. Returns: [initialcode, [bodytext_1lineperentry], finalcode]""" code, text = self.getresultcode() if code < 100 or code >= 200: raise Exception("Got '%s' when 100-class response expected" % \ code) bodylines = self.get100block().split("\n") code2 = self.get200result()[0] return [code, bodylines, code2] def get100dict(self): """Used when expecting a dictionary of results. Will read from the initial 100 code, to a period and the 200 code.""" dict = {} for line in self.get100result()[1]: key, val = line.split(' ', 1) dict[key] = dequote(val) return dict def saveconnectioninfo(self): """Called by __init__ to handle the initial connection. Will save off the capabilities and messageid.""" code, string = self.get200result() assert code == 220 capstr, msgid = re.search('<(.*)> (<.*>)$', string).groups() self.capabilities = capstr.split('.') self.messageid = msgid def getcapabilities(self): """Returns a list of the capabilities advertised by the server.""" return self.capabilities def getmessageid(self): """Returns the message id, including angle brackets.""" return self.messageid def getdbdescs(self): """Gets a dict of available databases. The key is the db name and the value is the db description. This command may generate network traffic!""" if hasattr(self, 'dbdescs'): return self.dbdescs self.sendcommand("SHOW DB") self.dbdescs = self.get100dict() return self.dbdescs def getstratdescs(self): """Gets a dict of available strategies. The key is the strat name and the value is the strat description. This call may generate network traffic!""" if hasattr(self, 'stratdescs'): return self.stratdescs self.sendcommand("SHOW STRAT") self.stratdescs = self.get100dict() return self.stratdescs def getdbobj(self, dbname): """Gets a Database object corresponding to the database name passed in. This function explicitly will *not* generate network traffic. If you have not yet run getdbdescs(), it will fail.""" if not hasattr(self, 'dbobjs'): self.dbobjs = {} if dbname in self.dbobjs: return self.dbobjs[dbname] # We use self.dbdescs explicitly since we don't want to # generate net traffic with this request! if dbname != '*' and dbname != '!' and \ not dbname in self.dbdescs.keys(): raise Exception("Invalid database name '%s'" % dbname) self.dbobjs[dbname] = Database(self, dbname) return self.dbobjs[dbname] def sendcommand(self, command): """Takes a command, without a newline character, and sends it to the server.""" self.wfile.write(command.encode('utf-8') + b"\n") def define(self, database, word): """Returns a list of Definition objects for each matching definition. Parameters are the database name and the word to look up. This is one of the main functions you will use to interact with the server. Returns a list of Definition objects. If there are no matches, an empty list is returned. Note: database may be '*' which means to search all databases, or '!' which means to return matches from the first database that has a match.""" self.getdbdescs() # Prime the cache if database != '*' and database != '!' and \ not database in self.getdbdescs(): raise Exception("Invalid database '%s' specified" % database) self.sendcommand("DEFINE " + enquote(database) + " " + enquote(word)) code = self.getresultcode()[0] retval = [] if code == 552: # No definitions. return [] if code != 150: raise Exception("Unknown code %d" % code) while True: code, text = self.getresultcode() if code != 151 or code is None: break resultword, resultdb = re.search('^"(.+)" (\S+)', text).groups() defstr = self.get100block() retval.append(Definition(self, self.getdbobj(resultdb), resultword, defstr)) return retval def match(self, database, strategy, word): """Gets matches for a query. Arguments are database name, the strategy (see available ones in getstratdescs()), and the pattern/word to look for. Returns a list of Definition objects. If there is no match, an empty list is returned. Note: database may be '*' which means to search all databases, or '!' which means to return matches from the first database that has a match.""" self.getstratdescs() # Prime the cache self.getdbdescs() # Prime the cache if not strategy in self.getstratdescs().keys(): raise Exception("Invalid strategy '%s'" % strategy) if database != '*' and database != '!' and \ not database in self.getdbdescs().keys(): raise Exception("Invalid database name '%s'" % database) self.sendcommand("MATCH %s %s %s" % (enquote(database), enquote(strategy), enquote(word))) code = self.getresultcode()[0] if code == 552: # No Matches return [] if code != 152: raise Exception("Unexpected code %d" % code) retval = [] for matchline in self.get100block().split("\n"): matchdict, matchword = matchline.split(" ", 1) retval.append(Definition(self, self.getdbobj(matchdict), dequote(matchword))) if self.getresultcode()[0] != 250: raise Exception("Unexpected end-of-list code %d" % code) return retval class Database: """An object corresponding to a particular database in a server.""" def __init__(self, dictconn, dbname): """Initialize the object -- requires a Connection object and a database name.""" self.conn = dictconn self.name = dbname def getname(self): """Returns the short name for this database.""" return self.name def getdescription(self): if hasattr(self, 'description'): return self.description if self.getname() == '*': self.description = 'All Databases' elif self.getname() == '!': self.description = 'First matching database' else: self.description = self.conn.getdbdescs()[self.getname()] return self.description def getinfo(self): """Returns a string of info describing this database.""" if hasattr(self, 'info'): return self.info if self.getname() == '*': self.info = "This special database will search all databases on the system." elif self.getname() == '!': self.info = "This special database will return matches from the first matching database." else: self.conn.sendcommand("SHOW INFO " + self.name) self.info = "\n".join(self.conn.get100result()[1]) return self.info def define(self, word): """Get a definition from within this database. The argument, word, is the word to look up. The return value is the same as from Connection.define().""" return self.conn.define(self.getname(), word) def match(self, strategy, word): """Get a match from within this database. The argument, word, is the word to look up. The return value is the same as from Connection.define().""" return self.conn.match(self.getname(), strategy, word) class Definition: """An object corresponding to a single definition.""" def __init__(self, dictconn, db, word, defstr = None): """Instantiate the object. Requires: a Connection object, a Database object (NOT corresponding to '*' or '!' databases), a word. Optional: a definition string. If not supplied, it will be fetched if/when it is requested.""" self.conn = dictconn self.db = db self.word = word self.defstr = defstr def getdb(self): """Get the Database object corresponding to this definition.""" return self.db def getdefstr(self): """Get the definition string (the actual content) of this definition.""" if not self.defstr: self.defstr = self.conn.define(self.getdb().getname(), self.word)[0].getdefstr() return self.defstr def getword(self): """Get the word this object describes.""" return self.word limnoria-2020.03.17/plugins/Dict/locales/0000755000175000017500000000000013634634547017313 5ustar valval00000000000000limnoria-2020.03.17/plugins/Dict/locales/fi.po0000644000175000017500000000675213634634532020255 0ustar valval00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen , 2011. # msgid "" msgstr "" "Project-Id-Version: Dict plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 11:59+EET\n" "PO-Revision-Date: 2014-12-20 12:23+0200\n" "Last-Translator: Mikaela Suomalainen \n" "Language-Team: \n" "Language: fi\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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.10\n" #: config.py:38 msgid "The default dictd server is dict.org." msgstr "Oletus dictd-palvelin on dict.org." #: config.py:39 msgid "Would you like to specify a different dictd server?" msgstr "Tahtoisitko määrittää eri dictd-palvelimen?" #: config.py:45 msgid "" "Determines what server the bot will\n" " retrieve definitions from." msgstr "" "Määrittää miltä palvelimelta botti\n" "hakee määritykset." #: config.py:48 #, fuzzy msgid "" "Determines the default dictionary the bot\n" " will ask for definitions in. If this value is '*' (without the quotes)\n" " the bot will use all dictionaries to define words." msgstr "" "Määrittää oletus sanakirjan, josta botti \n" "pyytää määrityksiä. Jos arvo on '*' (ilman lainausmerkkejä) botti\n" "käyttää kaikkia sanakirjoja määrittääkseen sanat." #: plugin.py:46 msgid "" "This plugin provides a function to look up words from different\n" " dictionaries." msgstr "Tämä plugin tarjoaa toiminnon sanojen etsimiseen eri sanakirjoista." #: plugin.py:51 msgid "" "takes no arguments\n" "\n" " Returns the dictionaries valid for the dict command.\n" " " msgstr "" "Ei ota parametrejä\n" "\n" " Palauttaa sanakirjat, jotka kelpaavat dict komennolle.\n" " " #: plugin.py:67 msgid "" "takes no arguments\n" "\n" " Returns a random valid dictionary.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Palauttaa satunnaisen kelvollisen sanakirjan.\n" " " #: plugin.py:82 msgid "" "[] \n" "\n" " Looks up the definition of on the dictd server specified by\n" " the supybot.plugins.Dict.server config variable.\n" " " msgstr "" "[] \n" "\n" " Etsii määritystä dictd-palvelimelta, joka on määritetty\n" " supybot.plugins.Dict.server asetus arvossa.\n" " " #: plugin.py:105 msgid "You must give a word to define." msgstr "Sinun täytyy antaa sana määritettäväksi." #: plugin.py:111 msgid "No definition for %q could be found." msgstr "Määritystä %q:lle ei löydetty." #: plugin.py:114 msgid "No definition for %q could be found in %s" msgstr "Määritystä %q:lle ei löydetty %s:stä." #: plugin.py:126 msgid "%L responded: %s" msgstr "%L vastasi: %s" #: plugin.py:133 #, fuzzy msgid "" " [ ...]\n" "\n" " Gets a random synonym from the Moby Thesaurus (moby-thesaurus) " "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" " Hakee satunnaisen synonyymin Moby Thesaurus (moby-thes) " "tietokannasta.\n" " \n" " Jos monia sanoja on annettu, hakee satunnaisen synonyymin niille " "kaikille.\n" " \n" " Laita lausekkeita lainausmerkkeihin saadaksesi ne kohdelluiksi " "yhtenä hakusanalla.\n" " " limnoria-2020.03.17/plugins/Dict/locales/fr.po0000644000175000017500000000602213634634532020254 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-06-28 19:40+CEST\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Limnoria \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" #: config.py:38 msgid "The default dictd server is dict.org." msgstr "Le serveur dictd par défaut est dict.org" #: config.py:39 msgid "Would you like to specify a different dictd server?" msgstr "Voulez-vous spécifier un serveur dictd différent ?" #: config.py:45 msgid "" "Determines what server the bot will\n" " retrieve definitions from." msgstr "Détermine de quel serveur le bot récupérera les définitions." #: config.py:48 msgid "" "Determines the default dictionary the bot will\n" " ask for definitions in. If this value is '*' (without the quotes) the bot\n" " 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:54 msgid "" "takes no arguments\n" "\n" " Returns the dictionaries valid for the dict command.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne les dictionnaires valides pour la commande dict." #: plugin.py:70 msgid "" "takes no arguments\n" "\n" " Returns a random valid dictionary.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne un dictionnaire valide aléatoire." #: plugin.py:85 msgid "" "[] \n" "\n" " Looks up the definition of on the dictd server specified by\n" " the supybot.plugins.Dict.server config variable.\n" " " msgstr "" "[] \n" "\n" "Recherche la définition du mot sur le serveur dictd spécifié par la variable de configuration supybot.plugins.Dict.server." #: plugin.py:108 msgid "You must give a word to define." msgstr "Vous devez donner un mot à définir." #: plugin.py:114 msgid "No definition for %q could be found." msgstr "La définition de %q ne peut être trouvée." #: 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:129 msgid "%L responded: %s" msgstr "%L a répondu : %s" #: plugin.py:136 msgid "" " [ ...]\n" "\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 au hasard de la base de données du Moby Thesaurus (moby-thes). Si plusieurs mots sont donnés, récupère un synonyme au hasard pour chacun d'eux. Citez les phrases pour qu'elles soient traitées comme un unique mot." limnoria-2020.03.17/plugins/Dict/locales/it.po0000644000175000017500000000602013634634532020257 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-28 20:21+0200\n" "Last-Translator: skizzhg \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:38 msgid "The default dictd server is dict.org." msgstr "Il server dictd predefinito è dict.org." #: config.py:39 msgid "Would you like to specify a different dictd server?" msgstr "Vuoi specificare un server dictd diverso?" #: config.py:45 msgid "" "Determines what server the bot will\n" " retrieve definitions from." msgstr "Determina da quale server il bot recupererà le definizioni." #: config.py:48 msgid "" "Determines the default dictionary the bot will\n" " ask for definitions in. If this value is '*' (without the quotes) the bot\n" " will use all dictionaries to define words." msgstr "" "Determina il dizionario predefinito dal quale il bot chiederà le definizioni.\n" " Se il valore è \"*\" (senza virgolette) il bot userà tutti i dizionari." #: plugin.py:54 #, docstring msgid "" "takes no arguments\n" "\n" " Returns the dictionaries valid for the dict command.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Riporta i dizionari validi per il comando dict.\n" " " #: plugin.py:70 #, docstring msgid "" "takes no arguments\n" "\n" " Returns a random valid dictionary.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Riporta un dizionario casuale.\n" " " #: plugin.py:85 #, docstring msgid "" "[] \n" "\n" " Looks up the definition of on the dictd server specified by\n" " the supybot.plugins.Dict.server config variable.\n" " " msgstr "" "[] \n" "\n" " Cerca la definizione di sul server dictd specificato dalla\n" " variabile supybot.plugins.Dict.server.\n" " " #: plugin.py:108 msgid "You must give a word to define." msgstr "Devi fornire una parola da definire." #: plugin.py:114 msgid "No definition for %q could be found." msgstr "Non è stata trovata nessuna definizione per %q." #: plugin.py:117 msgid "No definition for %q could be found in %s" msgstr "Non è stata trovata nessuna definizione per %q in %s" #: plugin.py:129 msgid "%L responded: %s" msgstr "%L ha risposto: %s" #: plugin.py:136 #, docstring msgid "" " [ ...]\n" "\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" " Ricava un sinonimo casuale dal database di Moby Thesaurus (moby-thes).\n" "\n" " Se sono state fornite più parole ottiene un sinonimo casuale per ognuna.\n" "\n" " Affinché vengano trattate come un'unica frase racchiuderle tra virgolette.\n" " " limnoria-2020.03.17/plugins/Dict/plugin.py0000644000175000017500000001441613634634532017541 0ustar valval00000000000000### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2008, James McCoy # 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 socket import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Dict') import random from .local import dictclient class Dict(callbacks.Plugin): """This plugin provides a function to look up words from different dictionaries.""" threaded = True @internationalizeDocstring def dictionaries(self, irc, msg, args): """takes no arguments Returns the dictionaries valid for the dict command. """ try: server = conf.supybot.plugins.Dict.server() conn = dictclient.Connection(server) dbs = list(conn.getdbdescs().keys()) dbs.sort() irc.reply(format('%L', dbs)) except socket.error as e: irc.error(utils.web.strError(e)) dictionaries = wrap(dictionaries) @internationalizeDocstring def random(self, irc, msg, args): """takes no arguments Returns a random valid dictionary. """ try: server = conf.supybot.plugins.Dict.server() conn = dictclient.Connection(server) dbs = conn.getdbdescs().keys() irc.reply(utils.iter.choice(dbs)) except socket.error as e: irc.error(utils.web.strError(e)) random = wrap(random) @internationalizeDocstring def dict(self, irc, msg, args, words): """[] Looks up the definition of on the dictd server specified by the supybot.plugins.Dict.server config variable. """ try: server = conf.supybot.plugins.Dict.server() conn = dictclient.Connection(server) except socket.error as e: irc.error(utils.web.strError(e), Raise=True) dbs = set(conn.getdbdescs()) if words[0] in dbs: dictionary = words.pop(0) else: default = self.registryValue('default', msg.channel, irc.network) if default in dbs: dictionary = default else: if default: self.log.info('Default dict for %s @ %s is not a supported ' 'dictionary: %s.', msg.channel, irc.network, default) dictionary = '*' if not words: irc.error(_('You must give a word to define.'), Raise=True) word = ' '.join(words) definitions = conn.define(dictionary, word) dbs = set() if not definitions: if dictionary == '*': irc.reply(format(_('No definition for %q could be found.'), word)) else: irc.reply(format(_('No definition for %q could be found in ' '%s'), word, ircutils.bold(dictionary))) return L = [] for d in definitions: dbs.add(ircutils.bold(d.getdb().getname())) (db, s) = (d.getdb().getname(), d.getdefstr()) db = ircutils.bold(db) s = utils.str.normalizeWhitespace(s).rstrip(';.,') L.append('%s: %s' % (db, s)) utils.sortBy(len, L) if dictionary == '*' and len(dbs) > 1 and \ self.registryValue("showDictName", msg.channel, irc.network): s = format(_('%L responded: %s'), list(dbs), '; '.join(L)) else: s = '; '.join(L) irc.reply(s) dict = wrap(dict, [many('something')]) def synonym(self, irc, msg, args, words): """ [ ...] Gets a random synonym from the Moby Thesaurus (moby-thesaurus) 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 as e: irc.error(utils.web.strError(e), Raise=True) dictionary = 'moby-thesaurus' 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 # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Dict/test.py0000644000175000017500000000471013634634532017216 0ustar valval00000000000000# -*- coding: utf8 -*- ### # Copyright (c) 2002-2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * class DictTestCase(PluginTestCase): plugins = ('Dict',) if network: def testDict(self): self.assertNotError('dict slash') self.assertNotError('dict flutter') self.assertNotRegexp('dict web1913 slash', 'foldoc') self.assertError('dict ""') self.assertRegexp('dict fd-eng-fra school', 'école') def testDictionaries(self): self.assertNotError('dictionaries') 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: limnoria-2020.03.17/plugins/Dunno/0000755000175000017500000000000013634634547016071 5ustar valval00000000000000limnoria-2020.03.17/plugins/Dunno/__init__.py0000644000175000017500000000523013634634532020174 0ustar valval00000000000000### # Copyright (c) 2003-2005, Daniel DiPaolo # 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. ### """ The Dunno module is used to spice up the reply when given an invalid command with random 'I dunno'-like responses. If you want something spicier than ' is not a valid command'-like responses, use this plugin. """ 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__ = "0.1" __author__ = supybot.authors.strike __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = { supybot.authors.jemfinch: ['Flatfile DB implementation.'], } from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Dunno/config.py0000644000175000017500000000464313634634532017711 0ustar valval00000000000000### # Copyright (c) 2003-2005, Daniel DiPaolo # 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 from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Dunno') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Dunno', True) Dunno = conf.registerPlugin('Dunno') conf.registerChannelValue(Dunno, 'prefixNick', registry.Boolean(True, _("""Determines whether the bot will prefix the nick of the user giving an invalid command to the "dunno" response."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Dunno/locales/0000755000175000017500000000000013634634547017513 5ustar valval00000000000000limnoria-2020.03.17/plugins/Dunno/locales/de.po0000644000175000017500000000242713634634532020442 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-11-04 18:45+0100\n" "Last-Translator: Florian Besser \n" "Language-Team: German \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" "Language: de\n" #: config.py:46 msgid "" "Determines whether the bot will prefix the nick\n" " of the user giving an invalid command to the \"dunno\" response." msgstr "Legt fest ob der Bot " #: plugin.py:37 msgid "" "This plugin was written initially to work with MoobotFactoids, the two\n" " of them to provide a similar-to-moobot-and-blootbot interface for factoids.\n" " Basically, it replaces the standard 'Error: is not a valid command.'\n" " messages with messages kept in a database, able to give more personable\n" " responses." msgstr "Dieses plugin wurde ursprünglich geschrieben um mit MoobotFactoids betrieben zu werden, die zwei zusammen um ein gleiches MooBot und Blootbot Interface für Factoids zu bieten. Es ersetzt die Standard 'Fehler: ist kein zulässiger Befehl.' Nachricht mit einer Nachricht die in der Datenbank gespeichert ist, um die Antworten mehr zu personalisieren." limnoria-2020.03.17/plugins/Dunno/locales/fi.po0000644000175000017500000000275013634634532020447 0ustar valval00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: \n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-08 16:50+0200\n" "Last-Translator: Mikaela 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" #: config.py:46 msgid "" "Determines whether the bot will prefix the nick\n" " of the user giving an invalid command to the \"dunno\" response." msgstr "" "Määrittää lisääkö botti virheellisen komennon antaneen käyttäjän\n" " nimimerkin etuliitteeksi \"dunno\" vastaukselle." #: plugin.py:37 msgid "" "This plugin was written initially to work with MoobotFactoids, the two\n" " of them to provide a similar-to-moobot-and-blootbot interface for factoids.\n" " Basically, it replaces the standard 'Error: is not a valid command.'\n" " messages with messages kept in a database, able to give more personable\n" " responses." msgstr "" "Tämä lisäosa kirjoitettiin toimimaan MoobotFactoidsin kanssa, molemmat\n" " niistä tarjoavat samankaltainen-moobottiin-ja-blootbottiin käyttöliittymä factoideille.\n" " Yksinkertaisesti, it se korvaa perus 'Virhe: ei ole kelvollinen komento'\n" " viestit viesteillä, joita pidetään tietokannassa pystyäkseen antamaan persoonallisempia\n" " vastauksia." limnoria-2020.03.17/plugins/Dunno/locales/fr.po0000644000175000017500000000263413634634532020461 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2010-10-17 10:48+CEST\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Limnoria \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" #: config.py:46 msgid "" "Determines whether the bot will prefix the nick\n" " of the user giving an invalid command to the \"dunno\" response." msgstr "Détermine si le bot utilisera comme préfixe le nick de l'utilisateur donnant la commande invalide dans la réponse \"dunno\"." #: plugin.py:38 msgid "" "This plugin was written initially to work with MoobotFactoids, the two\n" " of them to provide a similar-to-moobot-and-blootbot interface for factoids.\n" " Basically, it replaces the standard 'Error: is not a valid command.'\n" " messages with messages kept in a database, able to give more personable\n" " responses." msgstr "Ce plugin était à l'origine écrit pour fonctionner avec MoobotFactoids, pour fournir une interface similaire aux factoids de moobot et blootbot. Remplace simplement les message 'Error: is not a valid commande' par des messages configurés dans la base de données, ce qui vous permet de rendre les réponses plus personnalisables." limnoria-2020.03.17/plugins/Dunno/locales/it.po0000644000175000017500000000250513634634532020463 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-12 13:58+0200\n" "Last-Translator: skizzhg \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:46 msgid "" "Determines whether the bot will prefix the nick\n" " of the user giving an invalid command to the \"dunno\" response." msgstr "" "Determina se il bot userà il nick dell'utente che dà un comando non\n" " valido come prefisso per la risposta \"dunno\"." #: plugin.py:37 #, docstring msgid "" "This plugin was written initially to work with MoobotFactoids, the two\n" " of them to provide a similar-to-moobot-and-blootbot interface for factoids.\n" " Basically, it replaces the standard 'Error: is not a valid command.'\n" " messages with messages kept in a database, able to give more personable\n" " responses." msgstr "" "Questo plugin fu inizialmente scritto per funzionare con MoobotFactoids, per\n" " fornire un'interfaccia simile ai factoid di moobot e blootbot.\n" " Sostituisce i messaggi standard \"Errore: non è un comando valido.\"\n" " con quelli contenuti in un database, in modo da rendere le risposte più piacevoli." limnoria-2020.03.17/plugins/Dunno/plugin.py0000644000175000017500000000564213634634532017742 0ustar valval00000000000000### # Copyright (c) 2003-2005, Daniel DiPaolo # 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.commands import * import supybot.plugins as plugins import supybot.ircutils as ircutils from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Dunno') class Dunno(plugins.ChannelIdDatabasePlugin): """This plugin was written initially to work with MoobotFactoids, the two of them to provide a similar-to-moobot-and-blootbot interface for factoids. Basically, it replaces the standard 'Error: is not a valid command.' messages with messages kept in a database, able to give more personable responses.""" callAfter = ['MoobotFactoids', 'Factoids', 'Infobot'] def invalidCommand(self, irc, msg, tokens): if msg.channel: dunno = self.db.random(msg.channel) if dunno is not None: dunno = dunno.text prefixNick = self.registryValue('prefixNick', msg.channel, irc.network) env = {'command': tokens[0]} self.log.info('Issuing "dunno" answer, %s is not a command.', tokens[0]) dunno = ircutils.standardSubstitute(irc, msg, dunno, env=env) irc.reply(dunno, prefixNick=prefixNick) Dunno = internationalizeDocstring(Dunno) Class = Dunno # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Dunno/test.py0000644000175000017500000000651213634634532017420 0ustar valval00000000000000### # Copyright (c) 2003-2005, Daniel DiPaolo # 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 DunnoTestCase(ChannelPluginTestCase): plugins = ('Dunno', 'User') def setUp(self): PluginTestCase.setUp(self) self.prefix = 'foo!bar@baz' self.assertNotError('register tester moo', private=True) def testDunnoAdd(self): self.assertNotError('dunno add moo') self.assertResponse('asdfagagfosdfk', 'moo') def testDunnoRemove(self): self.assertNotError('dunno add moo') self.assertNotError('dunno remove 1') def testDunnoSearch(self): self.assertNotError('dunno add foo') self.assertRegexp('dunno search moo', 'No.*dunnos.*found') # Test searching using just the getopts self.assertRegexp('dunno search --regexp m/foo/', r'1 found') self.assertNotError('dunno add moo') self.assertRegexp('dunno search moo', r'1 found') self.assertRegexp('dunno search m', r'1 found') # Test multiple adds for i in range(5): self.assertNotError('dunno add moo%s' % i) self.assertRegexp('dunno search moo', r'6 found') def testDunnoGet(self): self.assertNotError('dunno add moo') self.assertRegexp('dunno get 1', r'#1.*moo') self.assertNotError('dunno add $who') self.assertRegexp('dunno get 2', r'#2.*\$who') self.assertError('dunno get 3') self.assertError('dunno get a') def testDunnoChange(self): self.assertNotError('dunno add moo') self.assertNotError('dunno change 1 s/moo/bar/') self.assertRegexp('dunno get 1', '.*?: [\'"]bar[\'"]') def testDollarCommand(self): self.assertNotError("dunno add I can't $command.") self.assertResponse('asdf', "I can't asdf.") # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Factoids/0000755000175000017500000000000013634634547016542 5ustar valval00000000000000limnoria-2020.03.17/plugins/Factoids/__init__.py0000644000175000017500000000520613634634532020650 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Handles 'factoids,' little tidbits of information held in a database and available on demand via several commands. """ 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__ = "0.1" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # 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/Factoids/download' from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Factoids/config.py0000644000175000017500000001132713634634532020357 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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 from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Factoids') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Factoids', True) class FactoidFormat(registry.TemplatedString): """Value must include $value, otherwise the factoid's value would be left out.""" requiredTemplates = ['value'] Factoids = conf.registerPlugin('Factoids') conf.registerGroup(Factoids, 'web') conf.registerGlobalValue(Factoids.web, 'enable', registry.Boolean(False, _("""Determines whether the Factoids plugins will be browsable on the HTTP server."""))) conf.registerChannelValue(Factoids.web, 'channel', registry.Boolean(False, _("""Determines whether factoids can be displayed via the web server."""))) conf.registerChannelValue(Factoids, 'requireVoice', registry.Boolean(False, _("""Only allows a user with voice or above on a channel to use the 'learn' and 'forget' commands."""))) conf.registerChannelValue(Factoids, 'learnSeparator', registry.String('is', _("""Determines what separator must be used in the learn command. Defaults to 'is' -- learn is . Users might want to change this to something else, so it's configurable."""))) conf.registerChannelValue(Factoids, 'showFactoidIfOnlyOneMatch', registry.Boolean(True, _("""Determines whether the bot will reply with the single matching factoid if only one factoid matches when using the search command."""))) 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, '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(_('$value'), _("""Determines the format of 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: limnoria-2020.03.17/plugins/Factoids/locales/0000755000175000017500000000000013634634547020164 5ustar valval00000000000000limnoria-2020.03.17/plugins/Factoids/locales/fi.po0000644000175000017500000003442013634634532021117 0ustar valval00000000000000# Factoids plugin in Limnoria # Copyright (C) 2011-2014 Limnoria # Mikaela Suomalainen , 2011-2014. # msgid "" msgstr "" "Project-Id-Version: Factoids plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 14:04+EET\n" "PO-Revision-Date: 2014-12-20 14:33+0200\n" "Last-Translator: Mikaela Suomalainen \n" "Language-Team: Finnish <>\n" "Language: fi\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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.10\n" #: config.py:45 msgid "" "Value must include $value, otherwise the factoid's value would be left\n" " out." msgstr "Arvon täytyy sisältää $value, muuten factoidin arvo jätettäisiin ulos." #: config.py:52 msgid "" "Determines whether the Factoids plugins will\n" " be browsable on the HTTP server." msgstr "Määrittää onko Factoids lisä-osa selattavissa HTTP-palvelimella." #: config.py:55 msgid "" "Determines whether factoids can be displayed\n" " via the web server." msgstr "Määrittää voidaanko factoideja näyttää verkkopalvelimen kautta." #: config.py:59 msgid "" "Only allows a user with voice or above on a\n" " channel to use the command." msgstr "" "Vain käyttäjät, joilla on ääni tai korkeampi valtuus kanavalla\n" " voivat käyttää tätä komentoa." #: config.py:62 msgid "" "Determines what separator must be used in \n" " the learn command. Defaults to 'is' -- learn is . \n" " Users might want to change this to something else, so it's\n" " configurable." msgstr "" "Määrittää sanan, jota käytetään erottimenä \"learn\"-komennossa. Tämä on\n" " oletuksena 'is\" -- \"learn is .\" Käyttäjät voivat haluta " "vaihtaa tämän\n" " joksikin muuksi, joten se on määritettävissä." #: config.py:67 #, fuzzy msgid "" "Determines whether the bot will reply with the\n" " single matching factoid if only one factoid matches when using the " "search\n" " command." msgstr "" "Määrittää vastaako botti yhden komennon täsmäävään\n" " factoidiin, jos vain yksi täsmää \"search\" komentoa käytettäessä.\n" #: config.py:71 msgid "" "Determines whether the bot will reply to invalid\n" " commands by searching for a factoid; basically making the whatis\n" " unnecessary when you want all factoids for a given key." msgstr "" "Määrittää vastaako botti virheelliseen komentoon etsimällä Factoidia; " "perusteellisesti\n" " tehden \"whatis\" komennon tarpeettomaksi, kun tahdotaan kaikki factoidit, " "jotka\n" " täsmäävät annettuun avaimeen." #: config.py:75 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 "" "Jos sinä yritetään etsiä olematonta factoidia, tämä asetus saa botin " "yrittämään \n" " löytää mahdollisesti täsmääviä avaimia muutaman tarkan täsmäusalgoritmin " "läpi ja\n" " palauttaa listan täsmäävistä avaimista ennen luovuttamistaan." #: config.py:80 msgid "$value" msgstr "$value" #: config.py:80 msgid "" "Determines the format of\n" " the response given when a factoid's value is requested. All the " "standard\n" " substitutes apply, in addition to \"$key\" for the factoid's key and " "\"$value\"\n" " for the factoid's value." msgstr "" "Määrittää missä muodossa factoidin arvo annetaan, kun factoidin arvoa " "pyydetään.\n" " Kaikki peruskorvaukset ovat voimassa, \"$key\" factoidin avaimelle ja\n" " \"$value\" factoidin arvolle." #: plugin.py:109 msgid "key" msgstr "avain" #: plugin.py:110 msgid "id" msgstr "id" #: plugin.py:111 msgid "fact" msgstr "facta" #: plugin.py:191 msgid "Provides the ability to show Factoids." msgstr "Tarjoaa kyvyn näyttää Factoideja." #: plugin.py:279 msgid "You have to be at least voiced to teach factoids." msgstr "Sinulla täytyy olla vähintään ääni opettaaksesi factoideja." #: plugin.py:316 msgid "" "[] %s \n" "\n" " Associates with . is only\n" " necessary if the message isn't sent on the channel\n" " itself. The word '%s' is necessary to separate " "the\n" " key from the value. It can be changed to another " "word\n" " via the learnSeparator registry value.\n" " " msgstr "" "[] %s \n" "\n" " Liittää . on vaadittu vain, jos viestiä ei " "lähetetä\n" " kanavalla itsellään. Sana '%s' on vaadittu erottamaan avain arvosta. Se " "voidaan\n" " vaihtaa toiseksi sanaksi rekisteriarvolla plugins.Factoids.learnSeparator." #: plugin.py:336 #, fuzzy 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 "" "Yritetäänkö mahdollisia factoideja etsiä kirjoitusvirhekorjauksella. " "Oletetaan, että\n" " ensimmäinen kirjain on oikein, jotta käsittelyaikaa voidaan vähentää.\n" " Ensin yritetään yksinkertaista jokerimerkki hakua ja jos se epäonnistuu, " "käytetään\n" " Damerau-Levenshtein muokkaus-etäisyys asteikkoa." #: plugin.py:394 plugin.py:524 msgid "That's not a valid number for that key." msgstr "Tuo ei ole kelvollinen numero tuolle avaimelle." #: plugin.py:416 plugin.py:510 plugin.py:739 msgid "No factoid matches that key." msgstr "Mikään factoidi ei täsmää tuota avainta." #: plugin.py:442 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 "" "[] [--raw] []\n" "\n" " Etsii arvoa factoidi tietokannasta. Jos numero on annettu,\n" " palauttaa vain juuri sen factoidin. Jos '--raw' asetus on\n" " annettu, muuttujan korvike ei vaikuta factoidissa.\n" " on vaadittu vain jos viestiä ei lähetetä kanavalla\n" " itsellään." #: plugin.py:459 plugin.py:664 msgid "key id" msgstr "avaimen id" # (verbatim) ? #: plugin.py:472 #, fuzzy 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" " Lisää factoidiin, joka on liitetty .\n" " on vaadittu vain, jos vain yksi factoidi on liitetty\n" " .\n" "\n" " Sama toiminto voidaan suorittaa 'learn' toiminnolla, mutta\n" " uusi avain tulee olemassa olamassa olevan (verbatim) factoidin sisällön " "kautta." #: plugin.py:519 plugin.py:536 msgid "This key-factoid relationship already exists." msgstr "Tämä avain-factoidi suhde on jo olemassa." #: plugin.py:527 msgid "" "This key has more than one factoid associated with it, but you have not " "provided a number." msgstr "" "Tähän avaimeen on liitetty useampi, kuin yksi factoidi, mutta numeroa ei ole " "annettu." #: plugin.py:541 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" " Palauttaa factoidien top-listan. käyttömäärän (rank) perusteella. Jos " "\n" " ei ole annettu, palauttaa factoideja rankListLength rekisteriarvon " "määrittämän\n" " määrän.\n" "\n" " Jos asetus --plain on annettu, ranking numeroita ja käyttömääriä ei " "sisällytetä\n" " ulostuloon.\n" "\n" " Jos --alpha asetus on annettu --plain:in lisäksi, avaimet lajitellaan \n" " aakkosjärjestyksessä, rankingin sijasta.\n" "\n" " on vaadittu vain jos viestiä ei lähetetä kanavalla\n" " itsellään." #: plugin.py:586 msgid "" "[] \n" "\n" " Locks the factoid(s) associated with so that they cannot be\n" " removed or added to. is only necessary if the message " "isn't\n" " sent in the channel itself.\n" " " msgstr "" "[] \n" "\n" " Lukitsee factoidi(n/t), jotka on liitetty , jotta niitä ei voida\n" " poistaa, eikä lisätä. on vaadittu vain, jos viestiä ei lähetetä\n" " kanavalla itsellään." #: plugin.py:604 msgid "" "[] \n" "\n" " Unlocks the factoid(s) associated with so that they can be\n" " removed or added to. is only necessary if the message " "isn't\n" " sent in the channel itself.\n" " " msgstr "" "[] \n" "\n" " Avaa factoidi(n/t), jotka on liitetty , jotta niitä voidaan\n" " lisätä ja poistaa. on vaadittu vain mikäli viestiä ei lähetetä " "kanavalla\n" " itsellään." #: plugin.py:643 msgid "" "[] [|*]\n" "\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" " Poistaa avain-factoidi suhteen factoidi tietokannasta. Jos " "avaimelle\n" " on useampi kuin yksi suhde, numero on vaadittu määrittämään mikä pitäisi " "poistaa/\n" " *:teä voidaan käyttää poistamaan kaikki suhteet .\n" "\n" " Jos tuloksena, avain (factoidi) jää ilman factoidia (avain), se poistetaan\n" " tietokannasta.\n" "\n" " on vaadittu vain, jos viestiä ei lähetetä kanavalla itsellään." #: plugin.py:658 msgid "You have to be at least voiced to remove factoids." msgstr "Sinulla täytyy olla ainakin ääni poistaaksesi factoideja." #: plugin.py:678 msgid "There is no such factoid." msgstr "Tuollaista factoidia ei ole." #: plugin.py:688 msgid "Invalid factoid number." msgstr "Epäkelvollinen factoidin numero." #: plugin.py:693 msgid "" "%s factoids have that key. Please specify which one to remove, or use * to " "designate all of them." msgstr "" "%s factoidilla on tuo avain. Määritä poistettava factoidi tai käytä *-" "merkkiä\n" " poistaaksesi ne kakki." #: plugin.py:701 #, fuzzy msgid "" "[]\n" "\n" " Returns random factoids from the database for . \n" " is only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[]\n" "\n" " Palauttaa satunnaisen factoidin tietokannasta. on " "vaadittu vain\n" " jos viestiä ei lähetetä kanavalla itsellään." #: plugin.py:723 msgid "I couldn't find a factoid." msgstr "En voinut löytää factoidia." #: plugin.py:728 msgid "" "[] \n" "\n" " Gives information about the factoid(s) associated with .\n" " is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[] \n" "\n" " Antaa tiedot factoid(ista/eista), jotka on liitetty .\n" " on vaadittu vain, jos viestiä ei lähetetä kanavalla\n" " itsellään." #: plugin.py:754 msgid "#%i was added by %s at %s, and has been recalled %n" msgstr "#Factoidin %i lisäsi %s %s, ja sitä on kutsuttu %n" #: plugin.py:757 msgid "time" msgstr "kerta" #: plugin.py:767 msgid "" "[] \n" "\n" " Changes the factoid # associated with according to\n" " .\n" " " msgstr "" "[] \n" "\n" " Vaihtaa factoidin #, joka on liitetty \n" " mukaan." #: plugin.py:781 msgid "I couldn't find any key %q" msgstr "En voinut löytää yhtään avainta %q" #: plugin.py:796 msgid "" "[] [--values] [--{regexp} ] [ ...]\n" "\n" " Searches the keyspace for keys matching . If --regexp is " "given,\n" " its associated value is taken as a regexp and matched against the " "keys.\n" " If --values is given, search the value space instead of the " "keyspace.\n" " " msgstr "" "[] [--values] [--{regexp} ] [ ...]\n" "\n" " Etsii avainavaruudesta avaimia, jotka täsmäävät . Jos --regexp " "on\n" " annettu, liittää arvon, joka on otettu säännöllisestä lausekkeesta ja " "täsmätty\n" " avaimia vastaan. Jos --values on annettu, etsii arvoavaruudesta " "avainavaruuden\n" " sijaan.\n" " " #: plugin.py:833 plugin.py:842 msgid "No keys matched that query." msgstr "Yksikään avain ei täsmännyt hakuun." #: plugin.py:838 plugin.py:847 msgid "More than 100 keys matched that query; please narrow your query." msgstr "Yli 100 avainta täsmäsi tuohon hakuun; ole hyvä ja kavenna hakuasi." #~ msgid "$key could be $value." #~ msgstr "$key voisi olla $value." limnoria-2020.03.17/plugins/Factoids/locales/fr.po0000644000175000017500000003333013634634532021127 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-01-21 19:24+CET\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: Limnoria \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" "Language: fr\n" #: config.py:45 msgid "" "Value must include $value, otherwise the factoid's value would be left\n" " out." msgstr "" "La valeur doit inclure $value, sinon la valeur de la factoid ne serait pas " "affichée." #: config.py:52 msgid "" "Determines whether the Factoids plugins will\n" " be browsable on the HTTP server." msgstr "Détermine si le plugin Factoids sera accessible par le serveur HTTP." #: config.py:55 msgid "" "Determines whether factoids can be displayed\n" " via the web server." msgstr "Détermine si les factoids seront affichées via le serveur web." #: config.py:59 msgid "" "Only allows a user with voice or above on a\n" " channel to use the command." msgstr "" "Autoriser uniquement un(e) utilisateur(trice) qui a un voice ou mieux dans " "le salon à utiliser la commande." #: config.py:62 msgid "" "Determines what separator must be used in the\n" " learn command. Defaults to 'as' -- learn as . Users " "might\n" " feel more comfortable with 'is' or something else, so it's\n" " configurable." msgstr "" "Détermine quel séparateur est utilisé par la commande 'learn'. Par défaut, " "il s'agit de 'est' : 'learn as . Les utilisateurs sont " "susceptibles de trouver 'is' plus confortable, ou n'importe quoi, c'est " "configurable." #: config.py:67 msgid "" "Determines whether the bot will reply with the\n" " single matching factoid if only one factoid matches when using the " "search\n" " command." msgstr "" "Détermine si le bot répondra avec une seule factoid, même si la recherche de " "l'utilisateur donne plusieurs résultats." #: config.py:71 msgid "" "Determines whether the bot will reply to invalid\n" " commands by searching for a factoid; basically making the whatis\n" " unnecessary when you want all factoids for a given key." 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:75 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:80 msgid "$key could be $value." msgstr "$key semble être $value." #: config.py:80 msgid "" "Determines the format of\n" " the response given when a factoid's value is requested. All the " "standard\n" " substitutes apply, in addition to \"$key\" for the factoid's key and " "\"$value\"\n" " 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:108 msgid "key" msgstr "clé" #: plugin.py:109 msgid "id" msgstr "n°" #: plugin.py:110 msgid "fact" msgstr "factoid" #: plugin.py:275 msgid "You have to be at least voiced to teach factoids." msgstr "Vous devez au moins être voice pour m’apprendre des factoids." #: plugin.py:312 msgid "" "[] %s \n" "\n" " Associates with . is only\n" " necessary if the message isn't sent on the channel\n" " itself. The word '%s' is necessary to separate " "the\n" " key from the value. It can be changed to another " "word\n" " via the learnSeparator registry value.\n" " " msgstr "" "[] %s \n" "\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:332 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:390 plugin.py:520 msgid "That's not a valid number for that key." msgstr "Ce n'est pas un nombre valide pour cette clef." #: plugin.py:412 plugin.py:506 plugin.py:735 msgid "No factoid matches that key." msgstr "Aucune factoid ne correspond à cette clef." #: plugin.py:438 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 "" "[] []\n" "\n" "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:455 plugin.py:660 msgid "key id" msgstr "id de clef" #: plugin.py:468 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:515 plugin.py:532 msgid "This key-factoid relationship already exists." msgstr "Cette relation clef-factoid existe déjà." #: plugin.py:523 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:537 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:582 msgid "" "[] \n" "\n" " Locks the factoid(s) associated with so that they cannot be\n" " removed or added to. is only necessary if the message " "isn't\n" " sent in the channel itself.\n" " " msgstr "" "[] \n" "\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:600 msgid "" "[] \n" "\n" " Unlocks the factoid(s) associated with so that they can be\n" " removed or added to. is only necessary if the message " "isn't\n" " sent in the channel itself.\n" " " msgstr "" "[] \n" "\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:639 msgid "" "[] [|*]\n" "\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. 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:654 msgid "You have to be at least voiced to remove factoids." msgstr "Vous devez au moins être voice pour supprimer des factoids." #: plugin.py:674 msgid "There is no such factoid." msgstr "Cette factoid n'existe pas." #: plugin.py:684 msgid "Invalid factoid number." msgstr "Numéro de factoid invalide." #: plugin.py:689 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:697 msgid "" "[]\n" "\n" " Returns random factoids from the database for . \n" " is only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[]\n" "\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:719 msgid "I couldn't find a factoid." msgstr "Je ne peux trouver une factoid" #: plugin.py:724 msgid "" "[] \n" "\n" " Gives information about the factoid(s) associated with .\n" " is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[] \n" "\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:750 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:753 msgid "time" msgstr "rappel" #: plugin.py:763 msgid "" "[] \n" "\n" " Changes the factoid # associated with according to\n" " .\n" " " msgstr "" "[] \n" "\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:777 msgid "I couldn't find any key %q" msgstr "Je ne peux trouver de clef %q" #: plugin.py:792 msgid "" "[] [--values] [--{regexp} ] [ ...]\n" "\n" " Searches the keyspace for keys matching . If --regexp is " "given,\n" " its associated value is taken as a regexp and matched against the " "keys.\n" " If --values is given, search the value space instead of the " "keyspace.\n" " " msgstr "" "[] [--values] [--{regexp} ] [...]\n" "\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:829 plugin.py:838 msgid "No keys matched that query." msgstr "Aucune clef ne correspond à cette requête." #: plugin.py:834 plugin.py:843 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." limnoria-2020.03.17/plugins/Factoids/locales/it.po0000644000175000017500000003150513634634532021136 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-07-18 13:05+0200\n" "Last-Translator: skizzhg \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:45 #, docstring msgid "" "Value must include $value, otherwise the factoid's value would be left\n" " out." msgstr "Il valore deve includere $value, altrimenti il valore del factoid non viene mostrato." #: config.py:51 msgid "" "Determines what separator must be used in the\n" " learn command. Defaults to 'as' -- learn as . Users might\n" " feel more comfortable with 'is' or something else, so it's\n" " configurable." msgstr "" "Determina quale separatore utilizzare nel comando \"learn\". Il predefinito è\n" " \"as\": learn as . Gli utenti possono trovare più comodo\n" " \"is\" o altro, per cui è configurabile." #: config.py:56 msgid "" "Determines whether the bot will reply with the\n" " single matching factoid if only one factoid matches when using the search\n" " command." msgstr "" "Determina se, usando il comando \"search\", il bot mosterà il contenuto del\n" " factoid se ne trova uno solo corrispondente." #: config.py:60 msgid "" "Determines whether the bot will reply to invalid\n" " commands by searching for a factoid; basically making the whatis\n" " unnecessary when you want all factoids for a given key." msgstr "" "Determina se il bot risponderà a comandi non validi cercando un factoid;\n" " fondamentalmente rende il comando \"whatis\" non necessario quando si\n" " vogliono tutti i factoid per una chiave specifica." #: 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 "" "Richiamando un factoid non esistente, con questa opzione il bot fa una ricerca\n" " di possibili chiavi corrispondenti tramite vari algoritmi di approssimazione\n" " e restituisce un elenco di chiavi simili." #: config.py:69 msgid "$key could be $value." msgstr "$key potrebbe essere $value." #: config.py:69 msgid "" "Determines the format of\n" " the response given when a factoid's value is requested. All the standard\n" " substitutes apply, in addition to \"$key\" for the factoid's key and \"$value\"\n" " for the factoid's value." msgstr "" "Determina il formato delle risposte date quando viene richiesto il valore di un\n" " factoid. In aggiunta a \"$key\" per la chiave e \"$value\" per il valore,\n" " sono applicati tutti gli standard di sostituzione." #: plugin.py:179 msgid "" "[] %s \n" "\n" " Associates with . is only\n" " necessary if the message isn't sent on the channel\n" " itself. The word '%s' is necessary to separate the\n" " key from the value. It can be changed to another word\n" " via the learnSeparator registry value.\n" " " msgstr "" "[] %s \n" "\n" " Associa a . è necessario solo se il messaggio\n" " non viene inviato nel canale stesso. La parola \"%s\" è necessaria per\n" " separare il valore della chiave; può essere modificata con una diversa\n" " tramite la voce di configurazione \"learnSeparator\".\n" " " #: plugin.py:199 #, docstring 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:386 msgid "That's not a valid number for that key." msgstr "Non è un numero valido per questa chiave." #: plugin.py:279 plugin.py:372 plugin.py:598 msgid "No factoid matches that key." msgstr "Nessun factoid corrisponde a questa chiave." #: 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 "" "[] [--raw] []\n" "\n" " Cerca il valore di nel database dei factoid. Fornendo un numero\n" " restituirà solo l'esatto factoid; se l'opzione --raw è specificata non\n" " verrà effettuata alcuna sostituzione di variabile. è necessario\n" " solo se il messaggio non viene inviato nel canale stesso." " " #: plugin.py:321 plugin.py:523 msgid "key id" msgstr "ID chiave" #: plugin.py:334 #, 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 "" "[] []\n" "\n" " Aggiunge al factoid associato a .\n" " è necessario solo se c'è più di un factoid associato a .\n" "\n" " La stessa cosa può essere ottenuta utilizzando la funzione \"learn\"\n" " con una nuova chiave su un factoid esistente.\n" " " #: plugin.py:381 plugin.py:398 msgid "This key-factoid relationship already exists." msgstr "Questa relazione chiave-factoid esiste già." #: plugin.py:389 msgid "This key has more than one factoid associated with it, but you have not provided a number." msgstr "Questa chiave ha più di un factoid associato ma non è stato fornito un numero." #: plugin.py:403 #, 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 "" "[] [--plain] [--alpha] []\n" "\n" " Riporta un elenco dei factoid più utilizzati. Se non è fornito,\n" " corrisponde al numero di chiavi definite dalla voce di registro rankListLength.\n" "\n" " Se l'opzione --plain è specificata, i numeri di classifica e il conteggio\n" " dell'uso non verranno inclusi nel risultato.\n" "\n" " Se l'opzione --alpha è specificata in aggiunta a --plain, le chiavi saranno\n" " ordinate alfabeticamente anziché per numero di classifica.\n" "\n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:448 #, docstring msgid "" "[] \n" "\n" " Locks the factoid(s) associated with so that they cannot be\n" " removed or added to. is only necessary if the message isn't\n" " sent in the channel itself.\n" " " msgstr "" "[] \n" "\n" " Blocca i factoid associati a in modo che non sia possibile rimuoverne o\n" " aggiungerne. è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:466 #, docstring msgid "" "[] \n" "\n" " Unlocks the factoid(s) associated with so that they can be\n" " removed or added to. is only necessary if the message isn't\n" " sent in the channel itself.\n" " " msgstr "" "[] \n" "\n" " Sblocca i factoid associati a in modo che sia possibile rimuoverne o\n" " aggiungerne. è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:505 #, docstring msgid "" "[] [|*]\n" "\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" " Rimuove una relazione chiave-factoid di dal database.\n" " Se è presnete più di una associazione, è necessario un numero per\n" " determinare quale vada rimossa. Un * (asterisco) può essere utilizzato\n" " per rimuovere tutte le relazioni di .\n" "\n" " Se in seguito a questa operazione la chiave (factoid) rimane senza una\n" " relazione con un factoid (chiave), questa andrà rimossa dal database.\n" "\n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:537 msgid "There is no such factoid." msgstr "Non c'è alcun factoid." #: plugin.py:547 msgid "Invalid factoid number." msgstr "Numero di factoid non valido." #: plugin.py:552 msgid "%s factoids have that key. Please specify which one to remove, or use * to designate all of them." msgstr "%s factoid hanno questa chiave; specifica quale desideri rimuovere o utilizza * per definirli tutti." #: plugin.py:560 #, docstring msgid "" "[]\n" "\n" " Returns a random factoid from the database for . \n" " is only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[]\n" "\n" " Restituisce un factoid casuale dal database di . è\n" " necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:582 msgid "I couldn't find a factoid." msgstr "Impossibile trovare un factoid." #: plugin.py:587 #, docstring msgid "" "[] \n" "\n" " Gives information about the factoid(s) associated with .\n" " is only necessary if the message isn't sent in the channel\n" " itself.\n" " " msgstr "" "[] \n" "\n" " Fornisce informazioni sui factoid associati a . è\n" " necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:613 msgid "#%i was added by %s at %s, and has been recalled %n" msgstr "#%i è stato aggiunto da %s il %s e richiamato %n" #: plugin.py:616 msgid "time" msgstr "volte" #: plugin.py:626 #, docstring msgid "" "[] \n" "\n" " Changes the factoid # associated with according to\n" " .\n" " " msgstr "" "[] \n" "\n" " Modifica il del factoid associato a in base a .\n" " " #: plugin.py:640 msgid "I couldn't find any key %q" msgstr "Non trovo nessuna chiave %q" #: plugin.py:655 #, docstring msgid "" "[] [--values] [--{regexp} ] [ ...]\n" "\n" " Searches the keyspace for keys matching . If --regexp is given,\n" " its associated value is taken as a regexp and matched against the keys.\n" " If --values is given, search the value space instead of the keyspace.\n" " " msgstr "" "[] [--values] [--{regexp} ] [ ...]\n" "\n" " Cerca le chiavi corrispondenti a . Se --regexp è fornita,\n" " ricerca le chiavi corrispondenti all'espressione regolare. Se --values è\n" " specificato, cerca i valori piuttosto che le chiavi.\n" " " #: plugin.py:692 plugin.py:701 msgid "No keys matched that query." msgstr "Nessuna chiave corrisponde a questa richiesta." #: plugin.py:697 plugin.py:706 msgid "More than 100 keys matched that query; please narrow your query." msgstr "A questa richiesta corrispondono più di 100 chiavi, restringi la ricerca." limnoria-2020.03.17/plugins/Factoids/plugin.py0000644000175000017500000011023313634634532020404 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009-2010, James McCoy # 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 os import sys import time import string import supybot.conf as conf import supybot.ircdb as ircdb import supybot.utils as utils from supybot.commands import * import supybot.plugins as plugins import supybot.utils.minisix as minisix import supybot.ircutils as ircutils import supybot.callbacks as callbacks import supybot.httpserver as httpserver from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Factoids') import sqlite3 import re from supybot.utils.seq import dameraulevenshtein def getFactoid(irc, msg, args, state): assert not state.channel callConverter('channel', irc, msg, args, state) separator = state.cb.registryValue('learnSeparator', state.channel, irc.network) try: i = args.index(separator) except ValueError: raise callbacks.ArgumentError args.pop(i) key = [] value = [] for (j, s) in enumerate(args[:]): if j < i: key.append(args.pop(0)) else: value.append(args.pop(0)) if not key or not value: raise callbacks.ArgumentError state.args.append(' '.join(key)) state.args.append(' '.join(value)) def getFactoidId(irc, msg, args, state): Type = 'key id' p = lambda i: i > 0 callConverter('int', irc, msg, args, state, Type, p) addConverter('factoid', getFactoid) addConverter('factoidId', getFactoidId) PAGE_SKELETON = """\ Factoids %s """ DEFAULT_TEMPLATES = { 'factoids/index.html': PAGE_SKELETON % """\

Factoids

""", 'factoids/channel.html': PAGE_SKELETON % """\

Factoids of %(channel)s

%(rows)s
""" + _('key') + """ """ + _('id') + """ """ + _('fact') + """
""" } httpserver.set_default_templates(DEFAULT_TEMPLATES) class FactoidsCallback(httpserver.SupyHTTPServerCallback): name = 'Factoids web interface' def doGetOrHead(self, handler, path, write_content): parts = path.split('/')[1:] if path == '/': self.send_response(200) self.send_header('Content-type', 'text/html; charset=utf-8') self.end_headers() self.write(httpserver.get_template('factoids/index.html')) elif len(parts) == 2: channel = utils.web.urlunquote(parts[0]) if not irc.isChannel(channel): self.send_response(404) self.send_header('Content-type', 'text/html; charset=utf-8') self.end_headers() if write_content: self.write(httpserver.get_template('generic/error.html')% {'title': 'Factoids - not a channel', 'error': 'This is not a channel'}) return if not self._plugin.registryValue('web.channel', channel, irc.network): self.send_response(403) self.send_header('Content-type', 'text/html; charset=utf-8') self.end_headers() if write_content: self.write(httpserver.get_template('generic/error.html')% {'title': 'Factoids - unavailable', 'error': 'This channel does not exist or its factoids ' 'are not available here.'}) return db = self._plugin.getDb(channel) cursor = db.cursor() cursor.execute("""SELECT keys.key, factoids.id, factoids.fact FROM factoids, keys, relations WHERE relations.key_id=keys.id AND relations.fact_id=factoids.id """) factoids = {} for (key, id_, fact) in cursor.fetchall(): if key not in factoids: factoids[key] = {} factoids[key][id_] = fact content = '' keys = sorted(factoids.keys()) for key in keys: facts = factoids[key] content += '' content += ('' '
%s' '') % (len(facts), key, key, key) for id_, fact in facts.items(): content += '%i' % id_ content += '%s' % fact content += '' content = content[:-len('')] self.send_response(200) self.send_header('Content-type', 'text/html; charset=utf-8') self.end_headers() if write_content: self.write(httpserver.get_template('factoids/channel.html')% {'channel': channel, 'rows': content}) def doPost(self, handler, path, form): if 'chan' in form: self.send_response(303) self.send_header('Location', './%s/' % utils.web.urlquote(form['chan'].value)) self.end_headers() else: self.send_response(400) self.send_header('Content-type', 'text/plain; charset=utf-8') self.end_headers() self.write('Missing field \'chan\'.') class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): """Provides the ability to show Factoids.""" def __init__(self, irc): callbacks.Plugin.__init__(self, irc) plugins.ChannelDBHandler.__init__(self) self._http_running = False conf.supybot.plugins.Factoids.web.enable.addCallback(self._doHttpConf) if self.registryValue('web.enable'): self._startHttp() def _doHttpConf(self, *args, **kwargs): if self.registryValue('web.enable'): if not self._http_running: self._startHttp() else: if self._http_running: self._stopHttp() def _startHttp(self): callback = FactoidsCallback() callback._plugin = self httpserver.hook('factoids', callback) self._http_running = True def _stopHttp(self): httpserver.unhook('factoids') self._http_running = False def die(self): if self.registryValue('web.enable'): self._stopHttp() super(self.__class__, self).die() def makeDb(self, filename): if os.path.exists(filename): db = sqlite3.connect(filename) if minisix.PY2: db.text_factory = str return db db = sqlite3.connect(filename) if minisix.PY2: db.text_factory = str cursor = db.cursor() cursor.execute("""CREATE TABLE keys ( id INTEGER PRIMARY KEY, key TEXT UNIQUE ON CONFLICT REPLACE )""") cursor.execute("""CREATE TABLE factoids ( id INTEGER PRIMARY KEY, added_by TEXT, added_at TIMESTAMP, 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 )""") db.commit() return db def getCommandHelp(self, command, simpleSyntax=None): method = self.getCommandMethod(command) if method.__func__.__name__ == 'learn': chan = None network = None if dynamic.msg is not None: chan = dynamic.msg.channel if dynamic.irc is not None: network = dynamic.irc.network s = self.registryValue('learnSeparator', chan, network) help = callbacks.getHelp if simpleSyntax is None: simpleSyntax = conf.supybot.reply.showSimpleSyntax.getSpecific( dynamic.irc.network, chan)() if simpleSyntax: help = callbacks.getSyntax return help(method, doc=method._fake__doc__ % (s, s), name=callbacks.formatCommand(command)) return super(Factoids, self).getCommandHelp(command, simpleSyntax) def _getKeyAndFactId(self, channel, key, factoid): db = self.getDb(channel) cursor = db.cursor() 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 self.registryValue('requireVoice', channel, irc.network) and \ not irc.state.channels[channel].isVoicePlus(msg.nick): irc.error(_('You have to be at least voiced to teach factoids.'), Raise=True) # 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() 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, ?, ?, ?, ?)""", (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("This key-factoid relationship already exists.") learn = wrap(learn, ['factoid'], checkDoc=False) learn._fake__doc__ = _("""[] %s Associates with . is only necessary if the message isn't sent on the channel itself. The word '%s' is necessary to separate the key from the value. It can be changed to another word via the learnSeparator registry value. """) def _lookupFactoid(self, channel, key): db = self.getDb(channel) cursor = db.cursor() 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() 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() 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(list(zip(flkeys, dl_metrics))) if min(dl_metrics) <= 2: return [key for key,item in dict_metrics.items() if item <= 2] if min(dl_metrics) <= 3: return [key for key,item in dict_metrics.items() if item <= 3] return [] def _updateRank(self, network, channel, factoids): if self.registryValue('keepRankInfo', channel, network): db = self.getDb(channel) cursor = db.cursor() 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 relations SET usage_count=? WHERE id=?", (old_count + 1, relationid,)) db.commit() def _replyFactoids(self, irc, msg, key, channel, factoids, 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(format_fact(factoids[number-1][0])) self._updateRank(irc.network, channel, [factoids[number-1]]) except IndexError: irc.error(_('That\'s not a valid number for that key.')) return else: env = {'key': key} def prefixer(v): env['value'] = v formatter = self.registryValue('format', msg.channel, irc.network) return ircutils.standardSubstitute(irc, msg, formatter, env) if len(factoids) == 1: irc.reply(format_fact(prefixer(factoids[0][0]))) else: factoidsS = [] counter = 1 for factoid in factoids: factoidsS.append(format('(#%i) %s', counter, format_fact(factoid[0]))) counter += 1 irc.replies(factoidsS, prefixer=prefixer, joiner=', or ', onlyPrefixFirst=True) self._updateRank(irc.network, channel, factoids) 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) factoids.sort() 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): channel = msg.channel if channel: if self.registryValue('replyWhenInvalidCommand', channel, irc.network): key = ' '.join(tokens) factoids = self._lookupFactoid(channel, key) if factoids: self._replyFactoids(irc, msg, key, channel, factoids, error=False) else: self._replyApproximateFactoids(irc, msg, channel, key, error=False) @internationalizeDocstring 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. 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(): number = int(words.pop()) if number <= 0: irc.errorInvalid(_('key id')) key = ' '.join(words) factoids = self._lookupFactoid(channel, key) if factoids: self._replyFactoids(irc, msg, key, channel, factoids, number, raw=raw) else: self._replyApproximateFactoids(irc, msg, channel, key) whatis = wrap(whatis, ['channel', getopts({'raw': '',}), 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, optlist, number): """[] [--plain] [--alpha] [] Returns a list of top-ranked factoid keys, sorted by usage count (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. """ if not number: number = self.registryValue('rankListLength', channel, irc.network) 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 ?""", (number,)) factkeys = cursor.fetchall() 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)) rank = wrap(rank, ['channel', getopts({'plain': '', 'alpha': '',}), optional('int')]) @internationalizeDocstring def lock(self, irc, msg, args, channel, key): """[] Locks the factoid(s) associated with so that they cannot be removed or added to. is only necessary if the message isn't sent in the channel itself. """ db = self.getDb(channel) cursor = db.cursor() cursor.execute("UPDATE factoids " "SET locked=1 WHERE factoids.id IN " "(SELECT fact_id FROM relations WHERE key_id IN " "(SELECT id FROM keys WHERE key LIKE ?));", (key,)) db.commit() irc.replySuccess() lock = wrap(lock, ['channel', 'text']) @internationalizeDocstring def unlock(self, irc, msg, args, channel, key): """[] Unlocks the factoid(s) associated with so that they can be removed or added to. is only necessary if the message isn't sent in the channel itself. """ db = self.getDb(channel) cursor = db.cursor() cursor.execute("UPDATE factoids " "SET locked=0 WHERE factoids.id IN " "(SELECT fact_id FROM relations WHERE key_id IN " "(SELECT id FROM keys WHERE key LIKE ?));", (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 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. """ if self.registryValue('requireVoice', channel, irc.network) and \ not irc.state.channels[channel].isVoicePlus(msg.nick): irc.error(_('You have to be at least voiced to remove factoids.'), Raise=True) number = None if len(words) > 1: if words[-1].isdigit(): number = int(words.pop()) if number <= 0: irc.errorInvalid(_('key id')) elif words[-1] == '*': words.pop() number = True key = ' '.join(words) db = self.getDb(channel) cursor = db.cursor() 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: self._deleteRelation(channel, results) irc.replySuccess() else: if number is not None: #results = cursor.fetchall() try: arelation = results[number-1] except IndexError: irc.error(_('Invalid factoid number.')) return self._deleteRelation(channel, [arelation,]) irc.replySuccess() else: irc.error(_('%s factoids have that key. ' 'Please specify which one to remove, ' 'or use * to designate all of them.') % len(results)) forget = wrap(forget, ['channel', many('something')]) @internationalizeDocstring def random(self, irc, msg, args, channel): """[] Returns random factoids from the database for . is only necessary if the message isn't sent in the channel itself. """ db = self.getDb(channel) cursor = db.cursor() cursor.execute("""SELECT id, key_id, fact_id FROM relations ORDER BY random() LIMIT 3""") results = cursor.fetchall() if len(results) != 0: L = [] 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: irc.error(_('I couldn\'t find a factoid.')) random = wrap(random, ['channel']) @internationalizeDocstring def info(self, irc, msg, args, channel, key): """[] Gives information about the factoid(s) associated with . is only necessary if the message isn't sent in the channel itself. """ db = self.getDb(channel) cursor = db.cursor() 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 = 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, locked, 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, 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', (counter, 'factoid'), factoids) irc.reply(s) info = wrap(info, ['channel', 'text']) @internationalizeDocstring def change(self, irc, msg, args, channel, key, number, replacer): """[] Changes the factoid # associated with according to . """ db = self.getDb(channel) cursor = db.cursor() cursor.execute("""SELECT factoids.id, factoids.fact 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)) return elif len(results) < number: irc.errorInvalid('key id') (id, fact) = results[number-1] newfact = replacer(fact) cursor.execute("UPDATE factoids SET fact=? WHERE id=?", (newfact, id)) db.commit() irc.replySuccess() change = wrap(change, ['channel', 'something', 'factoidId', 'regexpReplacer']) _sqlTrans = utils.str.MultipleReplacer({'*': '%', '?': '_'}) @internationalizeDocstring def search(self, irc, msg, args, channel, optlist, globs): """[] [--values] [--{regexp} ] [ ...] Searches the keyspace for keys matching . If --regexp is given, its associated value is taken as a regexp and matched against the keys. If --values is given, search the value space instead of the keyspace. """ if not optlist and not globs: raise callbacks.ArgumentError tables = ['keys'] formats = [] criteria = [] target = 'keys.key' predicateName = 'p' db = self.getDb(channel) for (option, arg) in optlist: if option == 'values': target = 'factoids.fact' if 'factoids' not in tables: tables.append('factoids') 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): return int(bool(r.search(s))) db.create_function(predicateName, 1, p) predicateName += 'p' for glob in globs: criteria.append('TARGET LIKE ?') formats.append(self._sqlTrans(glob)) 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: irc.reply(_('No keys matched that query.')) elif cursor.rowcount == 1 and \ self.registryValue('showFactoidIfOnlyOneMatch', channel, irc.network): self.whatis(irc, msg, [channel, cursor.fetchone()[0]]) 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, irc.network): 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.')) else: keys = [repr(t[0]) for t in results] s = format('%L', keys) irc.reply(s) search = wrap(search, ['channel', getopts({'values': '', 'regexp': 'regexpMatcher'}), any('glob')]) Class = Factoids # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Factoids/test.py0000644000175000017500000002403313634634532020067 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2010, James McCoy # 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 * import supybot.conf as conf import sqlite3 class FactoidsTestCase(ChannelPluginTestCase): plugins = ('Factoids',) def testRandomfactoid(self): self.assertError('random') self.assertNotError('learn jemfinch is my primary author') self.assertRegexp('random', 'primary author') def testLearn(self): self.assertError('learn is my primary author') self.assertError('learn jemfinch is') self.assertNotError('learn jemfinch is 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 is 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 is baz') self.assertNotError('info foo bar') self.assertRegexp('whatis foo bar', 'baz') self.assertNotError('learn foo bar is 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 'is' self.assertError('learn foo bar') # No 'is' with conf.supybot.plugins.Factoids.requireVoice.context(True): self.assertError('learn jemfinch is my primary author') self.irc.feedMsg(ircmsgs.mode(self.channel, args=('+h', self.nick))) self.assertNotError('learn jemfinch is my primary author') def testChangeFactoid(self): self.assertNotError('learn foo is 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 is my primary author') self.assertNotError('learn strike is a cool person working on me') self.assertNotError('learn inkedmn is another of my developers') self.assertNotError('learn jamessan is jamessan is a developer of much python') self.assertNotError('learn bwp is bwp is 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 is emergency number') self.assertRegexp('whatis 911', 'emergency number') def testNotZeroIndexed(self): self.assertNotError('learn foo is bar') self.assertNotRegexp('info foo', '#0') self.assertNotRegexp('whatis foo', '#0') self.assertNotError('learn foo is baz') self.assertNotRegexp('info foo', '#0') self.assertNotRegexp('whatis foo', '#0') def testInfoReturnsRightNumber(self): self.assertNotError('learn foo is bar') self.assertNotRegexp('info foo', '2 factoids') def testInfoUsageCount(self): self.assertNotError('learn moo is 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 as bar') self.assertNotError('learn foo is bar') self.assertRegexp('whatis foo', 'bar') orig = conf.supybot.plugins.Factoids.learnSeparator() try: conf.supybot.plugins.Factoids.learnSeparator.setValue('as') self.assertError('learn bar is baz') self.assertNotError('learn bar as 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.assertTrue(m1.args[1].startswith(m2.args[1])) finally: conf.supybot.plugins.Factoids. \ showFactoidIfOnlyOneMatch.setValue(orig) def testInvalidCommand(self): self.assertNotError('learn foo is bar') self.assertRegexp('foo', 'bar') self.assertNotError('learn mooz is cowz') self.assertRegexp('moo', 'mooz') self.assertRegexp('mzo', 'mooz') self.assertRegexp('moz', 'mooz') self.assertNotError('learn moped is pretty fast') self.assertRegexp('moe', 'mooz.*moped') self.assertError('nosuchthing') def testWhatis(self): self.assertNotError('learn foo is bar') self.assertRegexp('whatis foo', 'bar') self.assertRegexp('whatis foob', 'foo') self.assertNotError('learn foob is barb') self.assertRegexp('whatis foom', 'foo.*foob') def testStandardSubstitute(self): self.assertNotError('learn foo is this is $channel, and hour is $hour') self.assertRegexp('whatis foo', r'this is #test, and hour is \d{1,2}') self.assertRegexp('whatis --raw foo', r'this is \$channel, and hour is \$hour') self.assertNotError('learn bar is this is $$channel escaped') self.assertRegexp('whatis bar', r'this is \$channel') self.assertNotError('learn bar is this is $minute') self.assertRegexp('whatis bar', r'\$channel.*\d{1,2}') def testAlias(self): self.assertNotError('learn foo is bar') self.assertNotError('alias foo zoog') self.assertRegexp('whatis zoog', 'bar') self.assertNotError('learn foo is snorp') self.assertError('alias foo gnoop') self.assertNotError('alias foo gnoop 2') self.assertRegexp('whatis gnoop', 'snorp') def testRank(self): self.assertNotError('learn foo is bar') self.assertNotError('learn moo is cow') self.assertRegexp('factoids rank', r'#1 foo \(0\), #2 moo \(0\)') self.assertRegexp('whatis moo', r'.*cow.*') self.assertRegexp('factoids rank', r'#1 moo \(1\), #2 foo \(0\)') self.assertRegexp('factoids rank 1', r'#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 is "\\"bar\\""') self.assertRegexp('whatis foo', r'"bar"') def testLock(self): self.assertNotError('learn foo is bar') self.assertNotError('lock foo') self.assertNotError('unlock foo') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Filter/0000755000175000017500000000000013634634547016233 5ustar valval00000000000000limnoria-2020.03.17/plugins/Filter/__init__.py0000644000175000017500000000452713634634532020346 0ustar valval00000000000000### # Copyright (c) 2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Provides numerous filters, and a command (outfilter) to set them as filters on the output of the bot. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload reload(plugin) # In case we're being reloaded. if world.testing: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Filter/config.py0000644000175000017500000000600213634634532020042 0ustar valval00000000000000### # Copyright (c) 2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Filter') Filter = conf.registerPlugin('Filter') conf.registerGroup(Filter, 'spellit') conf.registerGlobalValue(Filter.spellit, 'replaceLetters', registry.Boolean(True, _("""Determines whether or not to replace letters in the output of spellit."""))) conf.registerGlobalValue(Filter.spellit, 'replacePunctuation', registry.Boolean(True, _("""Determines whether or not to replace punctuation in the output of spellit."""))) conf.registerGlobalValue(Filter.spellit, 'replaceNumbers', registry.Boolean(True, _("""Determines whether or not to replace numbers in the output of spellit."""))) conf.registerGroup(Filter, 'shrink') conf.registerChannelValue(Filter.shrink, 'minimum', registry.PositiveInteger(4, _("""Determines the minimum number of a letters in a word before it will be shrunken by the shrink command/filter."""))) def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Filter', True) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Filter/locales/0000755000175000017500000000000013634634547017655 5ustar valval00000000000000limnoria-2020.03.17/plugins/Filter/locales/fi.po0000644000175000017500000003234613634634532020615 0ustar valval00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: Filter plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 13:29+EET\n" "PO-Revision-Date: 2014-12-20 13:37+0200\n" "Last-Translator: Mikaela Suomalainen \n" "Language-Team: \n" "Language: fi\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-Generator: Poedit 1.6.10\n" #: config.py:38 msgid "" "Determines whether or not to\n" " replace letters in the output of spellit." msgstr "" "Määrittää\n" " korvataanko kirjaimet spellitin ulostulolla." #: config.py:41 msgid "" "Determines whether or not\n" " to replace punctuation in the output of spellit." msgstr "" "Määrittää korvataanko\n" " välimerkit spellitin ulostulolla.." #: config.py:44 msgid "" "Determines whether or not to\n" " replace numbers in the output of spellit." msgstr "" "Määrittää\n" " korvataanko numerot spellitin ulostulolla." #: config.py:48 msgid "" "Determines the minimum number of a letters\n" " in a word before it will be shrunken by the shrink command/filter." msgstr "" "Määritää minimi määrän kirjaimia\n" " sanassa ennen kuin se kutistetaan kutitus komennolla / suodattimella." #: plugin.py:52 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" " output of the bot -- for instance, you could make everything the bot " "says\n" " be in leetspeak, or Morse code, or any number of other kinds of " "filters.\n" " Not very useful, but definitely quite fun :)" msgstr "" "Tämä lisäosa tarjoaa muutamia komentoja, jotka muuttavat tekstiä jollakin " "tavalla.\n" " Se antaa myös valtuuden käyttää komentoja, jotka 'suodattavat' \n" " botin ulostuloa -- esimerkiksi voit saada kaiken mitä botti sanoo " "olevan \n" " leetspeakilla, tai Morsen aakkosilla, tai monen muunlaisella " "suodattimella.\n" " Ei kovin hyödyllinen, mutta varmasti aika hauska :)" #: plugin.py:86 msgid "" "[] []\n" "\n" " Sets the outFilter of this plugin to be . If no command " "is\n" " given, unsets the outFilter. is only necessary if the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[] []\n" "\n" " Asettaa tämän lisäosan ulostulo suodatuksen . Jos " "komentoa ei ole\n" " annettu, poistaa ulostulo suodattminen. on vaadittu vain " "jos\n" " viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:99 msgid "That's not a valid filter command." msgstr "Tuo ei ole kelvollinen filter komento." #: plugin.py:110 msgid "" "\n" "\n" " Removes all the vowels from . (If you're curious why this is\n" " named 'hebrew' it's because I (jemfinch) thought of it in Hebrew " "class,\n" " and printed Hebrew often elides the vowels.)\n" " " msgstr "" "\n" "\n" " Poistaa kaikki vokaalit . (Jos olet utelias miksi tämä " "on\n" " nimeltään 'hebrew', se johtuu siitä, että minä (jemfinch) luulin " "sitä Hepreaksi,\n" " ja tulostin Hebrewin usein vokaaleilla.)\n" " " #: plugin.py:121 msgid "" "\n" "\n" " Removes all the spaces from .\n" " " msgstr "" "\n" "\n" " Poistaa kaikki välilyönnit .\n" " " #: plugin.py:131 msgid "" "\n" "\n" " Returns , with all consecutive duplicated letters removed.\n" " " msgstr "" "\n" "\n" " Palauttaa , kaikki peräkkäiset samat kaksi kirjainta " "poistettuna.\n" " " #: plugin.py:144 msgid "" "\n" "\n" " Returns the binary representation of .\n" " " msgstr "" "\n" "\n" " Palauttaa binääri esitelmänä.\n" " " #: plugin.py:179 msgid "" "\n" "\n" " Returns the character representation of binary .\n" " Assumes ASCII, 8 digits per character.\n" " " msgstr "" "\n" "\n" " Palauttaa binäärimerkki esityksen.\n" " Olettaa ASCII, 8 numeroa per merkki.\n" " " #: plugin.py:193 msgid "" "\n" "\n" " Returns a hexstring from the given string; a hexstring is a string\n" " composed of the hexadecimal value of each character in the string\n" " " msgstr "" "\n" "\n" " Palauttaa heksaketjun annetusta ketjusta; heksaketju on ketju, jonka " "muodostaa\n" " jokaisen ketjussa olevan merkin heksadesimaaliarvo. \n" " " #: plugin.py:204 msgid "" "\n" "\n" " Returns the string corresponding to . Obviously,\n" " must be a string of hexadecimal digits.\n" " " msgstr "" "\n" "\n" " Palauttaa ketjun joka vastaa . Ilmeisesti,\n" " täytyy olla ketju heksadesimaalisia numeroita.\n" " " #: plugin.py:213 msgid "Invalid input." msgstr "Virheellinen sisääntulo." #: plugin.py:219 msgid "" "\n" "\n" " Rotates 13 characters to the right in the alphabet. Rot13 " "is\n" " commonly used for text that simply needs to be hidden from " "inadvertent\n" " reading by roaming eyes, since it's easily reversible.\n" " " msgstr "" "\n" "\n" " Kääntää kolmetoista merkkiä oikealle aakkosissa. Rot13 " "käytetään\n" " tekstissä, joka yksinkertaisesti täytyy piilottaa huomaamattomasti\n" " lukijan harhailevilta silmiltä, koska se on helposti kumottavissa.\n" " " #: plugin.py:240 msgid "" "\n" "\n" " Returns the l33tspeak version of \n" " " msgstr "" "\n" "\n" " Palauttaa l33tspeak version \n" " " #: plugin.py:260 msgid "" "\n" "\n" " Replies with an especially k-rad translation of .\n" " " msgstr "" "\n" "\n" " Vastaa ilmeisesti k-rad käännöksellä.\n" " " #: plugin.py:276 msgid "" "\n" "\n" " Replies with a string where each word is scrambled; i.e., each " "internal\n" " letter (that is, all letters but the first and last) are shuffled.\n" " " msgstr "" "\n" "\n" " Vastaa ketjulla, jossa jokainen sana on sekaisin; esim., jokainen " "sisänen\n" " kirjain (eli kaikki kirjaimet paitsi ensinmäinen ja viimeinen) " "sekoitetaan.\n" " " #: plugin.py:341 msgid "" "\n" "\n" " Does the reverse of the morse command.\n" " " msgstr "" "\n" "\n" " Tekee morse komennon käänteisenä.\n" " " #: plugin.py:358 msgid "" "\n" "\n" " Gives the Morse code equivalent of a given string.\n" " " msgstr "" "\n" "\n" " Antaa annetun ketjun Morsen aakkosilla.\n" " " #: plugin.py:370 msgid "" "\n" "\n" " Reverses .\n" " " msgstr "" "\n" "\n" " Kääntää .\n" " " #: plugin.py:388 msgid "" "\n" "\n" " Returns with each character randomly colorized.\n" " " msgstr "" "\n" "\n" " Palauttaa niin, että jokainen merkki on satunnaisesti " "värjätty.\n" " " #: plugin.py:398 msgid "" "\n" "\n" " Returns colorized like a rainbow.\n" " " msgstr "" "\n" "\n" " Palauttaa sateenkaaren väreillä.\n" " " #: plugin.py:413 msgid "" "\n" "\n" " Returns stripped of all color codes.\n" " " msgstr "" "\n" "\n" " Palauttaa kaikki värikoodit riisuttuna.\n" " " #: plugin.py:422 #, fuzzy msgid "" "\n" "\n" " Returns as if an AOL user had said it.\n" " " msgstr "" "\n" "\n" " Palauttaa kuin AOL käyttäjä olisi sanonut sen.\n" " " #: plugin.py:449 msgid "" "\n" "\n" " Returns as if JeffK had said it himself.\n" " " msgstr "" "\n" "\n" " Palauttaa kuin JeffK olisi sanonut sen itse.\n" " " #: plugin.py:545 msgid "ay" msgstr "aa" #: plugin.py:545 msgid "bee" msgstr "bee" #: plugin.py:545 msgid "dee" msgstr "dee" #: plugin.py:545 msgid "see" msgstr "see" #: plugin.py:546 msgid "aych" msgstr "hoo" #: plugin.py:546 msgid "ee" msgstr "ee" #: plugin.py:546 msgid "eff" msgstr "äf" #: plugin.py:546 msgid "gee" msgstr "gee" #: plugin.py:547 msgid "ell" msgstr "äll" #: plugin.py:547 msgid "eye" msgstr "iii" #: plugin.py:547 msgid "jay" msgstr "jii" #: plugin.py:547 msgid "kay" msgstr "koo" #: plugin.py:548 msgid "cue" msgstr "quu" #: plugin.py:548 msgid "em" msgstr "äm" #: plugin.py:548 msgid "en" msgstr "än" #: plugin.py:548 msgid "oh" msgstr "oo" #: plugin.py:548 msgid "pee" msgstr "pee" #: plugin.py:549 msgid "arr" msgstr "är" #: plugin.py:549 msgid "ess" msgstr "äs" #: plugin.py:549 msgid "tee" msgstr "tee" #: plugin.py:549 msgid "you" msgstr "uu" #: plugin.py:550 msgid "double-you" msgstr "tupla-vee" #: plugin.py:550 msgid "ecks" msgstr "äks" #: plugin.py:550 msgid "vee" msgstr "vee" #: plugin.py:550 msgid "why" msgstr "yy" #: plugin.py:551 msgid "zee" msgstr "zet" #: plugin.py:556 msgid "exclamation point" msgstr "huutomerkki" #: plugin.py:557 msgid "quote" msgstr "lainausmerkki" #: plugin.py:558 msgid "pound" msgstr "punta" #: plugin.py:559 msgid "dollar sign" msgstr "dollari merkki" #: plugin.py:560 msgid "percent" msgstr "prosentti" #: plugin.py:561 msgid "ampersand" msgstr "at-merkki" #: plugin.py:562 msgid "single quote" msgstr "heittomerkki" #: plugin.py:563 msgid "left paren" msgstr "vasen sulku" #: plugin.py:564 msgid "right paren" msgstr "oikea sulku" #: plugin.py:565 msgid "asterisk" msgstr "tähti" #: plugin.py:566 msgid "plus" msgstr "plus" #: plugin.py:567 msgid "comma" msgstr "pilkku" #: plugin.py:568 msgid "minus" msgstr "miinus" #: plugin.py:569 msgid "period" msgstr "piste" #: plugin.py:570 msgid "slash" msgstr "kauttaviiva" #: plugin.py:571 msgid "colon" msgstr "puolipiste" #: plugin.py:572 msgid "semicolon" msgstr "puoli piste" #: plugin.py:573 msgid "less than" msgstr "pienempi kuin" #: plugin.py:574 msgid "equals" msgstr "täsmäävä" #: plugin.py:575 msgid "greater than" msgstr "suurempi kuin" #: plugin.py:576 msgid "question mark" msgstr "kysymysmerkki" #: plugin.py:577 msgid "at" msgstr "miukumauku" #: plugin.py:578 msgid "left bracket" msgstr "vasen hakasulku" #: plugin.py:579 msgid "backslash" msgstr "vasen kenoviiva" #: plugin.py:580 msgid "right bracket" msgstr "oikea hakasulku" #: plugin.py:581 msgid "caret" msgstr "sirkumfleksi" #: plugin.py:582 msgid "underscore" msgstr "alaviiva" #: plugin.py:583 #, fuzzy msgid "backtick" msgstr "heittomerkki" #: plugin.py:584 #, fuzzy msgid "left brace" msgstr "vasen laatikkosulku" #: plugin.py:585 msgid "pipe" msgstr "putki" #: plugin.py:586 #, fuzzy msgid "right brace" msgstr "oikea laatikkosulku" #: plugin.py:587 msgid "tilde" msgstr "vinoviiva" #: plugin.py:590 msgid "one" msgstr "yksi" #: plugin.py:590 msgid "three" msgstr "kolme" #: plugin.py:590 msgid "two" msgstr "kaksi" #: plugin.py:590 msgid "zero" msgstr "nolla" #: plugin.py:591 msgid "five" msgstr "viisi" #: plugin.py:591 msgid "four" msgstr "neljä" #: plugin.py:591 msgid "seven" msgstr "seitsemän" #: plugin.py:591 msgid "six" msgstr "kuusi" #: plugin.py:592 msgid "eight" msgstr "kahdeksan" #: plugin.py:592 msgid "nine" msgstr "yhdeksän" #: plugin.py:596 msgid "" "\n" "\n" " Returns , phonetically spelled out.\n" " " msgstr "" "\n" "\n" " Palauttaa , foneettisesti kirjoitettuna.\n" " " #: plugin.py:626 msgid "" "\n" "\n" " Returns as GNU/RMS would say it.\n" " " msgstr "" "\n" "\n" " Palauttaa kuin GNU/RMS sanoisi sen.\n" " " #: plugin.py:635 msgid "" "\n" "\n" " Returns with each word longer than\n" " supybot.plugins.Filter.shrink.minimum being shrunken (i.e., like\n" " \"internationalization\" becomes \"i18n\").\n" " " msgstr "" "\n" "\n" " Palauttaa jokainen sana joka on suurempi kuin\n" " supybot.plugins.Filter.shrink.minimum kutistettuna (esim., kuten\n" " \"internationalization\" tulee \"i18n\").\n" " " #: plugin.py:693 msgid "" "\n" "\n" " Returns rotated 180 degrees. Only really works for ASCII\n" " printable characters.\n" " " msgstr "" "\n" "\n" " Palauttaa 180 astetta käännettynä. Toimii vain ASCII\n" " tulostettavilla merkeillä.\n" " " #~ msgid "" #~ "\n" #~ "\n" #~ " Returns the lisping version of \n" #~ " " #~ msgstr "" #~ "\n" #~ "\n" #~ " Palauttaa sammaltavan version .\n" #~ " " #~ msgid "" #~ "\n" #~ "\n" #~ " Returns with the l's made into r's and r's made into l's.\n" #~ " " #~ msgstr "" #~ "\n" #~ "\n" #~ " Palauttaa l:ät muutettuna r:iksi made, jotka on tehty l:" #~ "ksi.\n" #~ " " limnoria-2020.03.17/plugins/Filter/locales/fr.po0000644000175000017500000003070413634634532020622 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz \n" "Language-Team: Limnoria \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" #: config.py:38 msgid "" "Determines whether or not to\n" " replace letters in the output of spellit." msgstr "Détermine si il faut ou non remplacer les lettres dans la sortie de spellit." #: config.py:41 msgid "" "Determines whether or not\n" " to replace punctuation in the output of spellit." msgstr "Détermine si il faut replacer la ponctuation dans la sortie de spellit." #: config.py:44 msgid "" "Determines whether or not to\n" " replace numbers in the output of spellit." msgstr "Détermine si on doit remplacer les nombres dans la sortie de spellit." #: config.py:48 msgid "" "Determines the minimum number of a letters\n" " 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: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" " output of the bot -- for instance, you could make everything the bot says\n" " be in leetspeak, or Morse code, or any number of other kinds of filters.\n" " 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:84 msgid "" "[] []\n" "\n" " Sets the outFilter of this plugin to be . If no command is\n" " given, unsets the outFilter. is only necessary if the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[] []\n" "\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:97 msgid "That's not a valid filter command." msgstr "Ce n'est pas une commande de filtre valide" #: plugin.py:107 msgid "" "\n" "\n" " Removes all the vowels from . (If you're curious why this is\n" " named 'hebrew' it's because I (jemfinch) thought of it in Hebrew class,\n" " and printed Hebrew often elides the vowels.)\n" " " msgstr "" "\n" "\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:119 msgid "" "\n" "\n" " Removes all the spaces from .\n" " " msgstr "" "\n" "\n" "Supprime tous les espaces du ." #: plugin.py:129 msgid "" "\n" "\n" " Returns , with all consecutive duplicated letters removed.\n" " " msgstr "" "\n" "\n" "Renvoie le texte, avec toutes les lettres consécutives dupliquées supprimées." #: plugin.py:142 msgid "" "\n" "\n" " Returns the binary representation of .\n" " " msgstr "" "\n" "\n" "Retourne la représentation binaire du ." #: plugin.py:168 msgid "" "\n" "\n" " Returns the character representation of binary .\n" " Assumes ASCII, 8 digits per character.\n" " " msgstr "" "\n" "\n" "Retourne le caractère représentant du . Considère qu'il s'agit d'ASCII, 8 bits par caractère." #: plugin.py:179 msgid "" "\n" "\n" " Returns a hexstring from the given string; a hexstring is a string\n" " composed of the hexadecimal value of each character in the string\n" " " msgstr "" "\n" "\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:189 msgid "" "\n" "\n" " Returns the string corresponding to . Obviously,\n" " must be a string of hexadecimal digits.\n" " " msgstr "" "\n" "\n" "Retourne la chaîne correspondant à la . Bien sûr, ne doit contenir que des caractères hexadécimaux." #: plugin.py:197 msgid "Invalid input." msgstr "Entrée invalide." #: plugin.py:202 msgid "" "\n" "\n" " Rotates 13 characters to the right in the alphabet. Rot13 is\n" " commonly used for text that simply needs to be hidden from inadvertent\n" " reading by roaming eyes, since it's easily reversible.\n" " " msgstr "" "\n" "\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:213 msgid "" "\n" "\n" " Returns the lisping version of \n" " " msgstr "" "\n" "\n" "Retourne la version zézéyée du texte." #: plugin.py:244 msgid "" "\n" "\n" " Returns the l33tspeak version of \n" " " msgstr "" "\n" "\n" "Retourne la version l33t du ." #: plugin.py:264 msgid "" "\n" "\n" " Replies with an especially k-rad translation of .\n" " " msgstr "" "\n" "\n" "Répond avec une traduction k-rad du ." #: plugin.py:280 msgid "" "\n" "\n" " Replies with a string where each word is scrambled; i.e., each internal\n" " letter (that is, all letters but the first and last) are shuffled.\n" " " msgstr "" "\n" "\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:345 msgid "" "\n" "\n" " Does the reverse of the morse command.\n" " " msgstr "" "\n" "\n" "Fait l'inverse de la commande morse." #: plugin.py:362 msgid "" "\n" "\n" " Gives the Morse code equivalent of a given string.\n" " " msgstr "" "\n" "\n" "Donne le code Morse équivalent à la chaîne donnée." #: plugin.py:374 msgid "" "\n" "\n" " Reverses .\n" " " msgstr "" "\n" "\n" "Inverse le ." #: plugin.py:391 msgid "" "\n" "\n" " Returns with each character randomly colorized.\n" " " msgstr "" "\n" "\n" "Retourne le avec chaque caractère coloré de façon aléatoire." #: plugin.py:401 msgid "" "\n" "\n" " Returns colorized like a rainbow.\n" " " msgstr "" "\n" "\n" "Retourne le texte colorisé comme un arc-en-ciel." #: plugin.py:412 msgid "" "\n" "\n" " Returns stripped of all color codes.\n" " " msgstr "" "\n" "\n" "Retourne le texte en retirant tous les codes de couleur" #: plugin.py:421 msgid "" "\n" "\n" " Returns as if an AOLuser had said it.\n" " " msgstr "" "\n" "\n" "Retourne le comme si un AOLuser l'avait dit." #: plugin.py:448 msgid "" "\n" "\n" " Returns as if JeffK had said it himself.\n" " " msgstr "" "\n" "\n" "Retourne le comme si JeffK l'avait dit lui-même." #: plugin.py:544 msgid "ay" msgstr "ah" #: plugin.py:544 msgid "bee" msgstr "bé" #: plugin.py:544 msgid "dee" msgstr "dé" #: plugin.py:544 msgid "see" msgstr "cé" #: plugin.py:545 msgid "aych" msgstr "ache" #: plugin.py:545 msgid "ee" msgstr "euh" #: plugin.py:545 msgid "eff" msgstr "èf" #: plugin.py:545 msgid "gee" msgstr "gé" #: plugin.py:546 msgid "ell" msgstr "èl" #: plugin.py:546 msgid "eye" msgstr "ih" #: plugin.py:546 msgid "jay" msgstr "ji" #: plugin.py:546 msgid "kay" msgstr "ka" #: plugin.py:547 msgid "cue" msgstr "cu" #: plugin.py:547 msgid "em" msgstr "èm" #: plugin.py:547 msgid "en" msgstr "èn" #: plugin.py:547 msgid "oh" msgstr "oh" #: plugin.py:547 msgid "pee" msgstr "pé" #: plugin.py:548 msgid "arr" msgstr "ère" #: plugin.py:548 msgid "ess" msgstr "èce" #: plugin.py:548 msgid "tee" msgstr "té" #: plugin.py:548 msgid "you" msgstr "uh" #: plugin.py:549 msgid "double-you" msgstr "double-vé" #: plugin.py:549 msgid "ecks" msgstr "icks" #: plugin.py:549 msgid "vee" msgstr "vé" #: plugin.py:549 msgid "why" msgstr "i-grec" #: plugin.py:550 msgid "zee" msgstr "zèd" #: plugin.py:555 msgid "exclamation point" msgstr "point d'exclamation" #: plugin.py:556 msgid "quote" msgstr "guillemet double" #: plugin.py:557 msgid "pound" msgstr "livre" #: plugin.py:558 msgid "dollar sign" msgstr "signe du dollar" #: plugin.py:559 msgid "percent" msgstr "pourcent" #: plugin.py:560 msgid "ampersand" msgstr "espèrluette" #: plugin.py:561 msgid "single quote" msgstr "guillemet" #: plugin.py:562 msgid "left paren" msgstr "parenthèse ouvrante" #: plugin.py:563 msgid "right paren" msgstr "parenthèse fermante" #: plugin.py:564 msgid "asterisk" msgstr "asterisque" #: plugin.py:565 msgid "plus" msgstr "plus" #: plugin.py:566 msgid "comma" msgstr "virgule" #: plugin.py:567 msgid "minus" msgstr "moins" #: plugin.py:568 msgid "period" msgstr "point" #: plugin.py:569 msgid "slash" msgstr "slash" #: plugin.py:570 msgid "colon" msgstr "double-point" #: plugin.py:571 msgid "semicolon" msgstr "point-virgule" #: plugin.py:572 msgid "less than" msgstr "inférieur" #: plugin.py:573 msgid "equals" msgstr "moins que" #: plugin.py:574 msgid "greater than" msgstr "supérieur" #: plugin.py:575 msgid "question mark" msgstr "point d'exclamation" #: plugin.py:576 msgid "at" msgstr "arobase" #: plugin.py:577 msgid "left bracket" msgstr "crochet ouvrant" #: plugin.py:578 msgid "backslash" msgstr "anti-slash" #: plugin.py:579 msgid "right bracket" msgstr "crochet fermant" #: plugin.py:580 msgid "caret" msgstr "accent circonflexe" #: plugin.py:581 msgid "underscore" msgstr "underscore" #: plugin.py:582 msgid "backtick" msgstr "accent grave" #: plugin.py:583 msgid "left brace" msgstr "crochet ouvrant" #: plugin.py:584 msgid "pipe" msgstr "pipe" #: plugin.py:585 msgid "right brace" msgstr "crochet fermant" #: plugin.py:586 msgid "tilde" msgstr "tilde" #: plugin.py:589 msgid "one" msgstr "un" #: plugin.py:589 msgid "three" msgstr "trois" #: plugin.py:589 msgid "two" msgstr "deux" #: plugin.py:589 msgid "zero" msgstr "zéro" #: plugin.py:590 msgid "five" msgstr "cinq" #: plugin.py:590 msgid "four" msgstr "quatre" #: plugin.py:590 msgid "seven" msgstr "sept" #: plugin.py:590 msgid "six" msgstr "six" #: plugin.py:591 msgid "eight" msgstr "huit" #: plugin.py:591 msgid "nine" msgstr "neuf" #: plugin.py:595 msgid "" "\n" "\n" " Returns , phonetically spelled out.\n" " " msgstr "" "\n" "\n" "Retourne le , épellé phonétiquement" #: plugin.py:625 msgid "" "\n" "\n" " Returns as GNU/RMS would say it.\n" " " msgstr "" "\n" "\n" "Retourne le comme si GNU/RMS l'avait dite." #: plugin.py:634 msgid "" "\n" "\n" " Returns with each word longer than\n" " supybot.plugins.Filter.shrink.minimum being shrunken (i.e., like\n" " \"internationalization\" becomes \"i18n\").\n" " " msgstr "" "\n" "\n" "Retourne le texte avec chaque mot plus long que supybot.plugins.Filter.shrink.minimum découpé (par exemple, \"internationalization\" devient i18n)" #: plugin.py:653 msgid "" "\n" "\n" " Returns with the l's made into r's and r's made into l's.\n" " " msgstr "" "\n" "\n" "Retourne le avec les l transormés en r et les r transformés en l." #: plugin.py:702 msgid "" "\n" "\n" " Returns rotated 180 degrees. Only really works for ASCII\n" " printable characters.\n" " " msgstr "" "\n" "\n" "Retourne le tourné à 180 degrés. Ne marche pour de bon qu'avec des caractères ASCII imprimables" limnoria-2020.03.17/plugins/Filter/locales/it.po0000644000175000017500000003234313634634532020630 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-07-19 22:51+0200\n" "Last-Translator: skizzhg \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:38 msgid "" "Determines whether or not to\n" " replace letters in the output of spellit." msgstr "Determina se sostituire o meno le lettere nell'output di spellit." #: config.py:41 msgid "" "Determines whether or not\n" " to replace punctuation in the output of spellit." msgstr "Determina se sostituire o meno la punteggiatura nell'output di spellit." #: config.py:44 msgid "" "Determines whether or not to\n" " replace numbers in the output of spellit." msgstr "Determina se sostituire o meno i numeri nell'output di spellit." #: config.py:48 msgid "" "Determines the minimum number of a letters\n" " in a word before it will be shrunken by the shrink command/filter." msgstr "" "Determina il numero minimo di lettere in una parola prima di essere\n" " abbreviata dal comando shrink." #: plugin.py:50 #, docstring 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" " output of the bot -- for instance, you could make everything the bot says\n" " be in leetspeak, or Morse code, or any number of other kinds of filters.\n" " Not very useful, but definitely quite fun :)" msgstr "" "Questo plugin offre svariati comandi che trasformano il testo in vari modi.\n" " Fornisce anche la possibilità di utilizzare questi comandi per \"filtrare\"\n" " l'output del bot; è possibile, ad esempio, far sì che qualsiasi cosa dica\n" " sia in l33t5p34k, codice Morse o altri tipi di trasformazione.\n" " Non molto utile ma decisamente divertente :)" #: plugin.py:84 #, docstring msgid "" "[] []\n" "\n" " Sets the outFilter of this plugin to be . If no command is\n" " given, unsets the outFilter. is only necessary if the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[] []\n" "\n" " Imposta il filtro di uscita di questo plugin per essere .\n" " Se non viene specificato alcun comando il filtro sarà disattivato.\n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:97 msgid "That's not a valid filter command." msgstr "Questo non è un filtro valido." #: plugin.py:107 #, docstring msgid "" "\n" "\n" " Removes all the vowels from . (If you're curious why this is\n" " named 'hebrew' it's because I (jemfinch) thought of it in Hebrew class,\n" " and printed Hebrew often elides the vowels.)\n" " " msgstr "" "\n" "\n" " Rimuove tutte le vocali da (se siete curiosi di sapere come mai si\n" " chiama \"hebrew\" è perché io (jemfinch) l'ho pensata in ebraico, le stampe\n" " ebraiche sono spesso prive di vocali).\n" " " #: plugin.py:119 #, docstring msgid "" "\n" "\n" " Removes all the spaces from .\n" " " msgstr "" "\n" "\n" " Rimuove tutti gli spazi da .\n" " " #: plugin.py:129 #, docstring msgid "" "\n" "\n" " Returns , with all consecutive duplicated letters removed.\n" " " msgstr "" "\n" "\n" " Restituisce rimuovendo tutte le lettere doppie consecutive.\n" " " #: plugin.py:142 #, docstring msgid "" "\n" "\n" " Returns the binary representation of .\n" " " msgstr "" "\n" "\n" " Restituisce la rappresentazione binaria di .\n" " " #: plugin.py:168 #, docstring msgid "" "\n" "\n" " Returns the character representation of binary .\n" " Assumes ASCII, 8 digits per character.\n" " " msgstr "" "\n" "\n" " Restituisce la rappresentazione in caratteri di \n" " binario. Presuppone sia ASCII, 8 cifre per carattere.\n" " " #: plugin.py:179 #, docstring msgid "" "\n" "\n" " Returns a hexstring from the given string; a hexstring is a string\n" " composed of the hexadecimal value of each character in the string\n" " " msgstr "" "\n" "\n" " Restituisce una stringa esadecimale dalla stringa data; una stringa\n" " esadecimale è una stringa composta dal valore esadecimale di ogni carattere.\n" " " #: plugin.py:189 #, docstring msgid "" "\n" "\n" " Returns the string corresponding to . Obviously,\n" " must be a string of hexadecimal digits.\n" " " msgstr "" "\n" "\n" " Restituisce la stringa corrispondente a ,\n" " quest'ultima deve ovviamente contenere caratteri esadecimali.\n" " " #: plugin.py:197 msgid "Invalid input." msgstr "Input non valido." #: plugin.py:202 #, docstring msgid "" "\n" "\n" " Rotates 13 characters to the right in the alphabet. Rot13 is\n" " commonly used for text that simply needs to be hidden from inadvertent\n" " reading by roaming eyes, since it's easily reversible.\n" " " msgstr "" "\n" "\n" " Sposta ogni carattere di 13 posizioni a destra nell'alfabeto.\n" " Giacché Rot13 è facilmente reversibile, è comunemente usato per offuscare\n" " del testo da letture involontarie.\n" " " #: plugin.py:213 #, docstring msgid "" "\n" "\n" " Returns the lisping version of \n" " " msgstr "" "\n" "\n" " Restituisce la versione blesa di \n" " " #: plugin.py:244 #, docstring msgid "" "\n" "\n" " Returns the l33tspeak version of \n" " " msgstr "" "\n" "\n" " Restituisce la versione l33t di \n" " " #: plugin.py:264 #, docstring msgid "" "\n" "\n" " Replies with an especially k-rad translation of .\n" " " msgstr "" "\n" "\n" " Risponde con una particolare traduzione k-rad di .\n" " " #: plugin.py:280 #, docstring msgid "" "\n" "\n" " Replies with a string where each word is scrambled; i.e., each internal\n" " letter (that is, all letters but the first and last) are shuffled.\n" " " msgstr "" "\n" "\n" " Risponde con una stringa dove ogni parola è mescolata; ovvero ogni lettera\n" " interna (tutte le lettere tranne la prima e l'ultima) è mischiata con le altre.\n" " " #: plugin.py:345 #, docstring msgid "" "\n" "\n" " Does the reverse of the morse command.\n" " " msgstr "" "\n" "\n" " Fa il contrario del comando morse.\n" " " #: plugin.py:362 #, docstring msgid "" "\n" "\n" " Gives the Morse code equivalent of a given string.\n" " " msgstr "" "\n" "\n" " Mostra il codice Morse equivalente alla stringa fornita.\n" " " #: plugin.py:374 #, docstring msgid "" "\n" "\n" " Reverses .\n" " " msgstr "" "\n" "\n" " Riporta in maniera speculare.\n" " " #: plugin.py:391 #, docstring msgid "" "\n" "\n" " Returns with each character randomly colorized.\n" " " msgstr "" "\n" "\n" " Restituisce con ogni carattere colorato in modo casuale.\n" " " #: plugin.py:401 #, docstring msgid "" "\n" "\n" " Returns colorized like a rainbow.\n" " " msgstr "" "\n" "\n" " Restituisce colorato come un arcobaleno.\n" " " #: plugin.py:412 #, docstring msgid "" "\n" "\n" " Returns stripped of all color codes.\n" " " msgstr "" "\n" "\n" " Restituisce privato di tutti i codici colore.\n" " " #: plugin.py:421 #, docstring msgid "" "\n" "\n" " Returns as if an AOLuser had said it.\n" " " msgstr "" "\n" "\n" " Restituisce come pronunciato da un utente AOL.\n" " " #: plugin.py:448 #, docstring msgid "" "\n" "\n" " Returns as if JeffK had said it himself.\n" " " msgstr "" "\n" "\n" " Restituisce come pronunciato da JeffK.\n" " " #: plugin.py:544 msgid "ay" msgstr "a" #: plugin.py:544 msgid "bee" msgstr "bi" #: plugin.py:544 msgid "dee" msgstr "di" #: plugin.py:544 msgid "see" msgstr "ci" #: plugin.py:545 msgid "aych" msgstr "acca" #: plugin.py:545 msgid "ee" msgstr "e" #: plugin.py:545 msgid "eff" msgstr "effe" #: plugin.py:545 msgid "gee" msgstr "gi" #: plugin.py:546 msgid "ell" msgstr "elle" #: plugin.py:546 msgid "eye" msgstr "i" #: plugin.py:546 msgid "jay" msgstr "gei" #: plugin.py:546 msgid "kay" msgstr "cappa" #: plugin.py:547 msgid "cue" msgstr "cu" #: plugin.py:547 msgid "em" msgstr "emme" #: plugin.py:547 msgid "en" msgstr "enne" #: plugin.py:547 msgid "oh" msgstr "o" #: plugin.py:547 msgid "pee" msgstr "pi" #: plugin.py:548 msgid "arr" msgstr "erre" #: plugin.py:548 msgid "ess" msgstr "esse" #: plugin.py:548 msgid "tee" msgstr "ti" #: plugin.py:548 msgid "you" msgstr "u" #: plugin.py:549 msgid "double-you" msgstr "doppia vu" #: plugin.py:549 msgid "ecks" msgstr "ics" #: plugin.py:549 msgid "vee" msgstr "vi" #: plugin.py:549 msgid "why" msgstr "ipsilon" #: plugin.py:550 msgid "zee" msgstr "zeta" #: plugin.py:555 msgid "exclamation point" msgstr "punto esclamativo" #: plugin.py:556 msgid "quote" msgstr "apice doppio" #: plugin.py:557 msgid "pound" msgstr "cancelletto" #: plugin.py:558 msgid "dollar sign" msgstr "dollaro" #: plugin.py:559 msgid "percent" msgstr "percentuale" #: plugin.py:560 msgid "ampersand" msgstr "e commerciale" #: plugin.py:561 msgid "single quote" msgstr "apice singolo" #: plugin.py:562 msgid "left paren" msgstr "parentesi tonda sinistra" #: plugin.py:563 msgid "right paren" msgstr "parentesi tonda destra" #: plugin.py:564 msgid "asterisk" msgstr "asterisco" #: plugin.py:565 msgid "plus" msgstr "più" #: plugin.py:566 msgid "comma" msgstr "virgola" #: plugin.py:567 msgid "minus" msgstr "meno" #: plugin.py:568 msgid "period" msgstr "punto" #: plugin.py:569 msgid "slash" msgstr "slash" #: plugin.py:570 msgid "colon" msgstr "due punti" #: plugin.py:571 msgid "semicolon" msgstr "punto e virgola" #: plugin.py:572 msgid "less than" msgstr "minore" #: plugin.py:573 msgid "equals" msgstr "uguale" #: plugin.py:574 msgid "greater than" msgstr "maggiore" #: plugin.py:575 msgid "question mark" msgstr "punto interrogativo" #: plugin.py:576 msgid "at" msgstr "chiocciola" #: plugin.py:577 msgid "left bracket" msgstr "parentesi quadra sinistra" #: plugin.py:578 msgid "backslash" msgstr "backslash" #: plugin.py:579 msgid "right bracket" msgstr "parentesi quadra destra" #: plugin.py:580 msgid "caret" msgstr "circonflesso" #: plugin.py:581 msgid "underscore" msgstr "trattino basso" #: plugin.py:582 msgid "backtick" msgstr "accento grave" #: plugin.py:583 msgid "left brace" msgstr "parentesi graffa sinistra" #: plugin.py:584 msgid "pipe" msgstr "pipe" #: plugin.py:585 msgid "right brace" msgstr "parentesi graffa destra" #: plugin.py:586 msgid "tilde" msgstr "tilde" #: plugin.py:589 msgid "one" msgstr "uno" #: plugin.py:589 msgid "three" msgstr "tre" #: plugin.py:589 msgid "two" msgstr "due" #: plugin.py:589 msgid "zero" msgstr "zero" #: plugin.py:590 msgid "five" msgstr "cinque" #: plugin.py:590 msgid "four" msgstr "quattro" #: plugin.py:590 msgid "seven" msgstr "sette" #: plugin.py:590 msgid "six" msgstr "sei" #: plugin.py:591 msgid "eight" msgstr "otto" #: plugin.py:591 msgid "nine" msgstr "nove" #: plugin.py:595 #, docstring msgid "" "\n" "\n" " Returns , phonetically spelled out.\n" " " msgstr "" "\n" "\n" " Restituisce con trascrizione fonetica (spelling).\n" " " #: plugin.py:625 #, docstring msgid "" "\n" "\n" " Returns as GNU/RMS would say it.\n" " " msgstr "" "\n" "\n" " Restituisce come pronunciato da GNU/RMS.\n" " " #: plugin.py:634 #, docstring msgid "" "\n" "\n" " Returns with each word longer than\n" " supybot.plugins.Filter.shrink.minimum being shrunken (i.e., like\n" " \"internationalization\" becomes \"i18n\").\n" " " msgstr "" "\n" "\n" " Restituisce con ogni parola più lunga di\n" " supybot.plugins.Filter.shrink.minimum abbreviata\n" " (ad esempio \"internationalization\" diventa \"i18n\").\n" " " #: plugin.py:653 #, docstring msgid "" "\n" "\n" " Returns with the l's made into r's and r's made into l's.\n" " " msgstr "" "\n" "\n" " Restituisce con le elle trasformate in erre e viceversa.\n" " " #: plugin.py:702 #, docstring msgid "" "\n" "\n" " Returns rotated 180 degrees. Only really works for ASCII\n" " printable characters.\n" " " msgstr "" "\n" "\n" " Restituisce ruotato di 180 gradi. Funziona solo con caratteri ASCII stampabili.\n" " " limnoria-2020.03.17/plugins/Filter/plugin.py0000644000175000017500000006405413634634532020106 0ustar valval00000000000000# -*- encoding: utf-8 -*- ### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from __future__ import unicode_literals import re import sys import codecs import string import random import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.utils.minisix as minisix import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Filter') class MyFilterProxy(object): def reply(self, s): self.s = s class Filter(callbacks.Plugin): """This plugin offers several commands which transform text in some way. It also provides the capability of using such commands to 'filter' the output of the bot -- for instance, you could make everything the bot says be in leetspeak, or Morse code, or any number of other kinds of filters. Not very useful, but definitely quite fun :)""" def __init__(self, irc): self.__parent = super(Filter, self) self.__parent.__init__(irc) self.outFilters = ircutils.IrcDict() def outFilter(self, irc, msg): if msg.command in ('PRIVMSG', 'NOTICE'): if msg.channel in self.outFilters: if ircmsgs.isAction(msg): s = ircmsgs.unAction(msg) else: s = msg.args[1] methods = self.outFilters[msg.channel] for filtercommand in methods: myIrc = MyFilterProxy() filtercommand(myIrc, msg, [s]) s = myIrc.s if ircmsgs.isAction(msg): msg = ircmsgs.action(msg.args[0], s, msg=msg) else: msg = ircmsgs.IrcMsg(msg=msg, args=(msg.args[0], s)) return msg _filterCommands = ['jeffk', 'leet', 'rot13', 'hexlify', 'binary', 'scramble', 'morse', 'reverse', 'colorize', 'squish', 'supa1337', 'stripcolor', 'aol', 'rainbow', 'spellit', 'hebrew', 'undup', 'gnu', 'shrink', 'uniud', 'capwords', 'caps', 'vowelrot'] @internationalizeDocstring def outfilter(self, irc, msg, args, channel, command): """[] [] Sets the outFilter of this plugin to be . If no command is given, unsets the outFilter. is only necessary if the message isn't sent in the channel itself. """ if command: if not self.isDisabled(command) and \ command in self._filterCommands: method = getattr(self, command) self.outFilters.setdefault(channel, []).append(method) irc.replySuccess() else: irc.error(_('That\'s not a valid filter command.')) else: self.outFilters[channel] = [] irc.replySuccess() outfilter = wrap(outfilter, [('checkChannelCapability', 'op'), additional('commandName')]) _hebrew_remover = utils.str.MultipleRemover('aeiou') @internationalizeDocstring def hebrew(self, irc, msg, args, text): """ Removes all the vowels from . (If you're curious why this is named 'hebrew' it's because I (jemfinch) thought of it in Hebrew class, and printed Hebrew often elides the vowels.) """ irc.reply(self._hebrew_remover(text)) hebrew = wrap(hebrew, ['text']) def _squish(self, text): return text.replace(' ', '') @internationalizeDocstring def squish(self, irc, msg, args, text): """ Removes all the spaces from . """ irc.reply(self._squish(text)) squish = wrap(squish, ['text']) @internationalizeDocstring def undup(self, irc, msg, args, text): """ Returns , with all consecutive duplicated letters removed. """ L = [text[0]] for c in text: if c != L[-1]: L.append(c) irc.reply(''.join(L)) undup = wrap(undup, ['text']) @internationalizeDocstring def binary(self, irc, msg, args, text): """ Returns the binary representation of . """ L = [] if minisix.PY3: if isinstance(text, str): bytes_ = text.encode() else: bytes_ = text else: if isinstance(text, unicode): text = text.encode() bytes_ = map(ord, text) for i in bytes_: LL = [] assert i<=256 counter = 8 while i: counter -= 1 if i & 1: LL.append('1') else: LL.append('0') i >>= 1 while counter: LL.append('0') counter -= 1 LL.reverse() L.extend(LL) irc.reply(''.join(L)) binary = wrap(binary, ['text']) def unbinary(self, irc, msg, args, text): """ Returns the character representation of binary . Assumes ASCII, 8 digits per character. """ text = self._squish(text) # Strip spaces. try: L = [chr(int(text[i:(i+8)], 2)) for i in range(0, len(text), 8)] irc.reply(''.join(L)) except ValueError: irc.errorInvalid('binary string', text) unbinary = wrap(unbinary, ['text']) _hex_encoder = staticmethod(codecs.getencoder('hex_codec')) def hexlify(self, irc, msg, args, text): """ Returns a hexstring from the given string; a hexstring is a string composed of the hexadecimal value of each character in the string """ irc.reply(self._hex_encoder(text.encode('utf8'))[0].decode('utf8')) hexlify = wrap(hexlify, ['text']) _hex_decoder = staticmethod(codecs.getdecoder('hex_codec')) @internationalizeDocstring def unhexlify(self, irc, msg, args, text): """ Returns the string corresponding to . Obviously, must be a string of hexadecimal digits. """ try: irc.reply(self._hex_decoder(text.encode('utf8'))[0] .decode('utf8', 'replace')) except TypeError: irc.error(_('Invalid input.')) unhexlify = wrap(unhexlify, ['text']) _rot13_encoder = codecs.getencoder('rot-13') @internationalizeDocstring def rot13(self, irc, msg, args, text): """ Rotates 13 characters to the right in the alphabet. Rot13 is commonly used for text that simply needs to be hidden from inadvertent reading by roaming eyes, since it's easily reversible. """ if minisix.PY2: text = text.decode('utf8') irc.reply(self._rot13_encoder(text)[0]) rot13 = wrap(rot13, ['text']) _leettrans = utils.str.MultipleReplacer(dict(list(zip('oOaAeElBTiIts', '004433187!1+5')))) _leetres = [(re.compile(r'\b(?:(?:[yY][o0O][oO0uU])|u)\b'), 'j00'), (re.compile(r'fear'), 'ph33r'), (re.compile(r'[aA][tT][eE]'), '8'), (re.compile(r'[aA][tT]'), '@'), (re.compile(r'[sS]\b'), 'z'), (re.compile(r'x'), '><'),] @internationalizeDocstring def leet(self, irc, msg, args, text): """ Returns the l33tspeak version of """ for (r, sub) in self._leetres: text = re.sub(r, sub, text) text = self._leettrans(text) irc.reply(text) leet = wrap(leet, ['text']) _supaleetreplacers = [('xX', '><'), ('kK', '|<'), ('rR', '|2'), ('hH', '|-|'), ('L', '|_'), ('uU', '|_|'), ('O', '()'), ('nN', '|\\|'), ('mM', '/\\/\\'), ('G', '6'), ('Ss', '$'), ('i', ';'), ('aA', '/-\\'), ('eE', '3'), ('t', '+'), ('T', '7'), ('l', '1'), ('D', '|)'), ('B', '|3'), ('I', ']['), ('Vv', '\\/'), ('wW', '\\/\\/'), ('d', 'c|'), ('b', '|>'), ('c', '<'), ('h', '|n'),] @internationalizeDocstring def supa1337(self, irc, msg, args, text): """ Replies with an especially k-rad translation of . """ for (r, sub) in self._leetres: text = re.sub(r, sub, text) for (letters, replacement) in self._supaleetreplacers: for letter in letters: text = text.replace(letter, replacement) irc.reply(text) supa1337 = wrap(supa1337, ['text']) _scrambleRe = re.compile(r'(?:\b|(?![a-zA-Z]))([a-zA-Z])([a-zA-Z]*)' r'([a-zA-Z])(?:\b|(?![a-zA-Z]))') @internationalizeDocstring def scramble(self, irc, msg, args, text): """ Replies with a string where each word is scrambled; i.e., each internal letter (that is, all letters but the first and last) are shuffled. """ def _subber(m): L = list(m.group(2)) random.shuffle(L) return '%s%s%s' % (m.group(1), ''.join(L), m.group(3)) s = self._scrambleRe.sub(_subber, text) irc.reply(s) scramble = wrap(scramble, ['text']) _morseCode = { "A" : ".-", "B" : "-...", "C" : "-.-.", "D" : "-..", "E" : ".", "F" : "..-.", "G" : "--.", "H" : "....", "I" : "..", "J" : ".---", "K" : "-.-", "L" : ".-..", "M" : "--", "N" : "-.", "O" : "---", "P" : ".--.", "Q" : "--.-", "R" : ".-.", "S" : "...", "T" : "-", "U" : "..-", "V" : "...-", "W" : ".--", "X" : "-..-", "Y" : "-.--", "Z" : "--..", "0" : "-----", "1" : ".----", "2" : "..---", "3" : "...--", "4" : "....-", "5" : ".....", "6" : "-....", "7" : "--...", "8" : "---..", "9" : "----.", "." : ".-.-.-", "," : "--..--", ":" : "---...", "?" : "..--..", "'" : ".----.", "-" : "-....-", "/" : "-..-.", '"' : ".-..-.", "@" : ".--.-.", "=" : "-...-" } _revMorseCode = dict([(y, x) for (x, y) in _morseCode.items()]) _unmorsere = re.compile('([.-]+)') @internationalizeDocstring def unmorse(self, irc, msg, args, text): """ Does the reverse of the morse command. """ text = text.replace('_', '-') def morseToLetter(m): s = m.group(1) return self._revMorseCode.get(s, s) text = self._unmorsere.sub(morseToLetter, text) text = text.replace(' ', '\x00') text = text.replace(' ', '') text = text.replace('\x00', ' ') irc.reply(text) unmorse = wrap(unmorse, ['text']) @internationalizeDocstring def morse(self, irc, msg, args, text): """ Gives the Morse code equivalent of a given string. """ L = [] for c in text.upper(): L.append(self._morseCode.get(c, c)) irc.reply(' '.join(L)) morse = wrap(morse, ['text']) @internationalizeDocstring def reverse(self, irc, msg, args, text): """ Reverses . """ irc.reply(text[::-1]) reverse = wrap(reverse, ['text']) @internationalizeDocstring def _color(self, c, fg=None): if c == ' ': return c if fg is None: fg = random.randint(2, 15) fg = str(fg).zfill(2) return '\x03%s%s' % (fg, c) @internationalizeDocstring def colorize(self, irc, msg, args, text): """ Returns with each character randomly colorized. """ if minisix.PY2: text = text.decode('utf-8') text = ircutils.stripColor(text) L = [self._color(c) for c in text] if minisix.PY2: L = [c.encode('utf-8') for c in L] irc.reply('%s%s' % (''.join(L), '\x03')) colorize = wrap(colorize, ['text']) @internationalizeDocstring def rainbow(self, irc, msg, args, text): """ Returns colorized like a rainbow. """ if minisix.PY2: text = text.decode('utf-8') text = ircutils.stripColor(text) colors = utils.iter.cycle(['05', '04', '07', '08', '09', '03', '11', '10', '12', '02', '06', '13']) L = [self._color(c, fg=next(colors)) for c in text] if minisix.PY2: L = [c.encode('utf-8') for c in L] irc.reply(''.join(L) + '\x03') rainbow = wrap(rainbow, ['text']) @internationalizeDocstring def stripcolor(self, irc, msg, args, text): """ Returns stripped of all color codes. """ irc.reply(ircutils.stripColor(text)) stripcolor = wrap(stripcolor, ['text']) @internationalizeDocstring def aol(self, irc, msg, args, text): """ Returns as if an AOL user had said it. """ text = text.replace(' you ', ' u ') text = text.replace(' are ', ' r ') text = text.replace(' love ', ' <3 ') text = text.replace(' luv ', ' <3 ') text = text.replace(' too ', ' 2 ') text = text.replace(' to ', ' 2 ') text = text.replace(' two ', ' 2 ') text = text.replace('fore', '4') text = text.replace(' for ', ' 4 ') text = text.replace('be', 'b') text = text.replace('four', ' 4 ') text = text.replace(' their ', ' there ') text = text.replace(', ', ' ') text = text.replace(',', ' ') text = text.replace("'", '') text = text.replace('one', '1') smiley = utils.iter.choice(['<3', ':)', ':-)', ':D', ':-D']) text += smiley*3 irc.reply(text) aol = wrap(aol, ['text']) @internationalizeDocstring def jeffk(self, irc, msg, args, text): """ Returns as if JeffK had said it himself. """ def randomlyPick(L): return utils.iter.choice(L) def quoteOrNothing(m): return randomlyPick(['"', '']).join(m.groups()) def randomlyReplace(s, probability=0.5): def f(m): if random.random() < probability: return m.expand(s) else: return m.group(0) return f def randomExclaims(m): if random.random() < 0.85: return ('!' * random.randrange(1, 5)) + m.group(1) else: return '.' + m.group(1) def randomlyShuffle(m): L = list(m.groups()) random.shuffle(L) return ''.join(L) def lessRandomlyShuffle(m): L = list(m.groups()) if random.random() < .4: random.shuffle(L) return ''.join(L) def randomlyLaugh(text, probability=.3): if random.random() < probability: if random.random() < .5: insult = utils.iter.choice([' fagot1', ' fagorts', ' jerks', 'fagot' ' jerk', 'dumbshoes', ' dumbshoe']) else: insult = '' laugh1 = utils.iter.choice(['ha', 'hah', 'lol', 'l0l', 'ahh']) laugh2 = utils.iter.choice(['ha', 'hah', 'lol', 'l0l', 'ahh']) laugh1 = laugh1 * random.randrange(1, 5) laugh2 = laugh2 * random.randrange(1, 5) exclaim = utils.iter.choice(['!', '~', '!~', '~!!~~', '!!~', '~~~!']) exclaim += utils.iter.choice(['!', '~', '!~', '~!!~~', '!!~', '~~~!']) if random.random() < 0.5: exclaim += utils.iter.choice(['!', '~', '!~', '~!!~~', '!!~', '~~~!']) laugh = ''.join([' ', laugh1, laugh2, insult, exclaim]) text += laugh return text if random.random() < .03: irc.reply(randomlyLaugh('NO YUO', probability=1)) return alwaysInsertions = { r'er\b': 'ar', r'\bthe\b': 'teh', r'\byou\b': 'yuo', r'\bis\b': 'si', r'\blike\b': 'liek', r'[^e]ing\b': 'eing', } for (r, s) in alwaysInsertions.items(): text = re.sub(r, s, text) randomInsertions = { r'i': 'ui', r'le\b': 'al', r'i': 'io', r'l': 'll', r'to': 'too', r'that': 'taht', r'[^s]c([ei])': r'sci\1', r'ed\b': r'e', r'\band\b': 'adn', r'\bhere\b': 'hear', r'\bthey\'re': 'their', r'\bthere\b': 'they\'re', r'\btheir\b': 'there', r'[^e]y': 'ey', } for (r, s) in randomInsertions.items(): text = re.sub(r, randomlyReplace(s), text) text = re.sub(r'(\w)\'(\w)', quoteOrNothing, text) text = re.sub(r'\.(\s+|$)', randomExclaims, text) text = re.sub(r'([aeiou])([aeiou])', randomlyShuffle, text) text = re.sub(r'([bcdfghkjlmnpqrstvwxyz])([bcdfghkjlmnpqrstvwxyz])', lessRandomlyShuffle, text) text = randomlyLaugh(text) if random.random() < .4: text = text.upper() irc.reply(text) jeffk = wrap(jeffk, ['text']) # Keeping these separate so people can just replace the alphabets for # whatever their language of choice _spellLetters = { 'a': _('ay'), 'b': _('bee'), 'c': _('see'), 'd': _('dee'), 'e': _('ee'), 'f': _('eff'), 'g': _('gee'), 'h': _('aych'), 'i': _('eye'), 'j': _('jay'), 'k': _('kay'), 'l': _('ell'), 'm': _('em'), 'n': _('en'), 'o': _('oh'), 'p': _('pee'), 'q': _('cue'), 'r': _('arr'), 's': _('ess'), 't': _('tee'), 'u': _('you'), 'v': _('vee'), 'w': _('double-you'), 'x': _('ecks'), 'y': _('why'), 'z': _('zee') } for (k, v) in list(_spellLetters.items()): _spellLetters[k.upper()] = v _spellPunctuation = { '!': _('exclamation point'), '"': _('quote'), '#': _('pound'), '$': _('dollar sign'), '%': _('percent'), '&': _('ampersand'), '\'': _('single quote'), '(': _('left paren'), ')': _('right paren'), '*': _('asterisk'), '+': _('plus'), ',': _('comma'), '-': _('minus'), '.': _('period'), '/': _('slash'), ':': _('colon'), ';': _('semicolon'), '<': _('less than'), '=': _('equals'), '>': _('greater than'), '?': _('question mark'), '@': _('at'), '[': _('left bracket'), '\\': _('backslash'), ']': _('right bracket'), '^': _('caret'), '_': _('underscore'), '`': _('backtick'), '{': _('left brace'), '|': _('pipe'), '}': _('right brace'), '~': _('tilde') } _spellNumbers = { '0': _('zero'), '1': _('one'), '2': _('two'), '3': _('three'), '4': _('four'), '5': _('five'), '6': _('six'), '7': _('seven'), '8': _('eight'), '9': _('nine') } @internationalizeDocstring def spellit(self, irc, msg, args, text): """ Returns , phonetically spelled out. """ d = {} if self.registryValue('spellit.replaceLetters'): d.update(self._spellLetters) if self.registryValue('spellit.replaceNumbers'): d.update(self._spellNumbers) if self.registryValue('spellit.replacePunctuation'): d.update(self._spellPunctuation) # A bug in unicode on OSX prevents me from testing this. ## dd = {} ## for (c, v) in d.items(): ## dd[ord(c)] = unicode(v + ' ') ## irc.reply(unicode(text).translate(dd)) out = minisix.io.StringIO() write = out.write for c in text: try: c = d[c] write(' ') except KeyError: pass write(c) irc.reply(out.getvalue().strip()) spellit = wrap(spellit, ['text']) @internationalizeDocstring def gnu(self, irc, msg, args, text): """ Returns as GNU/RMS would say it. """ irc.reply(' '.join(['GNU/' + s for s in text.split()])) gnu = wrap(gnu, ['text']) @internationalizeDocstring def shrink(self, irc, msg, args, text): """ Returns with each word longer than supybot.plugins.Filter.shrink.minimum being shrunken (i.e., like "internationalization" becomes "i18n"). """ L = [] minimum = self.registryValue('shrink.minimum', msg.channel, irc.network) r = re.compile(r'[A-Za-z]{%s,}' % minimum) def shrink(m): s = m.group(0) return ''.join((s[0], str(len(s)-2), s[-1])) text = r.sub(shrink, text) irc.reply(text) shrink = wrap(shrink, ['text']) # TODO: 2,4,; # XXX suckiest: B,K,P,Q,T # alternatives: 3: U+2107 _uniudMap = { ' ': ' ', '0': '0', '@': '@', '!': '\u00a1', '1': '1', 'A': '\u2200', '"': '\u201e', '2': '\u2681', 'B': 'q', '#': '#', '3': '\u0190', 'C': '\u0186', '$': '$', '4': '\u2683', 'D': '\u15e1', '%': '%', '5': '\u1515', 'E': '\u018e', '&': '\u214b', '6': '9', 'F': '\u2132', "'": '\u0375', '7': 'L', 'G': '\u2141', '(': ')', '8': '8', 'H': 'H', ')': '(', '9': '6', 'I': 'I', '*': '*', ':': ':', 'J': '\u148b', '+': '+', ';': ';', 'K': '\u029e', ',': '\u2018', '<': '>', 'L': '\u2142', '-': '-', '=': '=', 'M': '\u019c', '.': '\u02d9', '>': '<', 'N': 'N', '/': '/', '?': '\u00bf', 'O': 'O', 'P': 'd', '`': '\u02ce', 'p': 'd', 'Q': 'b', 'a': '\u0250', 'q': 'b', 'R': '\u1d1a', 'b': 'q', 'r': '\u0279', 'S': 'S', 'c': '\u0254', 's': 's', 'T': '\u22a5', 'd': 'p', 't': '\u0287', 'U': '\u144e', 'e': '\u01dd', '': 'n', 'V': '\u039b', 'f': '\u214e', 'v': '\u028c', 'W': 'M', 'g': '\u0253', 'w': '\u028d', 'X': 'X', 'h': '\u0265', 'x': 'x', 'Y': '\u2144', 'i': '\u1d09', 'y': '\u028e', 'Z': 'Z', 'j': '\u027f', 'z': 'z', '[': ']', 'k': '\u029e', '{': '}', '\\': '\\', 'l': '\u05df', '|': '|', ']': '[', 'm': '\u026f', '}': '{', '^': '\u2335', 'n': '', '~': '~', '_': '\u203e', 'o': 'o', } @internationalizeDocstring def uniud(self, irc, msg, args, text): """ Returns rotated 180 degrees. Only really works for ASCII printable characters. """ turned = [] tlen = 0 for c in text: if c in self._uniudMap: tmp = self._uniudMap[c] if not len(tmp): tmp = '\ufffd' turned.append(tmp) tlen += 1 elif c == '\t': tablen = 8 - tlen % 8 turned.append(' ' * tablen) tlen += tablen elif ord(c) >= 32: turned.append(c) tlen += 1 s = '%s \x02 \x02' % ''.join(reversed(turned)) irc.reply(s) uniud = wrap(uniud, ['text']) def capwords(self, irc, msg, args, text): """ Capitalises the first letter of each word. """ text = string.capwords(text) irc.reply(text) capwords = wrap(capwords, ['text']) def caps(self, irc, msg, args, text): """ EVERYONE LOVES CAPS LOCK. """ irc.reply(text.upper()) caps = wrap(caps, ['text']) _vowelrottrans = utils.str.MultipleReplacer(dict(list(zip('aeiouAEIOU', 'eiouaEIOUA')))) def vowelrot(self, irc, msg, args, text): """ Returns with vowels rotated """ text = self._vowelrottrans(text) irc.reply(text) vowelrot = wrap(vowelrot, ['text']) Filter = internationalizeDocstring(Filter) Class = Filter # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Filter/test.py0000644000175000017500000001651113634634532017562 0ustar valval00000000000000# -*- coding: utf8 -*- ### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from __future__ import unicode_literals from supybot.test import * import re import codecs import supybot.utils as utils import supybot.callbacks as callbacks from supybot.utils.minisix import u class FilterTest(ChannelPluginTestCase): plugins = ('Filter', 'Utilities', 'Reply') def testNoErrors(self): self.assertNotError('leet foobar') self.assertNotError('supa1337 foobar') self.assertNotError('aol I\'m too legit to quit.') def testDisabledCommandsCannotFilter(self): self.assertNotError('outfilter rot13') self.assertResponse('echo foo', 'sbb') self.assertNotError('outfilter') try: self.assertNotError('disable rot13') self.assertError('outfilter rot13') self.assertNotError('enable rot13') self.assertNotError('outfilter rot13') finally: try: callbacks.Plugin._disabled.remove('rot13') except KeyError: pass def testHebrew(self): self.assertResponse('hebrew The quick brown fox ' 'jumps over the lazy dog.', 'Th qck brwn fx jmps vr th lzy dg.') def testJeffk(self): for i in range(100): self.assertNotError('jeffk the quick brown fox is ghetto') def testSquish(self): self.assertResponse('squish foo bar baz', 'foobarbaz') self.assertResponse('squish "foo bar baz"', 'foobarbaz') def testUndup(self): self.assertResponse('undup foo bar baz quux', 'fo bar baz qux') self.assertResponse('undup aaaaaaaaaa', 'a') def testMorse(self): self.assertResponse('unmorse [morse jemfinch]', 'JEMFINCH') def testReverse(self): for s in map(str, range(1000, 1010)): self.assertResponse('reverse %s' % s, s[::-1]) def testBinary(self): self.assertResponse('binary A', '01000001') def testUnbinary(self): self.assertResponse('unbinary 011011010110111101101111', 'moo') self.assertError('unbinary moo') self.assertResponse('unbinary 01101101 01101111 01101111', 'moo') def testRot13(self): for s in map(str, range(1000, 1010)): self.assertResponse('rot13 [rot13 %s]' % s, s) def testRot13HandlesNonAsciiStuff(self): self.assertNotError('rot13 é') def testHexlifyUnhexlify(self): for s in map(str, range(1000, 1010)): self.assertResponse('unhexlify [hexlify %s]' % s, s) self.assertNotError('unhexlify ff') def testScramble(self): s = 'the recalcitrant jamessan tests his scramble function' self.assertNotRegexp('scramble %s' % s, s) s = 'the recalc1trant jam3ssan tests his scramble fun>, 2011. # msgid "" msgstr "" "Project-Id-Version: Format plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 11:29+EET\n" "PO-Revision-Date: 2014-12-20 11:40+0200\n" "Last-Translator: Mikaela Suomalainen \n" "Language-Team: \n" "Language: fi\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-Generator: Poedit 1.6.10\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: plugin.py:41 msgid "" "Provides some commands for formatting text, such as making text bold or\n" " capitalized." msgstr "" "Tarjoaa komentoja tekstin muotoiluun, vaikkapa tekstin korostamiseen tai\n" " kirjainkoon muuttamiseen." #: plugin.py:45 msgid "" "\n" "\n" " Returns bolded.\n" " " msgstr "" "\n" "\n" " Palautttaa korostettuna.\n" " " #: plugin.py:54 msgid "" "\n" "\n" " Strips bold, underline, and colors from ." msgstr "" "\n" "\n" " Riisuu korostuksen, alleviivauksen ja värit." #: plugin.py:61 msgid "" "\n" "\n" " Returns in reverse-video.\n" " " msgstr "" "\n" "\n" " Palauttaa käänteis-videona..\n" " " #: plugin.py:70 msgid "" "\n" "\n" " Returns underlined.\n" " " msgstr "" "\n" "\n" " Palauttaa alleviivattuna.\n" " " #: plugin.py:79 msgid "" " [] \n" "\n" " Returns with foreground color and background " "color\n" " (if given)\n" " " msgstr "" " [] \n" "\n" " Palauttaa etuala värillä ja tausta värillä\n" " (jos annettu)\n" " " #: plugin.py:89 msgid "" " [ ...]\n" "\n" " Joins all the arguments together with .\n" " " msgstr "" " [ ...]\n" "\n" " Yhdistää kaikki parametrit yhteen .\n" " " #: plugin.py:98 msgid "" " \n" "\n" " Replaces with in\n" " . The first and second arguments must necessarily be the " "same\n" " length.\n" " " msgstr "" " \n" "\n" " Korvaa \n" " . Ensinmäisen ja viimeisen parametrin täytyy olla " "täsmälleen saman\n" " pituisia.\n" " " #: plugin.py:105 msgid "" " must be the same length as ." msgstr "" " täytyy olla saman pituisia, kuin ." #: plugin.py:112 msgid "" " \n" "\n" " Replaces all non-overlapping occurrences of \n" " with in .\n" " " msgstr "" " \n" "\n" " Korvaa kaikki ei-ylilyövät sattumat \n" " .\n" " " #: plugin.py:121 msgid "" "\n" "\n" " Returns uppercased.\n" " " msgstr "" "\n" "\n" " Palauttaa isoilla kirjaimilla.\n" " " #: plugin.py:130 msgid "" "\n" "\n" " Returns lowercased.\n" " " msgstr "" "\n" "\n" " Palauttaa pienillä kirjaimilla.\n" " " #: plugin.py:139 msgid "" "\n" "\n" " Returns capitalized.\n" " " msgstr "" "\n" "\n" " Palauttaa aktivoituna.\n" " " #: plugin.py:148 msgid "" "\n" "\n" " Returns titlecased.\n" " " msgstr "" "\n" "\n" " Palauttaa otsikoituna.\n" " " #: plugin.py:157 msgid "" "\n" "\n" " Returns surrounded by double quotes.\n" " " msgstr "" "\n" "\n" " Palauttaa tekstin kahden lainausmerkin sisällä.\n" " " #: plugin.py:166 msgid "" " \n" "\n" " Concatenates two strings. Do keep in mind that this is *not* the " "same\n" " thing as join \"\", since if contains spaces, they won't " "be\n" " removed by concat.\n" " " msgstr "" " \n" "\n" " Liittää merkkiketjut yhteen. Pidä mielessä että tämä *ei* ole sama\n" " asia kuin liitä \"\", koska jos sisältää " "välilyöntejä, ne eivät tule\n" " liitoksen poistamiksi.\n" " " #: plugin.py:177 msgid "" " \n" "\n" " Cuts down to by chopping off the rightmost characters " "in\n" " excess of . If is a negative number, it chops that " "many\n" " characters off the end of .\n" " " msgstr "" " \n" "\n" " Leikkaa katkaisemalla oikeimmat\n" " pääsyt. Jos on negatiivinen numero, se leikkaa niin " "monta\n" " merkkiä lopusta.\n" " " #: plugin.py:188 msgid "" " \n" "\n" " Returns the th space-separated field of . I.e., if " "text\n" " is \"foo bar baz\" and is 2, \"bar\" is returned.\n" " " msgstr "" " \n" "\n" " Palauttaa :nen välilyönnillä erotetut osan . " "Esim. jos teksti\n" " on \"foo bar baz\" ja on 2, palauttaa \"bar\" :in.\".\n" " " #: plugin.py:201 msgid "" " [ ...]\n" "\n" " Expands a Python-style format string using the remaining args. Just " "be\n" " sure always to use %s, not %d or %f or whatever, because all the " "args\n" " are strings.\n" " " msgstr "" " [ ...]\n" "\n" " Laajentaa Python-tyylistä merkkiketjua käyttämällä jäljellä olevia " "parametrejä. Ole varma, että käytät\n" " aina vain %s:ää, etkä %d:tä tai %f:ää tai mitä tahansa, koska kaikki " "parametrit ovat\n" " merkkiketjuja.\n" " " #: plugin.py:215 msgid "Not enough arguments for the format string." msgstr "Ei tarpeeksi parametrejä formaatti merkkiketjulle." limnoria-2020.03.17/plugins/Format/locales/fr.po0000644000175000017500000001334313634634532020625 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-01-22 13:46+CET\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: Limnoria \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" "Language: fr\n" #: plugin.py:43 msgid "" "\n" "\n" " Returns bolded.\n" " " msgstr "" "\n" "\n" "Retourne le texte, en gras" #: plugin.py:52 msgid "" "\n" "\n" " Strips bold, underline, and colors from ." msgstr "" "\n" "\n" "Retire le le gras, le soulignement, et les couleurs du ." #: plugin.py:59 msgid "" "\n" "\n" " Returns in reverse-video.\n" " " msgstr "" "\n" "\n" "Retourne le texte, inversé" #: plugin.py:68 msgid "" "\n" "\n" " Returns underlined.\n" " " msgstr "" "\n" "\n" "Retourne le texte, souligné" #: plugin.py:77 msgid "" " [] \n" "\n" " Returns with foreground color and background " "color\n" " (if given)\n" " " msgstr "" " [] \n" "\n" "Retourne le avec les couleurs de et de l' (si donné)." #: plugin.py:87 msgid "" " [ ...]\n" "\n" " Joins all the arguments together with .\n" " " msgstr "" " [ ...]\n" "\n" "Joint tous les arguments en utilisant le ." #: plugin.py:96 msgid "" " \n" "\n" " Replaces with in\n" " . The first and second arguments must necessarily be the " "same\n" " length.\n" " " msgstr "" " \n" "\n" "Remplacer des caractères par d'autres. Le premier et le second argument " "doivent obligatoirement être de la même taille." #: plugin.py:103 msgid "" " must be the same length as ." msgstr "" " doit être de la même taille que " #: plugin.py:110 msgid "" " \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:119 msgid "" "\n" "\n" " Returns uppercased.\n" " " msgstr "" "\n" "\n" "Retourne le texte, en majuscules" #: plugin.py:128 msgid "" "\n" "\n" " Returns lowercased.\n" " " msgstr "" "\n" "\n" "Retourne le texte, en minuscules" #: plugin.py:137 msgid "" "\n" "\n" " Returns capitalized.\n" " " msgstr "" "\n" "\n" "Retourne le texte, capitalisé" #: plugin.py:146 msgid "" "\n" "\n" " Returns titlecased.\n" " " msgstr "" "\n" "\n" "Retourne le texte, mis en majuscules de titre." #: plugin.py:155 msgid "" "\n" "\n" " Returns surrounded by double quotes.\n" " " msgstr "" "\n" "\n" "Retourne le texte, entouré de guillemets doubles." #: plugin.py:164 msgid "" " \n" "\n" " Concatenates two strings. Do keep in mind that this is *not* the " "same\n" " thing as join \"\", since if contains spaces, they won't " "be\n" " removed by concat.\n" " " msgstr "" " \n" "\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:175 msgid "" " \n" "\n" " Cuts down to by chopping off the rightmost characters " "in\n" " excess of . If is a negative number, it chops that " "many\n" " characters off the end of .\n" " " msgstr "" " \n" "\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:186 msgid "" " \n" "\n" " Returns the th space-separated field of . I.e., if " "text\n" " is \"foo bar baz\" and is 2, \"bar\" is returned.\n" " " msgstr "" " \n" "\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:199 msgid "" " [ ...]\n" "\n" " Expands a Python-style format string using the remaining args. Just " "be\n" " sure always to use %s, not %d or %f or whatever, because all the " "args\n" " are strings.\n" " " msgstr "" " [ ...]\n" "\n" "Étant une chaine de formattage dans le style de Python avec les arguments " "restants.Assurez-vous seulement de toujours utiliser %s, et pas %d, %f, ou " "quoi que ce soit d’autre, puisque tous les arguments sont des chaines de " "caractères." #: plugin.py:213 msgid "Not enough arguments for the format string." msgstr "Pas assez d'arguments pour formatter la chaîne." limnoria-2020.03.17/plugins/Format/locales/it.po0000644000175000017500000001374013634634532020633 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-29 14:11+0200\n" "Last-Translator: skizzhg \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugin.py:43 #, docstring msgid "" "\n" "\n" " Returns bolded.\n" " " msgstr "" "\n" "\n" " Restituisce in grassetto.\n" " " #: plugin.py:52 #, docstring msgid "" "\n" "\n" " Returns in reverse-video.\n" " " msgstr "" "\n" "\n" " Restituisce in negativo.\n" " " #: plugin.py:61 #, docstring msgid "" "\n" "\n" " Returns underlined.\n" " " msgstr "" "\n" "\n" " Restituisce sottolineato.\n" " " #: plugin.py:70 #, docstring msgid "" " [] \n" "\n" " Returns with foreground color and background color\n" " (if given)\n" " " msgstr "" " [] \n" "\n" " Restituisce con colore di e colore di (se specificato)\n" " " #: plugin.py:80 #, docstring msgid "" " [ ...]\n" "\n" " Joins all the arguments together with .\n" " " msgstr "" " [ ...]\n" "\n" " Unisce tutti gli argomenti con .\n" " " #: plugin.py:89 #, docstring msgid "" " \n" "\n" " Replaces with in\n" " . The first and second arguments must necessarily be the same\n" " length.\n" " " msgstr "" " \n" "\n" " Sostituisce con in .\n" " Il primo e il secondo argomento devono essere obbligatoriamente della stessa lunghezza.\n" " " #: plugin.py:96 msgid " must be the same length as ." msgstr " deve essere della stessa lunghezza di ." #: plugin.py:103 #, docstring msgid "" " \n" "\n" " Replaces all non-overlapping occurrences of \n" " with in .\n" " " msgstr "" " \n" "\n" " Sostituisce tutte le occorrenze di (a condizione\n" " che non entrino in conflitto) con in .\n" " " #: plugin.py:112 #, docstring msgid "" "\n" "\n" " Returns uppercased.\n" " " msgstr "" "\n" "\n" " Restituisce tutto maiuscolo.\n" " " #: plugin.py:121 #, docstring msgid "" "\n" "\n" " Returns lowercased.\n" " " msgstr "" "\n" "\n" " Restituisce minuscolo.\n" " " #: plugin.py:130 #, docstring msgid "" "\n" "\n" " Returns capitalized.\n" " " msgstr "" "\n" "\n" " Restituisce maiuscolo.\n" " " #: plugin.py:139 #, docstring msgid "" "\n" "\n" " Returns titlecased.\n" " " msgstr "" "\n" "\n" " Restituisce con tutte le prime lettere delle parole in maiuscolo.\n" " " #: plugin.py:148 #, docstring msgid "" "\n" "\n" " Returns surrounded by double quotes.\n" " " msgstr "" "\n" "\n" " Restituisce tra virgolette doppie.\n" " " #: plugin.py:157 #, docstring msgid "" " \n" "\n" " Concatenates two strings. Do keep in mind that this is *not* the same\n" " thing as join \"\", since if contains spaces, they won't be\n" " removed by concat.\n" " " msgstr "" " \n" "\n" " Concatena due stringhe. Nota che non è come unire con \"\", dal momento\n" " che se contiene spazi questi non verranno rimossi da concat.\n" " " #: plugin.py:168 #, docstring msgid "" " \n" "\n" " Cuts down to by chopping off the rightmost characters in\n" " excess of . If is a negative number, it chops that many\n" " characters off the end of .\n" " " msgstr "" " \n" "\n" " Taglia a rimuovendo i caratteri in eccesso più a\n" " destra di . Se è un numero negativo, rimuove \n" " i caratteri alla fine di .\n" " " #: plugin.py:179 #, docstring msgid "" " \n" "\n" " Returns the th space-separated field of . I.e., if text\n" " is \"foo bar baz\" and is 2, \"bar\" is returned.\n" " " msgstr "" " \n" "\n" " Riporta i campi (separati da spazi) di . Ovvero se il\n" " testo è \"foo bar baz\" e è 2, verrà mostrato \"bar\".\n" " " #: plugin.py:192 #, docstring msgid "" " [ ...]\n" "\n" " Expands a Python-style format string using the remaining args. Just be\n" " sure always to use %s, not %d or %f or whatever, because all the args\n" " are strings.\n" " " msgstr "" " [ ...]\n" "\n" " Espande un formato di stringa in stile Python utilizzando gli argomenti\n" " restanti. Assicurati di usare sempre %s, non %d o %f o altro, in quanto\n" " tutti gli argomenti sono stringhe.\n" " " #: plugin.py:206 msgid "Not enough arguments for the format string." msgstr "Argomenti non sufficienti per il formato della stringa." limnoria-2020.03.17/plugins/Format/plugin.py0000644000175000017500000001667413634634532020116 0ustar valval00000000000000### # Copyright (c) 2004-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import string import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Format') class Format(callbacks.Plugin): """Provides some commands for formatting text, such as making text bold or capitalized.""" @internationalizeDocstring def bold(self, irc, msg, args, text): """ Returns bolded. """ irc.reply(ircutils.bold(text)) bold = wrap(bold, ['text']) @wrap(['text']) def stripformatting(self, irc, msg, args, text): """ Strips bold, underline, and colors from .""" irc.reply(ircutils.stripFormatting(text)) @internationalizeDocstring def reverse(self, irc, msg, args, text): """ Returns in reverse-video. """ irc.reply(ircutils.reverse(text)) reverse = wrap(reverse, ['text']) @internationalizeDocstring def underline(self, irc, msg, args, text): """ Returns underlined. """ irc.reply(ircutils.underline(text)) underline = wrap(underline, ['text']) @internationalizeDocstring def color(self, irc, msg, args, fg, bg, text): """ [] Returns with foreground color and background color (if given) """ irc.reply(ircutils.mircColor(text, fg=fg, bg=bg)) color = wrap(color, ['color', optional('color'), 'text']) @internationalizeDocstring def join(self, irc, msg, args, sep): """ [ ...] Joins all the arguments together with . """ irc.reply(sep.join(args)) join = wrap(join, ['anything'], allowExtra=True) @internationalizeDocstring def translate(self, irc, msg, args, bad, good, text): """ Replaces with in . The first and second arguments must necessarily be the same length. """ if len(bad) != len(good): irc.error(_(' must be the same length as ' '.'), Raise=True) irc.reply(utils.str.MultipleReplacer(dict(list(zip(bad, good))))(text)) 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): """ Returns uppercased. """ irc.reply(text.upper()) upper = wrap(upper, ['text']) @internationalizeDocstring def lower(self, irc, msg, args, text): """ Returns lowercased. """ irc.reply(text.lower()) lower = wrap(lower, ['text']) @internationalizeDocstring def capitalize(self, irc, msg, args, text): """ Returns capitalized. """ irc.reply(text.capitalize()) capitalize = wrap(capitalize, ['text']) @internationalizeDocstring def title(self, irc, msg, args, text): """ Returns titlecased. """ irc.reply(string.capwords(text, " ")) title = wrap(title, ['text']) @internationalizeDocstring def repr(self, irc, msg, args, text): """ Returns surrounded by double quotes. """ irc.reply(utils.str.dqrepr(text)) repr = wrap(repr, ['text']) @internationalizeDocstring def concat(self, irc, msg, args, first, second): """ Concatenates two strings. Do keep in mind that this is *not* the same thing as join "", since if contains spaces, they won't be removed by concat. """ irc.reply(first+second) concat = wrap(concat, ['something', 'text']) @internationalizeDocstring def cut(self, irc, msg, args, size, text): """ Cuts down to by chopping off the rightmost characters in excess of . If is a negative number, it chops that many characters off the end of . """ irc.reply(text[:size]) cut = wrap(cut, ['int', 'text']) @internationalizeDocstring def field(self, irc, msg, args, index, text): """ Returns the th space-separated field of . I.e., if text is "foo bar baz" and is 2, "bar" is returned. """ try: irc.reply(text.split()[index]) except IndexError: irc.errorInvalid('field') field = wrap(field, ['index', 'text']) @internationalizeDocstring def format(self, irc, msg, args): """ [ ...] Expands a Python-style format string using the remaining args. Just be sure always to use %s, not %d or %f or whatever, because all the args are strings. """ if not args: raise callbacks.ArgumentError s = args.pop(0) try: s %= tuple(args) irc.reply(s) except TypeError as e: self.log.debug(utils.exnToString(e)) irc.error(_('Not enough arguments for the format string.'), Raise=True) Class = Format # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Format/test.py0000644000175000017500000000736513634634532017574 0ustar valval00000000000000### # Copyright (c) 2002-2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * class FormatTestCase(PluginTestCase): plugins = ('Format',) def testBold(self): self.assertResponse('bold foobar', '\x02foobar\x02') def testUnderline(self): self.assertResponse('underline foobar', '\x1ffoobar\x1f') def testReverse(self): self.assertResponse('reverse foobar', '\x16foobar\x16') def testFormat(self): self.assertResponse('format %s foo', 'foo') self.assertResponse('format %s%s foo bar', 'foobar') self.assertResponse('format "%sbaz%s" "foo bar" 1', 'foo barbaz1') self.assertError('format %s foo bar') self.assertError('format %s%s foo') def testJoin(self): self.assertResponse('join + foo bar baz', 'foo+bar+baz') self.assertResponse('join "" foo bar baz', 'foobarbaz') 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') self.assertResponse('upper FOO', 'FOO') def testLower(self): self.assertResponse('lower foo', 'foo') self.assertResponse('lower FOO', 'foo') def testCapitalize(self): self.assertResponse('capitalize foo', 'Foo') self.assertResponse('capitalize foo bar', 'Foo bar') def testTitle(self): self.assertResponse('title foo', 'Foo') self.assertResponse('title foo bar', 'Foo Bar') self.assertResponse('title foo\'s bar', 'Foo\'s Bar') def testRepr(self): self.assertResponse('repr foo bar baz', '"foo bar baz"') def testConcat(self): self.assertResponse('concat foo bar baz', 'foobar baz') def testCut(self): self.assertResponse('cut 5 abcdefgh', 'abcde') self.assertResponse('cut 5 abcd', 'abcd') self.assertResponse('cut -1 abcde', 'abcd') def testField(self): self.assertResponse('field 2 foo bar baz', 'bar') self.assertResponse('field -1 foo bar baz', 'baz') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/GPG/0000755000175000017500000000000013634634547015423 5ustar valval00000000000000limnoria-2020.03.17/plugins/GPG/__init__.py0000644000175000017500000000505113634634532017527 0ustar valval00000000000000### # Copyright (c) 2015, Valentin Lorentz # 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. ### """ GPG: Provides authentication based on GPG keys. """ 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__ = "" __author__ = supybot.authors.progval __maintainer__ = supybot.authors.limnoria_core # 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__ = '' from . import config from . import plugin from importlib import reload from importlib import reload # In case we're being reloaded. reload(config) reload(plugin) # 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/GPG/config.py0000644000175000017500000000565713634634532017251 0ustar valval00000000000000### # Copyright (c) 2015, Valentin Lorentz # 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 try: from supybot.i18n import PluginInternationalization _ = PluginInternationalization('GPG') except: # Placeholder that allows to run the plugin on a bot # without the i18n module _ = 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 themself 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('GPG', True) GPG = conf.registerPlugin('GPG') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(GPG, 'someConfigVariableName', # registry.Boolean(False, _("""Help for someConfigVariableName."""))) conf.registerGroup(GPG, 'auth') conf.registerGroup(GPG.auth, 'sign') conf.registerGlobalValue(GPG.auth.sign, 'enable', registry.Boolean(True, """Determines whether or not users are allowed to use GPG signing for authentication.""")) conf.registerGlobalValue(GPG.auth.sign, 'TokenTimeout', registry.PositiveInteger(60*10, """Determines the lifetime of a GPG signature authentication token (in seconds).""")) # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/GPG/plugin.py0000644000175000017500000002253113634634532017270 0ustar valval00000000000000### # Copyright (c) 2015, Valentin Lorentz # 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 re import sys import time import uuid import functools import supybot.gpg as gpg import supybot.conf as conf import supybot.utils as utils import supybot.ircdb as ircdb from supybot.commands import * import supybot.utils.minisix as minisix import supybot.plugins as plugins import supybot.commands as commands import supybot.ircutils as ircutils import supybot.callbacks as callbacks if minisix.PY3: import http.client as http_client else: import httplib as http_client try: from supybot.i18n import PluginInternationalization _ = PluginInternationalization('GPG') except ImportError: # Placeholder that allows to run the plugin on a bot # without the i18n module _ = lambda x: x def check_gpg_available(f): if gpg.available: return f else: if not gpg.found_gnupg_lib: def newf(self, irc, *args): irc.error(_('gnupg features are not available because ' 'the python-gnupg library is not installed.')) elif not gpg.found_gnupg_bin: def newf(self, irc, *args): irc.error(_('gnupg features are not available because ' 'the gnupg executable is not installed.')) else: # This case should never happen. def newf(self, irc, *args): irc.error(_('gnupg features are not available.')) newf.__doc__ = f.__doc__ newf.__name__ = f.__name__ return newf if hasattr(http_client, '_MAXHEADERS'): safe_getUrl = utils.web.getUrl else: def safe_getUrl(url): try: return commands.process(utils.web.getUrl, url, timeout=10, heap_size=10*1024*1024, pn='GPG') except (commands.ProcessTimeoutError, MemoryError): raise utils.web.Error(_('Page is too big or the server took ' 'too much time to answer the request.')) class GPG(callbacks.Plugin): """Provides authentication based on GPG keys.""" class key(callbacks.Commands): @check_gpg_available def add(self, irc, msg, args, user, keyid, keyserver): """ Add a GPG key to your account.""" if keyid in user.gpgkeys: irc.error(_('This key is already associated with your ' 'account.')) return result = gpg.keyring.recv_keys(keyserver, keyid) reply = format(_('%n imported, %i unchanged, %i not imported.'), (result.imported, _('key')), result.unchanged, result.not_imported, [x['fingerprint'] for x in result.results]) if result.imported == 1: user.gpgkeys.append(keyid) irc.reply(reply) else: irc.error(reply) add = wrap(add, ['user', ('somethingWithoutSpaces', _('You must give a valid key id')), ('somethingWithoutSpaces', _('You must give a valid key server'))]) @check_gpg_available def remove(self, irc, msg, args, user, fingerprint): """ Remove a GPG key from your account.""" try: keyids = [x['keyid'] for x in gpg.keyring.list_keys() if x['fingerprint'] == fingerprint] if len(keyids) == 0: raise ValueError for keyid in keyids: try: user.gpgkeys.remove(keyid) except ValueError: user.gpgkeys.remove('0x' + keyid) gpg.keyring.delete_keys(fingerprint) irc.replySuccess() except ValueError: irc.error(_('GPG key not associated with your account.')) remove = wrap(remove, ['user', 'somethingWithoutSpaces']) @check_gpg_available def list(self, irc, msg, args, user): """takes no arguments List your GPG keys.""" keyids = user.gpgkeys if len(keyids) == 0: irc.reply(_('No key is associated with your account.')) else: irc.reply(format('%L', keyids)) list = wrap(list, ['user']) class signing(callbacks.Commands): def __init__(self, *args): super(GPG.signing, self).__init__(*args) self._tokens = {} def _expire_tokens(self): now = time.time() self._tokens = dict(filter(lambda x_y: x_y[1][1]>now, self._tokens.items())) @check_gpg_available def gettoken(self, irc, msg, args): """takes no arguments Send you a token that you'll have to sign with your key.""" self._expire_tokens() token = '{%s}' % str(uuid.uuid4()) lifetime = conf.supybot.plugins.GPG.auth.sign.TokenTimeout() self._tokens.update({token: (msg.prefix, time.time()+lifetime)}) irc.reply(_('Your token is: %s. Please sign it with your ' 'GPG key, paste it somewhere, and call the \'auth\' ' 'command with the URL to the (raw) file containing the ' 'signature.') % token) gettoken = wrap(gettoken, []) _auth_re = re.compile(r'-----BEGIN PGP SIGNED MESSAGE-----\r?\n' r'Hash: .*\r?\n\r?\n' r'\s*({[0-9a-z-]+})\s*\r?\n' r'-----BEGIN PGP SIGNATURE-----\r?\n.*' r'\r?\n-----END PGP SIGNATURE-----', re.S) @check_gpg_available def auth(self, irc, msg, args, url): """ Check the GPG signature at the and authenticates you if the key used is associated to a user.""" self._expire_tokens() content = safe_getUrl(url) if minisix.PY3 and isinstance(content, bytes): content = content.decode() match = self._auth_re.search(content) if not match: irc.error(_('Signature or token not found.'), Raise=True) data = match.group(0) token = match.group(1) if token not in self._tokens: irc.error(_('Unknown token. It may have expired before you ' 'submit it.'), Raise=True) if self._tokens[token][0] != msg.prefix: irc.error(_('Your hostname/nick changed in the process. ' 'Authentication aborted.'), Raise=True) verified = gpg.keyring.verify(data) if verified and verified.valid: keyid = verified.pubkey_fingerprint[-16:] prefix, expiry = self._tokens.pop(token) found = False for (id, user) in ircdb.users.items(): if keyid in [x[-len(keyid):] for x in user.gpgkeys]: try: user.addAuth(msg.prefix) except ValueError: irc.error(_('Your secure flag is true and your ' 'hostmask doesn\'t match any of your ' 'known hostmasks.'), Raise=True) ircdb.users.setUser(user, flush=False) irc.reply(_('You are now authenticated as %s.') % user.name) return irc.error(_('Unknown GPG key.'), Raise=True) else: irc.error(_('Signature could not be verified. Make sure ' 'this is a valid GPG signature and the URL is valid.')) auth = wrap(auth, ['url']) Class = GPG # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/GPG/test.py0000644000175000017500000001326413634634532016754 0ustar valval00000000000000### # Copyright (c) 2015, Valentin Lorentz # 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 * import supybot.utils.minisix as minisix import supybot.gpg as gpg PRIVATE_KEY = """ -----BEGIN PGP PRIVATE KEY BLOCK----- Version: GnuPG v1.4.12 (GNU/Linux) lQHYBFD7GxQBBACeu7bj/wgnnv5NkfHImZJVJLaq2cwKYc3rErv7pqLXpxXZbDOI jP+5eSmTLhPUK67aRD6gG0wQ9iAhYR03weOmyjDGh0eF7kLYhu/4Il56Y/YbB8ll Imz/pep/Hi72ShcW8AtifDup/KeHjaWa1yF2WThHbX/0N2ghSxbJnatpBwARAQAB AAP6Arf7le7FD3ZhGZvIBkPr25qca6i0Qxb5XpOinV7jLcoycZriJ9Xofmhda9UO xhNVppMvs/ofI/m0umnR4GLKtRKnJSc8Edxi4YKyqLehfBTF20R/kBYPZ772FkNW Kzo5yCpP1jpOc0+QqBuU7OmrG4QhQzTLXIUgw4XheORncEECAMGkvR47PslJqzbY VRIzWEv297r1Jxqy6qgcuCJn3RWYJbEZ/qdTYy+MgHGmaNFQ7yhfIzkBueq0RWZp Z4PfJn8CANHZGj6AJZcvb+VclNtc5VNfnKjYD+qQOh2IS8NhE/0umGMKz3frH1TH yCbh2LlPR89cqNcd4QvbHKA/UmzISXkB/37MbUnxXTpS9Y4HNpQCh/6SYlB0lucV QN0cgjfhd6nBrb6uO6+u40nBzgynWcEpPMNfN0AtQeA4Dx+WrnK6kZqfd7QMU3Vw eWJvdCB0ZXN0iLgEEwECACIFAlD7GxQCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4B AheAAAoJEMnTMjwgrwErV3AD/0kRq8UWPlkc6nyiIR6qiT3EoBNHKIi4cz68Wa1u F2M6einrRR0HolrxonynTGsdr1u2f3egOS4fNfGhTNAowSefYR9q5kIYiYE2DL5G YnjJKNfmnRxZM9YqmEnN50rgu2cifSRehp61fXdTtmOAR3js+9wb73dwbYzr3kIc 3WH1 =UBcd -----END PGP PRIVATE KEY BLOCK----- """ WRONG_TOKEN_SIGNATURE = """ -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 {a95dc112-780e-47f7-a83a-c6f3820d7dc3} -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.12 (GNU/Linux) iJwEAQECAAYFAlD7Jb0ACgkQydMyPCCvASv9HgQAhQf/oFMWcKwGncH0hjXC3QYz 7ck3chgL3S1pPAvS69viz6i2bwYZYD8fhzHNJ/qtw/rx6thO6PwT4SpdhKerap+I kdem3LjM4fAGHRunHZYP39obNKMn1xv+f26mEAAWxdv/W/BLAFqxi3RijJywRkXm zo5GUl844kpnV+uk0Xk= =z2Cz -----END PGP SIGNATURE----- """ FINGERPRINT = '2CF3E41500218D30F0B654F5C9D3323C20AF012B' class GPGTestCase(PluginTestCase): plugins = ('GPG', 'User') def setUp(self): super(GPGTestCase, self).setUp() gpg.loadKeyring() if gpg.available and network: def testGpgAddRemove(self): self.assertNotError('register foo bar') self.assertError('gpg key add 51E516F0B0C5CE6A pgp.mit.edu') self.assertResponse('gpg key add EB17F1E0CEB63930 pgp.mit.edu', '1 key imported, 0 unchanged, 0 not imported.') self.assertNotError( 'gpg key remove F88ECDE235846FA8652DAF5FEB17F1E0CEB63930') self.assertResponse('gpg key add EB17F1E0CEB63930 pgp.mit.edu', '1 key imported, 0 unchanged, 0 not imported.') self.assertResponse('gpg key add EB17F1E0CEB63930 pgp.mit.edu', 'Error: This key is already associated with your account.') if gpg.available: def testGpgAuth(self): self.assertNotError('register spam egg') gpg.keyring.import_keys(PRIVATE_KEY).__dict__ (id, user) = list(ircdb.users.items())[0] user.gpgkeys.append(FINGERPRINT) msg = self.getMsg('gpg signing gettoken').args[-1] match = re.search('is: ({.*}).', msg) assert match, repr(msg) token = match.group(1) def fakeGetUrlFd(*args, **kwargs): fd.geturl = lambda :None return fd (utils.web.getUrlFd, realGetUrlFd) = (fakeGetUrlFd, utils.web.getUrlFd) fd = minisix.io.StringIO() fd.write('foo') fd.seek(0) self.assertResponse('gpg signing auth http://foo.bar/baz.gpg', 'Error: Signature or token not found.') fd = minisix.io.StringIO() fd.write(token) fd.seek(0) self.assertResponse('gpg signing auth http://foo.bar/baz.gpg', 'Error: Signature or token not found.') fd = minisix.io.StringIO() fd.write(WRONG_TOKEN_SIGNATURE) fd.seek(0) self.assertRegexp('gpg signing auth http://foo.bar/baz.gpg', 'Error: Unknown token.*') fd = minisix.io.StringIO() fd.write(str(gpg.keyring.sign(token))) fd.seek(0) self.assertResponse('gpg signing auth http://foo.bar/baz.gpg', 'You are now authenticated as spam.') utils.web.getUrlFd = realGetUrlFd # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Games/0000755000175000017500000000000013634634547016042 5ustar valval00000000000000limnoria-2020.03.17/plugins/Games/__init__.py0000644000175000017500000000466313634634532020156 0ustar valval00000000000000### # Copyright (c) 2003-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Provides various game-related commands. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Games/config.py0000644000175000017500000000433513634634532017660 0ustar valval00000000000000### # Copyright (c) 2003-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Games') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Games', True) Games = conf.registerPlugin('Games') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Games/locales/0000755000175000017500000000000013634634547017464 5ustar valval00000000000000limnoria-2020.03.17/plugins/Games/locales/de.po0000644000175000017500000001072313634634532020411 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-11-10 23:19+0100\n" "Last-Translator: Florian Besser \n" "Language-Team: DE \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: German\n" #: plugin.py:46 msgid "" "takes no arguments\n" "\n" " Flips a coin and returns the result.\n" " " msgstr "" "hat keine Argumente\n" "\n" "Wirft eine Münze und gibt das Ergebnis aus." #: plugin.py:51 msgid "heads" msgstr "Kopf" #: plugin.py:53 msgid "tails" msgstr "Zahl" #: plugin.py:58 msgid "" "d\n" "\n" " Rolls a die with number of sides times.\n" " For example, 2d6 will roll 2 six-sided dice; 10d10 will roll 10\n" " ten-sided dice.\n" " " msgstr "" "d\n" "\n" "Wirft einen Würfel mit Anzahl der Seiten, mal. z.B. 2d6 wirft 2 Würfel mit 6 Seiten; 10d10 wirft 10 mal einen 10 seitigen Würfel." #: plugin.py:66 msgid "You can't roll more than 1000 dice." msgstr "Du kannst nicht mehr wie 1000 Würfel werfen." #: plugin.py:68 msgid "Dice can't have more than 100 sides." msgstr "Würfel kann nicht mehr als 100 Seiten haben." #: plugin.py:70 msgid "Dice can't have fewer than 3 sides." msgstr "Würfel können nicht weniger wie 3 Seiten haben." #: plugin.py:78 msgid "Dice must be of the form d" msgstr "Würfel muss mit d angegeben werden" #: plugin.py:82 msgid "It is possible.|Yes!|Of course.|Naturally.|Obviously.|It shall be.|The outlook is good.|It is so.|One would be wise to think so.|The answer is certainly yes." msgstr "Es ist möglich.| Ja!|Natürlich.|Natürlicher.|Ist doch klar.|So soll es sein.|Es sieht gut aus.|So ist es.| Es wäre gut so zu denken.|Die Antwort ist sicherlich ja." #: plugin.py:86 msgid "In your dreams.|I doubt it very much.|No chance.|The outlook is poor.|Unlikely.|About as likely as pigs flying.|You're kidding, right?|NO!|NO.|No.|The answer is a resounding no." msgstr "In deinen Träumen.|Ich zweifel das stark an.| Keine Chance.|Es sieht schlecht aus.|Unwahrscheinlich.|So wahrscheinlich wie fliegende Schweine.| Du machst Witze, oder?|NEIN!|NEIN.|Nein.| Die Antwort ist ein klares Nein." #: plugin.py:90 msgid "Maybe...|No clue.|_I_ don't know.|The outlook is hazy, please ask again later.|What are you asking me for?|Come again?|You know the answer better than I.|The answer is def-- oooh! shiny thing!" msgstr "Vielleicht...| Keine Ahnung.| Ich weiß nicht.|Die Aussicht ist unklar, frag später nochmal.|Nach was fragst du mich?|Kommst du wieder?|Du weißt die Antwort besser als ich.|Die Antwort ist defin---- OHH! Da glänzt was!" #: plugin.py:107 msgid "" "[]\n" "\n" " Ask a question and the answer shall be provided.\n" " " msgstr "" "[]\n" "\n" "Frage deine Frage und dir wird eine Antwort gegeben." #: plugin.py:121 msgid "" "[spin]\n" "\n" " Fires the revolver. If the bullet was in the chamber, you're dead.\n" " Tell me to spin the chambers and I will.\n" " " msgstr "" "[spin]\n" "\n" "Feuert den Revolver. Falls eine Patrone in der Kammer war, bist du tod. Sag mir das ich die Kammern drehen soll, dann werde ich das tun." #: plugin.py:128 msgid "*SPIN* Are you feeling lucky?" msgstr "*DREHE* Denkst du du hast Glück?" #: plugin.py:137 msgid "*BANG* Hey, who put a blank in here?!" msgstr "*BANG* Hey, wer hat hier eine leere Patrone reingetan?!" #: plugin.py:139 msgid "reloads and spins the chambers." msgstr "läd nach und dreht die Kammer." #: plugin.py:141 msgid "*click*" msgstr "*klick*" #: plugin.py:148 msgid "" "[]\n" "\n" " Returns the number of consecutive lines you've sent in \n" " without being interrupted by someone else (i.e. how long your current\n" " 'monologue' is). is only necessary if the message isn't sent\n" " in the channel itself.\n" " " msgstr "" "[]\n" "\n" "Gibt die Anzahl der aufeinderfolgenen Zeilen an, die du im gesendet hast, ohne von jemand anderem unterbrochen worden zu sein (z.B. wie lang dein momentaner Monolog ist). ist nur nötig falls die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:167 msgid "Your current monologue is at least %n long." msgstr "Dein momentaner Monolog ist mindestens %n lang." #: plugin.py:168 msgid "line" msgstr "Zeile" limnoria-2020.03.17/plugins/Games/locales/fi.po0000644000175000017500000001223113634634532020413 0ustar valval00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen , 2011. # msgid "" msgstr "" "Project-Id-Version: Games plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 11:29+EET\n" "PO-Revision-Date: 2014-12-20 11:36+0200\n" "Last-Translator: Mikaela Suomalainen \n" "Language-Team: \n" "Language: fi\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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.10\n" #: plugin.py:45 msgid "" "This plugin provides some small games like (Russian) roulette,\n" " eightball, monologue, coin and dice." msgstr "" "Tämä plugini sisältää joitakin pieniä pelejä, kuten (venäläisen) ruletin,\n" " kasipalllon, monologin, kolikon heittäminen ja nopan heittäminen." #: plugin.py:49 msgid "" "takes no arguments\n" "\n" " Flips a coin and returns the result.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Heittää rahan ja palauttaa tuloksen.\n" " " #: plugin.py:54 msgid "heads" msgstr "kruuna" #: plugin.py:56 msgid "tails" msgstr "klaava" #: plugin.py:61 msgid "" "d\n" "\n" " Rolls a die with number of sides times.\n" " For example, 2d6 will roll 2 six-sided dice; 10d10 will roll 10\n" " ten-sided dice.\n" " " msgstr "" "d\n" "\n" " Heittää noppaa sivujen lukumäärä kertaa.\n" " Esimerkiksi, 2d6 heittää 2 kuusisivuista noppaa; 10d10 heittää 10\n" " kymmenen-sivuista noppaa.\n" " " #: plugin.py:69 msgid "You can't roll more than 1000 dice." msgstr "Et voi heittää useampaa kuin tuhatta noppaa." #: plugin.py:71 msgid "Dice can't have more than 100 sides." msgstr "Nopalla ei voi olla useampaa kuin sataa sivua." #: plugin.py:73 msgid "Dice can't have fewer than 3 sides." msgstr "Nopalla ei voi olla vähempää kuin kolmea sivua." #: plugin.py:81 msgid "Dice must be of the form d" msgstr "Nopan täytyy olla muodossa d." #: plugin.py:85 msgid "" "It is possible.|Yes!|Of course.|Naturally.|Obviously.|It shall be.|The " "outlook is good.|It is so.|One would be wise to think so.|The answer is " "certainly yes." msgstr "" "Se on mahdollista.|Kyllä!|Tietysti.|Luonnollisesti.|Ilmeisesti.|Olkoon niin.|" "Hyvät näkymät.|Se on niin.|Erään olisi viisasta ajatella niin.|Vastaus on " "varmasti kyllä." #: plugin.py:89 msgid "" "In your dreams.|I doubt it very much.|No chance.|The outlook is poor.|" "Unlikely.|About as likely as pigs flying.|You're kidding, right?|NO!|NO.|No.|" "The answer is a resounding no." msgstr "" "Unissasi.|Minä epäilen sitä kovasti.|Ei mahdollista.|Näkymät ovat huonot.|" "Epätodennäköisesti.|Yhtä todennäköisesti, kuin siat lentävät.|Kai sinä " "pilailet?|EI!|EI.|Ei.|Vastaus on raikuva ei." #: plugin.py:93 msgid "" "Maybe...|No clue.|_I_ don't know.|The outlook is hazy, please ask again " "later.|What are you asking me for?|Come again?|You know the answer better " "than I.|The answer is def-- oooh! shiny thing!" msgstr "" "Ehkäpä...|Ei tietoa.|_Minä_ en tiedä.|Näkymä on sotkuinen, ole hyvä ja kysy " "myöhemmin uudelleen.|Miksi kysyt minulta?|Tule uudelleen?|Sinä tiedät " "vastauksen paremmin kuin minä.|Vastaus on var-- oooh! kiiltävä esine!" #: plugin.py:110 msgid "" "[]\n" "\n" " Ask a question and the answer shall be provided.\n" " " msgstr "" "[]\n" "\n" " Kysy kysymys ja vastaus annetaan.\n" " " #: plugin.py:124 msgid "" "[spin]\n" "\n" " Fires the revolver. If the bullet was in the chamber, you're dead.\n" " Tell me to spin the chambers and I will.\n" " " msgstr "" "[spin]\n" "\n" " Ampuu revolverillä. Jos luoti oli kammiossa, olet kuollut.\n" " Käske minun pyöräyttää kammiota ja minä teen sen.\n" " " #: plugin.py:131 msgid "*SPIN* Are you feeling lucky?" msgstr "*Pyörähdys* Tuntuuko sinusta onnekkaalta?" #: plugin.py:141 msgid "*BANG* Hey, who put a blank in here?!" msgstr "*PANG* Hei, kuka laittoi tuon laudan tuohon?!" #: plugin.py:143 msgid "reloads and spins the chambers." msgstr "lataa ja pyöräyttää kammioita." #: plugin.py:145 msgid "*click*" msgstr "*klick*" #: plugin.py:152 msgid "" "[]\n" "\n" " Returns the number of consecutive lines you've sent in \n" " without being interrupted by someone else (i.e. how long your " "current\n" " 'monologue' is). is only necessary if the message isn't " "sent\n" " in the channel itself.\n" " " msgstr "" "[]\n" "\n" " Palauttaa jatkuvan määrän rivejä, jotka olet lähettänyt \n" " tulematta kenenkään muun keskeyttämäksi (esim. kuinka pitkä " "nykyinen\n" " 'monologisi' on). on vaadittu vain jos viestiä ei lähetetä " "kanavalla\n" " itsellään.\n" " " #: plugin.py:171 msgid "Your current monologue is at least %n long." msgstr "Sinun nykyinen monologisi on ainakin %n pitkä." #: plugin.py:172 msgid "line" msgstr "rivi" limnoria-2020.03.17/plugins/Games/locales/fr.po0000644000175000017500000001122313634634532020424 0ustar valval00000000000000# French translations for PACKAGE package # Traductions françaises du paquet PACKAGE. # Copyright (C) 2010 ORGANIZATION # ProgVal , 2010. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: 2010-10-17 13:16+CEST\n" "PO-Revision-Date: 2010-12-23 19:55+0100\n" "Last-Translator: Valentin Lorentz \n" "Language-Team: French\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" "Language: fr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: plugin.py:46 msgid "" "takes no arguments\n" "\n" " Flips a coin and returns the result.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Lance une pièce et retourne le résultat." #: plugin.py:51 msgid "heads" msgstr "face" #: plugin.py:53 msgid "tails" msgstr "pile" #: plugin.py:58 msgid "" "d\n" "\n" " Rolls a die with number of sides times.\n" " For example, 2d6 will roll 2 six-sided dice; 10d10 will roll 10\n" " ten-sided dice.\n" " " msgstr "" "d\n" "\n" "Fait un certain nombre de d'un dé d'un certain nombre de . Par exemple, 2d6 lancera deux dés à six faces, et 10d10 dix dés à dix faces." #: plugin.py:66 msgid "You can't roll more than 1000 dice." msgstr "Vous ne pouvez lancer plus de 1000 dés." #: plugin.py:68 msgid "Dice can't have more than 100 sides." msgstr "Vous ne pouvez pas avoir plus de 100 faces." #: plugin.py:70 msgid "Dice can't have fewer than 3 sides." msgstr "Vous ne pouvez pas avoir moins de trois faces." #: plugin.py:78 msgid "Dice must be of the form d" msgstr "Les dés doivent être de la forme d" #: plugin.py:82 msgid "It is possible.|Yes!|Of course.|Naturally.|Obviously.|It shall be.|The outlook is good.|It is so.|One would be wise to think so.|The answer is certainly yes." msgstr "C'est possible.|Oui !|Bien sûr.|Naturellement.|Évidemment.|Ce doit être ça.|Ce n'est pas impossible.|C'est le cas.|C'est ce que l'on peut penser.|La réponse est certainement oui." #: plugin.py:86 msgid "In your dreams.|I doubt it very much.|No chance.|The outlook is poor.|Unlikely.|About as likely as pigs flying.|You're kidding, right?|NO!|NO.|No.|The answer is a resounding no." msgstr "Dans tes rêves.|J'en doute.|Impossible.|Ça m'étonnerait|Improbable.|Quand les poules auront des dents|La semaine des quatre jeudis.|Tu rigoles ?|NON !|NON.|Non.|La réponse semble être non." #: plugin.py:90 msgid "Maybe...|No clue.|_I_ don't know.|The outlook is hazy, please ask again later.|What are you asking me for?|Come again?|You know the answer better than I.|The answer is def-- oooh! shiny thing!" msgstr "Peut-être...|Aucune idée|*Je* ne sais pas|Je n'en sais rien, veuillez réessayer.|Qu'est-ce que vous me demander ?|Revenez plus tard.|Vous connaissez la réponse mieux que moi.|La réponse est... oooh ! un truc qui brille !" #: plugin.py:107 msgid "" "[]\n" "\n" " Ask a question and the answer shall be provided.\n" " " msgstr "" "[]\n" "\n" "Posez une question, et la réponse devrait vous être donnée." #: plugin.py:121 msgid "" "[spin]\n" "\n" " Fires the revolver. If the bullet was in the chamber, you're dead.\n" " Tell me to spin the chambers and I will.\n" " " msgstr "" "[spin]\n" "\n" "Tire avec le revolver. Si la balle était dans la chambre, vous être mort. Dites-moi de recharger les chambres (en indiquant 'spin') et je le ferais." #: plugin.py:128 msgid "*SPIN* Are you feeling lucky?" msgstr "*FAIT TOURNER LE BARILLET* Prêt à mettre votre chance à l'épreuve ?" #: plugin.py:137 msgid "*BANG* Hey, who put a blank in here?!" msgstr "*BANG* Eh, qui a fait un trou ici ?" #: plugin.py:139 msgid "reloads and spins the chambers." msgstr "recharge et fait tourner les chambres" #: plugin.py:141 msgid "*click*" msgstr "*clic*" #: plugin.py:148 msgid "" "[]\n" "\n" " Returns the number of consecutive lines you've sent in \n" " without being interrupted by someone else (i.e. how long your current\n" " 'monologue' is). is only necessary if the message isn't sent\n" " in the channel itself.\n" " " msgstr "" "[]\n" "\n" "Retourne le nombre de lignes consécutives que vous avez écrites sur le sans être interrompu par qui que ce soit. n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:167 msgid "Your current monologue is at least %n long." msgstr "Votre monologue actuel est au moins long de %n." #: plugin.py:168 msgid "line" msgstr "ligne" limnoria-2020.03.17/plugins/Games/locales/it.po0000644000175000017500000001074613634634532020442 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-11-20 21:27+0100\n" "Last-Translator: skizzhg \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugin.py:46 #, docstring msgid "" "takes no arguments\n" "\n" " Flips a coin and returns the result.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Lancia una moneta e restituisce il risultato.\n" " " #: plugin.py:51 msgid "heads" msgstr "testa" #: plugin.py:53 msgid "tails" msgstr "croce" #: plugin.py:58 #, docstring msgid "" "d\n" "\n" " Rolls a die with number of sides times.\n" " For example, 2d6 will roll 2 six-sided dice; 10d10 will roll 10\n" " ten-sided dice.\n" " " msgstr "" "d\n" "\n" " Tira un certo numero di dadi con un certo numero di facce.\n" " Ad esempio, 2d6 tirerà due dadi da sei facce; 10d10 dieci dadi da dieci facce.\n" " " #: plugin.py:66 msgid "You can't roll more than 1000 dice." msgstr "Non è possibile tirare più di 1000 dadi." #: plugin.py:68 msgid "Dice can't have more than 100 sides." msgstr "I dadi non possono avere più di 100 facce." #: plugin.py:70 msgid "Dice can't have fewer than 3 sides." msgstr "I dadi non possono avere meno di tre facce." #: plugin.py:78 msgid "Dice must be of the form d" msgstr "I dadi vanno espressi nella forma d" #: plugin.py:82 msgid "It is possible.|Yes!|Of course.|Naturally.|Obviously.|It shall be.|The outlook is good.|It is so.|One would be wise to think so.|The answer is certainly yes." msgstr "È possibile.|Sì!|Certamente.|Naturalmente.|Ovviamente.|Deve essere così.|La prospettiva è promettente.|È così.|Sarebbe saggio pensarlo.|La risposta è certamente sì." #: plugin.py:86 msgid "In your dreams.|I doubt it very much.|No chance.|The outlook is poor.|Unlikely.|About as likely as pigs flying.|You're kidding, right?|NO!|NO.|No.|The answer is a resounding no." msgstr "Nei tuoi sogni.|Ne dubito fortemente.|Nessuna possibilità.|La prospettiva è scarsa.|Improbabile.|Quando gli asini voleranno.|Stai scherzando, vero?|NO!|NO.|No.|La risposta è un clamoroso no." #: plugin.py:90 msgid "Maybe...|No clue.|_I_ don't know.|The outlook is hazy, please ask again later.|What are you asking me for?|Come again?|You know the answer better than I.|The answer is def-- oooh! shiny thing!" msgstr "Forse...|Non ne ho idea.|Non lo so.|La prospettiva è confusa, riprova più tardi.|Cosa mi stai chiedendo?|Torna più tardi.|Conosci la risposta meglio di me.|La risposta è... oooh! brillante!" #: plugin.py:107 #, docstring msgid "" "[]\n" "\n" " Ask a question and the answer shall be provided.\n" " " msgstr "" "[]\n" "\n" " Poni una domanda e otterrai una risposta.\n" " " #: plugin.py:121 #, docstring msgid "" "[spin]\n" "\n" " Fires the revolver. If the bullet was in the chamber, you're dead.\n" " Tell me to spin the chambers and I will.\n" " " msgstr "" "[spin]\n" "\n" " Spara con il revolver. Se il proiettile era in camera, sei morto.\n" " Dimmi di girare il tamburo (aggiungendo \"spin\") e lo farò.\n" " " #: plugin.py:128 msgid "*SPIN* Are you feeling lucky?" msgstr "*GIRA* Pronto a mettere alla prova la fortuna?" #: plugin.py:137 msgid "*BANG* Hey, who put a blank in here?!" msgstr "*BANG* Hei, chi ha messo una cartuccia a salve qui?!" #: plugin.py:139 msgid "reloads and spins the chambers." msgstr "ricarica e gira il tamburo." #: plugin.py:141 msgid "*click*" msgstr "*clic*" #: plugin.py:148 #, docstring msgid "" "[]\n" "\n" " Returns the number of consecutive lines you've sent in \n" " without being interrupted by someone else (i.e. how long your current\n" " 'monologue' is). is only necessary if the message isn't sent\n" " in the channel itself.\n" " " msgstr "" "[]\n" "\n" " Restituisce il numero di righe consecutive inviate in senza essere\n" " stati interrotti da qualcun altro. è necessario solo se il messaggio\n" " non viene inviato nel canale stesso.\n" " " #: plugin.py:167 msgid "Your current monologue is at least %n long." msgstr "Il tuo attuale monologo è lungo almeno %n." #: plugin.py:168 msgid "line" msgstr "riga" limnoria-2020.03.17/plugins/Games/plugin.py0000644000175000017500000001604413634634532017711 0ustar valval00000000000000### # Copyright (c) 2003-2005, Jeremiah Fincher # Copyright (c) 2010, James McCoy # 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 re import random import supybot.utils as utils from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Games') class Games(callbacks.Plugin): """This plugin provides some small games like (Russian) roulette, eightball, monologue, coin and dice.""" @internationalizeDocstring def coin(self, irc, msg, args): """takes no arguments Flips a coin and returns the result. """ if random.randrange(0, 2): irc.reply(_('heads')) else: irc.reply(_('tails')) coin = wrap(coin) @internationalizeDocstring def dice(self, irc, msg, args, m): """d Rolls a die with number of sides times. For example, 2d6 will roll 2 six-sided dice; 10d10 will roll 10 ten-sided dice. """ (dice, sides) = list(map(int, m.groups())) if dice > 1000: irc.error(_('You can\'t roll more than 1000 dice.')) elif sides > 100: irc.error(_('Dice can\'t have more than 100 sides.')) elif sides < 3: irc.error(_('Dice can\'t have fewer than 3 sides.')) else: L = [0] * dice for i in range(dice): L[i] = random.randrange(1, sides+1) irc.reply(format('%L', [str(x) for x in L])) _dicere = re.compile(r'^(\d+)d(\d+)$') dice = wrap(dice, [('matches', _dicere, _('Dice must be of the form d'))]) # The list of words and algorithm are pulled straight the mozbot # MagicEightBall.bm module: http://tinyurl.com/7ytg7 _positive = _('It is possible.|Yes!|Of course.|Naturally.|Obviously.|' 'It shall be.|The outlook is good.|It is so.|' 'One would be wise to think so.|' 'The answer is certainly yes.') _negative = _('In your dreams.|I doubt it very much.|No chance.|' 'The outlook is poor.|Unlikely.|' 'About as likely as pigs flying.|You\'re kidding, right?|' 'NO!|NO.|No.|The answer is a resounding no.') _unknown = _('Maybe...|No clue.|_I_ don\'t know.|' 'The outlook is hazy, please ask again later.|' 'What are you asking me for?|Come again?|' 'You know the answer better than I.|' 'The answer is def-- oooh! shiny thing!') def _checkTheBall(self, questionLength): if questionLength % 3 == 0: catalog = self._positive elif questionLength % 3 == 1: catalog = self._negative else: catalog = self._unknown return utils.iter.choice(catalog.split('|')) @internationalizeDocstring def eightball(self, irc, msg, args, text): """[] Ask a question and the answer shall be provided. """ if text: irc.reply(self._checkTheBall(len(text))) else: irc.reply(self._checkTheBall(random.randint(0, 2))) eightball = wrap(eightball, [additional('text')]) _rouletteChamber = random.randrange(0, 6) _rouletteBullet = random.randrange(0, 6) @internationalizeDocstring def roulette(self, irc, msg, args, spin): """[spin] Fires the revolver. If the bullet was in the chamber, you're dead. Tell me to spin the chambers and I will. """ if spin: self._rouletteBullet = random.randrange(0, 6) irc.reply(_('*SPIN* Are you feeling lucky?'), prefixNick=False) return channel = msg.channel if self._rouletteChamber == self._rouletteBullet: self._rouletteBullet = random.randrange(0, 6) self._rouletteChamber = random.randrange(0, 6) if irc.nick in irc.state.channels[channel].ops or \ irc.nick in irc.state.channels[channel].halfops: irc.queueMsg(ircmsgs.kick(channel, msg.nick, 'BANG!')) else: irc.reply(_('*BANG* Hey, who put a blank in here?!'), prefixNick=False) irc.reply(_('reloads and spins the chambers.'), action=True) else: irc.reply(_('*click*')) self._rouletteChamber += 1 self._rouletteChamber %= 6 roulette = wrap(roulette, ['public', additional(('literal', 'spin'))]) @internationalizeDocstring def monologue(self, irc, msg, args, channel): """[] Returns the number of consecutive lines you've sent in without being interrupted by someone else (i.e. how long your current 'monologue' is). is only necessary if the message isn't sent in the channel itself. """ i = 0 for m in reversed(irc.state.history): if m.command != 'PRIVMSG': continue if not m.prefix: continue if not ircutils.strEqual(m.args[0], channel): continue if msg.prefix == m.prefix: i += 1 else: break irc.reply(format(_('Your current monologue is at least %n long.'), (i, _('line')))) monologue = wrap(monologue, ['channel']) Class = Games # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Games/test.py0000644000175000017500000000532013634634532017365 0ustar valval00000000000000### # Copyright (c) 2003-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * class GamesTestCase(ChannelPluginTestCase): plugins = ('Games',) _nonKickRe = re.compile(r'bang|click|spin', re.I) def testRoulette(self): self.irc.feedMsg(ircmsgs.op(self.channel, self.irc.nick)) sawKick = False for i in range(100): m = self.getMsg('roulette', frm='someoneElse!foo@bar') if m.command == 'PRIVMSG': self.assertTrue(self._nonKickRe.search(m.args[1]), 'Got a msg without bang|click|spin: %r' % m) elif m.command == 'KICK': sawKick = True self.assertTrue('bang' in m.args[2].lower(), 'Got a KICK without bang in it.') else: self.fail('Got something other than a kick or a privmsg.') self.assertTrue(sawKick, 'Didn\'t get a kick in %s iterations!' % i) def testEightball(self): self.assertNotError('eightball') self.assertNotError('eightball a') self.assertNotError('eightball ab') self.assertNotError('eightball abc') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Google/0000755000175000017500000000000013634634547016222 5ustar valval00000000000000limnoria-2020.03.17/plugins/Google/__init__.py0000644000175000017500000000463713634634532020337 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Accesses Google for various things. """ 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__ = "" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Google/config.py0000644000175000017500000001552613634634532020044 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # Copyright (c) 2008-2010, James McCoy # 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 from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Google') def configure(advanced): from supybot.questions import output, yn conf.registerPlugin('Google', True) output(_("""The Google plugin has the functionality to watch for URLs that match a specific pattern. (We call this a snarfer) When supybot sees such a URL, it will parse the web page for information and reply with the results.""")) if yn(_('Do you want the Google search snarfer enabled by default?')): conf.supybot.plugins.Google.searchSnarfer.setValue(True) class Language(registry.OnlySomeStrings): transLangs = {'Afrikaans': 'af', 'Albanian': 'sq', 'Amharic': 'am', 'Arabic': 'ar', 'Armenian': 'hy', 'Azerbaijani': 'az', 'Basque': 'eu', 'Belarusian': 'be', 'Bengali': 'bn', 'Bulgarian': 'bg', 'Burmese': 'my', 'Catalan': 'ca', 'Chinese': 'zh', 'Chinese_simplified': 'zh-CN', 'Chinese_traditional': 'zh-TW', 'Croatian': 'hr', 'Czech': 'cs', 'Danish': 'da', 'Dhivehi': 'dv', 'Dutch': 'nl', 'English': 'en', 'Esperanto': 'eo', 'Estonian': 'et', 'Filipino': 'tl', 'Finnish': 'fi', 'French': 'fr', 'Galician': 'gl', 'Georgian': 'ka', 'German': 'de', 'Greek': 'el', 'Gujarati': 'gu', 'Hebrew': 'iw', 'Hindi': 'hi', 'Hungarian': 'hu', 'Icelandic': 'is', 'Indonesian': 'id', 'Inuktitut': 'iu', 'Italian': 'it', 'Japanese': 'ja', 'Kannada': 'kn', 'Kazakh': 'kk', 'Khmer': 'km', 'Korean': 'ko', 'Kurdish': 'ku', 'Kyrgyz': 'ky', 'Laothian': 'lo', 'Latvian': 'lv', 'Lithuanian': 'lt', 'Macedonian': 'mk', 'Malay': 'ms', 'Malayalam': 'ml', 'Maltese': 'mt', 'Marathi': 'mr', 'Mongolian': 'mn', 'Nepali': 'ne', 'Norwegian': 'no', 'Oriya': 'or', 'Pashto': 'ps', 'Persian': 'fa', 'Polish': 'pl', 'Portuguese': 'pt-PT', 'Punjabi': 'pa', 'Romanian': 'ro', 'Russian': 'ru', 'Sanskrit': 'sa', 'Serbian': 'sr', 'Sindhi': 'sd', 'Sinhalese': 'si', 'Slovak': 'sk', 'Slovenian': 'sl', 'Spanish': 'es', 'Swedish': 'sv', 'Tajik': 'tg', 'Tamil': 'ta', 'Tagalog': 'tl', 'Telugu': 'te', 'Thai': 'th', 'Tibetan': 'bo', 'Turkish': 'tr', 'Ukranian': 'uk', 'Urdu': 'ur', 'Uzbek': 'uz', 'Uighur': 'ug', 'Vietnamese': 'vi', 'Detect language': 'auto'} validStrings = ['lang_' + s for s in transLangs.values()] validStrings.append('') def normalize(self, s): if s and not s.startswith('lang_'): s = 'lang_' + s if not s.endswith('CN') or s.endswith('TW') or s.endswith('PT'): s = s.lower() else: s = s.lower()[:-2] + s[-2:] return s class NumSearchResults(registry.PositiveInteger): """Value must be 1 <= n <= 8""" def setValue(self, v): if v > 8: self.error() super(self.__class__, self).setValue(v) class SafeSearch(registry.OnlySomeStrings): validStrings = ['active', 'moderate', 'off'] Google = conf.registerPlugin('Google') conf.registerGlobalValue(Google, 'referer', registry.String('', _("""Determines the URL that will be sent to Google for the Referer field of the search requests. If this value is empty, a Referer will be generated in the following format: http://$server/$botName"""))) conf.registerChannelValue(Google, 'baseUrl', registry.String('google.com', _("""Determines the base URL used for requests."""))) conf.registerChannelValue(Google, 'searchSnarfer', registry.Boolean(False, _("""Determines whether the search snarfer is enabled. If so, messages (even unaddressed ones) beginning with the word 'google' will result in the first URL Google returns being sent to the channel."""))) conf.registerChannelValue(Google, 'colorfulFilter', registry.Boolean(False, _("""Determines whether the word 'google' in the bot's output will be made colorful (like Google's logo)."""))) conf.registerChannelValue(Google, 'bold', registry.Boolean(True, _("""Determines whether results are bolded."""))) conf.registerChannelValue(Google, 'oneToOne', registry.Boolean(False, _("""Determines whether results are sent in different lines or all in the same one."""))) conf.registerChannelValue(Google, 'maximumResults', NumSearchResults(3, _("""Determines the maximum number of results returned from the google command."""))) conf.registerChannelValue(Google, 'defaultLanguage', Language('lang_'+ _('en'), _("""Determines what default language is used in searches. If left empty, no specific language will be requested."""))) conf.registerChannelValue(Google, 'searchFilter', SafeSearch('moderate', _("""Determines what level of search filtering to use by default. 'active' - most filtering, 'moderate' - default filtering, 'off' - no filtering"""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Google/locales/0000755000175000017500000000000013634634547017644 5ustar valval00000000000000limnoria-2020.03.17/plugins/Google/locales/es.po0000644000175000017500000002052113634634532020605 0ustar valval00000000000000# Spanish translation for Limnoria # Copyright (c) 2015 Limnoria 2015 # This file is distributed under the same license as the Limnoria package. # Aaron Farias , 2015. # msgid "" msgstr "" "Project-Id-Version: limnoria\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2014-12-24 15:42+0000\n" "PO-Revision-Date: 2015-01-01 18:06+0000\n" "Last-Translator: Aaron Farias \n" "Language-Team: Spanish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2015-01-01 18:08+0000\n" "X-Generator: Launchpad (build 17286)\n" #: config.py:39 msgid "" "The Google plugin has the functionality to watch for URLs\n" " that match a specific pattern. (We call this a snarfer)\n" " When supybot sees such a URL, it will parse the web page\n" " for information and reply with the results." msgstr "" "El plugin de Google tiene la funcionalidad de ver para las direcciones URL\n" "que coinciden con un patrón específico. (A esto le llamamos una Snarfer)\n" "Cuando Supybot ve un enlace, será analizar la página web\n" "de información y respuesta con los resultados." #: config.py:43 msgid "Do you want the Google search snarfer enabled by default?" msgstr "¿Quieres que la búsqueda Snarfer Google activado por defecto?" #: config.py:89 #, docstring msgid "Value must be 1 <= n <= 8" msgstr "El valor debe ser 1 <= n <= 8" #: 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" " Referer will be generated in the following format:\n" " http://$server/$botName" msgstr "" "Determina la dirección URL que se enviará a Google para\n" "el campo de referencia de las solicitudes de búsqueda. Si este valor está " "vacío, una\n" "Árbitro será generado en el siguiente formato:\n" "http: // servidor $ / $ botName" #: config.py:105 msgid "" "Determines the base URL used for\n" " requests." msgstr "" "Determina la URL base que se utiliza para\n" "peticiones." #: config.py:108 msgid "" "Determines whether the search snarfer is\n" " enabled. If so, messages (even unaddressed ones) beginning with the " "word\n" " 'google' will result in the first URL Google returns being sent to the\n" " channel." msgstr "" "Determina si la búsqueda es Snarfer\n" "habilitado. Si es así, los mensajes (incluso sin dirección) comienza con la " "palabra\n" "'Google' dará lugar a la primera URL Google devuelve está enviando al\n" "canal." #: config.py:113 msgid "" "Determines whether the word 'google' in the\n" " bot's output will be made colorful (like Google's logo)." msgstr "" "Determina si la palabra \"Google\" en el\n" "salida del bot se hará colorido (como el logotipo de Google)." #: config.py:116 msgid "Determines whether results are bolded." msgstr "Determina si los resultados están en negrita." #: config.py:118 msgid "" "Determines whether results are sent in\n" " different lines or all in the same one." msgstr "" "Determina si los resultados se envían en\n" "diferentes líneas o todos en el mismo." #: config.py:121 msgid "" "Determines the maximum number of results returned\n" " from the google command." msgstr "" "Determina el número máximo de resultados devueltos\n" "desde el comando google." #: config.py:124 msgid "" "Determines what default language is used in\n" " searches. If left empty, no specific language will be requested." msgstr "" "Determina el idioma por defecto se utiliza en\n" "búsquedas. Si se deja vacío, se solicitará ningún lenguaje específico." #: config.py:124 msgid "en" msgstr "en" #: config.py:127 msgid "" "Determines what level of search filtering to use\n" " by default. 'active' - most filtering, 'moderate' - default filtering,\n" " 'off' - no filtering" msgstr "" "Determina el nivel de filtrado de búsqueda qué usar\n" "de forma predeterminada. \"Activo\" - más filtrado, \"moderado\" - Filtrado " "por defecto,\n" "\"Off\" - sin filtrado" #: plugin.py:52 #, docstring msgid "" "This is a simple plugin to provide access to the Google services we\n" " all know and love from our favorite IRC bot." msgstr "" "Este es un sencillo plugin para proporcionar acceso a los servicios que " "Google\n" "todos conocemos y amamos de nuestro bot de IRC favorito." #: plugin.py:86 #, docstring msgid "" "Perform a search using Google's AJAX API.\n" " search(\"search phrase\", options={})\n" "\n" " Valid options are:\n" " smallsearch - True/False (Default: False)\n" " filter - {active,moderate,off} (Default: \"moderate\")\n" " language - Restrict search to documents in the given language\n" " (Default: \"lang_en\")\n" " " msgstr "" "Realice una búsqueda usando API AJAX de Google.\n" "Búsqueda (\"frase de búsqueda\", las opciones = {})\n" "\n" "Las opciones válidas son:\n" "smallsearch - True / False (predeterminado: False)\n" "Filtro - {activa, moderada off} (por defecto: \"moderado\")\n" "idioma - Restringir la búsqueda a los documentos en el idioma que tenga\n" "(Por defecto: \"lang_en\")\n" " " #: plugin.py:125 plugin.py:191 msgid "We broke The Google!" msgstr "Rompimos El Google!" #: plugin.py:150 msgid "No matches found." msgstr "No se encontró ninguna coincidencia." #: plugin.py:158 #, docstring msgid "" "[--snippet] \n" "\n" " Does a google search, but only returns the first result.\n" " If option --snippet is given, returns also the page text snippet.\n" " " msgstr "" "[--snippet] \n" "\n" "¿Tiene una búsqueda en Google, pero sólo devuelve el primer resultado.\n" "Si se da la opción --snippet, devuelve también el fragmento de texto de la " "página.\n" " " #: plugin.py:175 msgid "Google found nothing." msgstr "Google no encontró nada." #: plugin.py:180 #, docstring msgid "" " [--{filter,language} ]\n" "\n" " Searches google.com for the given string. As many results as can " "fit\n" " are included. --language accepts a language abbreviation; --filter\n" " accepts a filtering level ('active', 'moderate', 'off').\n" " " msgstr "" " [- {filtro, idioma} ]\n" "\n" "Busca en google.com para la cadena dada. Como muchos resultados como la " "capacidad es\n" "están incluidos. --language acepta una abreviatura del idioma; --filter\n" "acepta un nivel de filtrado (\"activa\", \"moderado\", \"off\").\n" " " #: plugin.py:208 #, docstring msgid "" "\n" "\n" " Returns a link to the cached version of if it is available.\n" " " msgstr "" "\n" "\n" "Devuelve un enlace a la versión en caché de si está disponible.\n" " " #: plugin.py:219 msgid "Google seems to have no cache for that site." msgstr "Google parece no tener memoria caché para ese sitio." #: plugin.py:224 #, docstring msgid "" " [ ...]\n" "\n" " Returns the results of each search, in order, from greatest number\n" " of results to least.\n" " " msgstr "" " [ ...]\n" "\n" "Devuelve los resultados de cada búsqueda, en orden, de mayor número\n" "de los resultados a menos.\n" " " #: plugin.py:247 #, docstring msgid "" " [to] \n" "\n" " Returns translated from into .\n" " " msgstr "" " [a] \n" "\n" "Devuelve traducidos de a .\n" " " #: plugin.py:285 #, docstring msgid "^google\\s+(.*)$" msgstr "^google\\s+(.*)$" #: plugin.py:306 #, docstring msgid "" "\n" "\n" " Uses Google's calculator to calculate the value of .\n" " " msgstr "" "\n" "\n" "Utiliza la calculadora de Google para calcular el valor de .\n" " " #: plugin.py:341 #, docstring msgid "" "\n" "\n" " Looks up on Google.\n" " " msgstr "" "\n" "\n" "Mirar en Google.\n" " " #: plugin.py:358 msgid "Google's phonebook didn't come up with anything." msgstr "Guía de teléfonos de Google no llegó a nada." limnoria-2020.03.17/plugins/Google/locales/fi.po0000644000175000017500000002060613634634532020600 0ustar valval00000000000000# Google plugin in Limnoria. # Copyright (C) 2012 Limnoria # Mikaela Suomalainen , 2011, 2012. # msgid "" msgstr "" "Project-Id-Version: Google plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 14:04+EET\n" "PO-Revision-Date: 2014-12-20 14:10+0200\n" "Last-Translator: Mikaela Suomalainen \n" "Language-Team: \n" "Language: fi\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-Generator: Poedit 1.6.10\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: config.py:39 msgid "" "The Google plugin has the functionality to watch for URLs\n" " that match a specific pattern. (We call this a snarfer)\n" " When supybot sees such a URL, it will parse the web page\n" " for information and reply with the results." msgstr "" "Google lisäosalla on toiminnallisuus vahtia URL-osoitteita, jotka\n" " täsmäävät tiettyyn kuvioon. (Me kutsumme tätä kaappaajaksi)\n" " Kun supybot näkee sellaisen URL-osoitteen, se jäsentää verkkosivun\n" " tiedot ja vastaa tuloksilla." #: config.py:43 msgid "Do you want the Google search snarfer enabled by default?" msgstr "Tahdotko Google haku kaappaajan olevan oletuksenä käytössä?" #: config.py:89 msgid "Value must be 1 <= n <= 8" msgstr "Arvon täytyy olla 1 <= n <= 8" #: 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" " Referer will be generated in the following format:\n" " http://$server/$botName" msgstr "" "Määrittää URLin, joka lähetetään Googlelle\n" " hakupyyntöjen Referer kenttään. Jos tämä arvo on tyhjä, \n" " Referer luodaan seuraavassa muodossa:\n" " http://$palvelin/$botinNimi" #: config.py:105 msgid "" "Determines the base URL used for\n" " requests." msgstr "" "Märittää perus URL-osoitteen, jota käytetään\n" " hakupyynnöille." #: config.py:108 msgid "" "Determines whether the search snarfer is\n" " enabled. If so, messages (even unaddressed ones) beginning with the " "word\n" " 'google' will result in the first URL Google returns being sent to the\n" " channel." msgstr "" "Määrittää onko haku kaappaaja\n" " käytössä. Jos niin, viestit (jopa botille osoittamattomat), jotka " "alkavat sanalla\n" " 'google' tuovat ensinmäisen URLin jonka Google haku palauttaa\n" " kanavalle." #: config.py:113 msgid "" "Determines whether the word 'google' in the\n" " bot's output will be made colorful (like Google's logo)." msgstr "" "Määrittää tehdäänkö sana 'google'\n" " botin ulostulossa värikkääksi (kuten Googlen logo)." #: config.py:116 msgid "Determines whether results are bolded." msgstr "Määrittää korostetaanko viestit." #: config.py:118 msgid "" "Determines whether results are sent in\n" " different lines or all in the same one." msgstr "Määrittää lähetetäänkö tulokset eri vai samoilla riveillä." #: config.py:121 msgid "" "Determines the maximum number of results returned\n" " from the google command." msgstr "" "Määrittää maksimi numeron tuloksia, jotka palaavat\n" " Google komennolla." #: config.py:124 msgid "" "Determines what default language is used in\n" " searches. If left empty, no specific language will be requested." msgstr "" "Määrittää mitä oletuskieltä käytetään hauissa.\n" " jos jätetty tyhjäksi, yhtään tiettyä kieltä ei pyydetä." #: config.py:124 msgid "en" msgstr "en" #: config.py:127 msgid "" "Determines what level of search filtering to use\n" " by default. 'active' - most filtering, 'moderate' - default filtering,\n" " 'off' - no filtering" msgstr "" "Määrittää minkälaista suodatusta käytetään\n" " oletuksena. 'active' - eniten suodatusta, 'moderate' - oletus " "suodatus,\n" " 'off' - ei suodatusta." #: plugin.py:52 msgid "" "This is a simple plugin to provide access to the Google services we\n" " all know and love from our favorite IRC bot." msgstr "" "Tämä on yksinkertainen plugini tarjoamaan pääsyn Googlen palveluihin, jotka " "me kaikki tunnemme, ja joita me kaikki\n" " rakastamme suosikki IRC-botistamme." #: plugin.py:86 msgid "" "Perform a search using Google's AJAX API.\n" " search(\"search phrase\", options={})\n" "\n" " Valid options are:\n" " smallsearch - True/False (Default: False)\n" " filter - {active,moderate,off} (Default: \"moderate\")\n" " language - Restrict search to documents in the given language\n" " (Default: \"lang_en\")\n" " " msgstr "" "Tee jaku käyttämällä Googlen AJAX APIa.\n" " haky(\"hakusana\", asetukset={})\n" "\n" " Kelvolliset asetukset ovat:\n" " smallsearch - True/False (Oletus: False)\n" " filter - {active,moderate,off} (Oletus: \"moderate\")\n" " language - Rajoita haku documentteihin annetulla kielellä.\n" " (Oletus: \"lang_en\")\n" " " #: plugin.py:125 plugin.py:191 msgid "We broke The Google!" msgstr "Me hajotimme Googlen!" #: plugin.py:150 msgid "No matches found." msgstr "Osumia ei löytynyt." #: plugin.py:158 msgid "" "[--snippet] \n" "\n" " Does a google search, but only returns the first result.\n" " If option --snippet is given, returns also the page text snippet.\n" " " msgstr "" "[--snippet] \n" "\n" " Tekee google haun, mutta palauttaa vain ensinmäisen tuloksen.\n" " Jos --snippet on annettu, palauttaa myös pätkän sivun tekstistä.\n" " " #: plugin.py:175 msgid "Google found nothing." msgstr "Google ei löytänyt mitään." #: plugin.py:180 msgid "" " [--{filter,language} ]\n" "\n" " Searches google.com for the given string. As many results as can " "fit\n" " are included. --language accepts a language abbreviation; --filter\n" " accepts a filtering level ('active', 'moderate', 'off').\n" " " msgstr "" " [--{suodatin,kieli} ]\n" "\n" " Hakee google.com :ista annetulla merkkiketjulla. Niin monta " "tulosta, kuin mahtuu\n" " sisällytetään. --language hyväksyy kieli lyhenteen; --filter\n" " hyväksyy suodatus tason ('active', 'moderate', 'off').\n" " " #: plugin.py:208 msgid "" "\n" "\n" " Returns a link to the cached version of if it is available.\n" " " msgstr "" "\n" "\n" " Palauttaa linkin välimuistissa olevaan versioon, jos se on " "saatavilla.\n" " " #: plugin.py:219 msgid "Google seems to have no cache for that site." msgstr "Googlella ei näytä olevan välimuistia tuolle sivulle." #: plugin.py:224 msgid "" " [ ...]\n" "\n" " Returns the results of each search, in order, from greatest number\n" " of results to least.\n" " " msgstr "" " [ ...]\n" "\n" " Palauttaa tulokset jokaiselle haulle järjestyksessä suurimmasta " "numerosta\n" " pienimpään.\n" " " #: plugin.py:247 msgid "" " [to] \n" "\n" " Returns translated from into .\n" " " msgstr "" " [to] \n" "\n" " Kääntää .\n" " " #: plugin.py:285 msgid "^google\\s+(.*)$" msgstr "^google\\s+(.*)$" #: plugin.py:306 msgid "" "\n" "\n" " Uses Google's calculator to calculate the value of .\n" " " msgstr "" "\n" "\n" " Käyttää Googlen laskinta laskeakseen arvon.\n" " " #: plugin.py:341 msgid "" "\n" "\n" " Looks up on Google.\n" " " msgstr "" "\n" "\n" " Etsii Googlesta.\n" " " #: plugin.py:358 msgid "Google's phonebook didn't come up with anything." msgstr "Googlen puhelinluettelo ei keksinyt mitään." #~ msgid "Google says: Error: %s." #~ msgstr "Google sanoo: Virhe: %s." #~ msgid "from language" #~ msgstr "kielestä" #~ msgid "Valid languages are: %L" #~ msgstr "Kelvolliset kielet ovat: %L" #~ msgid "to language" #~ msgstr "kielelle" #~ msgid "Google's calculator didn't come up with anything." #~ msgstr "Googlen laskin ei keksinyt mitään." limnoria-2020.03.17/plugins/Google/locales/fr.po0000644000175000017500000001747113634634532020617 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-01-21 22:36+CET\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: Limnoria \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" "Language: fr\n" #: config.py:39 msgid "" "The Google plugin has the functionality to watch for URLs\n" " that match a specific pattern. (We call this a snarfer)\n" " When supybot sees such a URL, it will parse the web page\n" " for information and reply with the results." msgstr "" "Le plugin Google sert à lire des URLs qui correspondent à un masque " "spécifique (nous appelons ça un 'snarfer'). Lorsque Supybot voit une telle " "URL, il parse la page web pour trouver des informations et répond avec les " "résultats." #: config.py:43 msgid "Do you want the Google search snarfer enabled by default?" msgstr "Voulez-vous activer le snarfer de recherche Google par défaut ?" #: config.py:89 msgid "Value must be 1 <= n <= 8" msgstr "La valeur doit être comprise entre 1 et 8 (inclus)" #: 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" " Referer will be generated in the following format:\n" " http://$server/$botName" msgstr "" "Détermine l'URL qui sera envoyée à Google comme Referer pour les recerches. " "Si la valeur est vide, un Refere sera automatiquement généré dans le " "format : http://$server/$botNamr" #: config.py:105 msgid "" "Determines the base URL used for\n" " requests." msgstr "Détermine l’URL de base utilisée pour les requêtes." #: config.py:108 msgid "" "Determines whether the search snarfer is\n" " enabled. If so, messages (even unaddressed ones) beginning with the " "word\n" " 'google' will result in the first URL Google returns being sent to the\n" " channel." msgstr "" "Détermine si le snarger de recherche est activé. Si c'est le cas, les " "messages (même non adressés) commençant par 'google' seront répondus par la " "première URL que Google donne pour cette recherche." #: config.py:113 msgid "" "Determines whether the word 'google' in the\n" " bot's output will be made colorful (like Google's logo)." msgstr "" "Détermine si le mot 'google' dans la sortie du bot sera coloré (comme le " "logo de Google)" #: config.py:116 msgid "Determines whether results are bolded." msgstr "Détermine si les résultats sont mis en gras." #: config.py:118 msgid "" "Determines whether results are sent in\n" " different lines or all in the same one." msgstr "" "Détermine si les résultats sont envoyés sur différentes lignes, ou tous sur " "la même." #: config.py:121 msgid "" "Determines the maximum number of results returned\n" " from the google command." msgstr "" "Détermine le nombre maximum de résultats retournés par la commande google." #: config.py:124 msgid "" "Determines what default language is used in\n" " searches. If left empty, no specific language will be requested." msgstr "" "Détermine quand langue par défaut est utilisée dans les recherches. Si " "laissé vide, aucune langue spécifique ne sera demandée." #: config.py:124 msgid "en" msgstr "fr" #: config.py:127 msgid "" "Determines what level of search filtering to use\n" " by default. 'active' - most filtering, 'moderate' - default filtering,\n" " 'off' - no filtering" msgstr "" "Détermine le niveau de filtrage à utiliser par défaut. 'active' filtre tout, " "'moderate' est le filtre par défaut, et 'off' désactive le filtrage." #: plugin.py:84 msgid "" "Perform a search using Google's AJAX API.\n" " search(\"search phrase\", options={})\n" "\n" " Valid options are:\n" " smallsearch - True/False (Default: False)\n" " filter - {active,moderate,off} (Default: \"moderate\")\n" " language - Restrict search to documents in the given language\n" " (Default: \"lang_en\")\n" " " msgstr "" "Perform a search using Google's AJAX API.\n" " search(\"search phrase\", options={})\n" "\n" " Valid options are:\n" " smallsearch - True/False (Default: False)\n" " filter - {active,moderate,off} (Default: \"moderate\")\n" " language - Restrict search to documents in the given language\n" " (Default: \"lang_en\")\n" " " #: plugin.py:123 plugin.py:189 msgid "We broke The Google!" msgstr "Google est toukassay !" #: plugin.py:148 msgid "No matches found." msgstr "Aucune correspondance." #: plugin.py:156 msgid "" "[--snippet] \n" "\n" " Does a google search, but only returns the first result.\n" " If option --snippet is given, returns also the page text snippet.\n" " " msgstr "" "[--snippet] \n" "\n" "Effectue une recherche google, mais ne retourne que le premier résultat. Si " "l'option --snippet est donnée, retournera également un fragment de la page." #: plugin.py:173 msgid "Google found nothing." msgstr "Google n'a rien trouvé." #: plugin.py:178 msgid "" " [--{filter,language} ]\n" "\n" " Searches google.com for the given string. As many results as can " "fit\n" " are included. --language accepts a language abbreviation; --filter\n" " accepts a filtering level ('active', 'moderate', 'off').\n" " " msgstr "" " [--filter ] [--language ]\n" "\n" "Rercherche la chaîne donnée sur Google. Autant de résultats que possible " "sont donnés. --language accepte une abbréviation de langue ; --filter " "accepte un niveau de filtrage ('active', 'moderate', 'off')." #: plugin.py:206 msgid "" "\n" "\n" " Returns a link to the cached version of if it is available.\n" " " msgstr "" "\n" "\n" "Retourne un lien vers la version en cache de l', si elle est disponible." #: plugin.py:217 msgid "Google seems to have no cache for that site." msgstr "Google semble ne pas avoir de cache pour ce site." #: plugin.py:222 msgid "" " [ ...]\n" "\n" " Returns the results of each search, in order, from greatest number\n" " of results to least.\n" " " msgstr "" " [ ...]\n" "\n" "Retourne les résultats de chaque recherche, dans l'ordre, par ordre " "croissant du nombre de résultats." #: plugin.py:245 msgid "" " [to] \n" "\n" " Returns translated from into .\n" " " msgstr "" " [to] \n" "\n" "Retourne le , traduire de la vers la ." #: plugin.py:283 msgid "^google\\s+(.*)$" msgstr "^google\\s+(.*)$" #: plugin.py:310 msgid "" "\n" "\n" " Uses Google's calculator to calculate the value of .\n" " " msgstr "" "\n" "\n" "Utilise la calculatrice Google pour calculer la valeur de l'." #: plugin.py:345 msgid "" "\n" "\n" " Looks up on Google.\n" " " msgstr "" "\n" "\n" "Recherche le sur Google." #: plugin.py:362 msgid "Google's phonebook didn't come up with anything." msgstr "L'annuaire téléphonique de Google ne donne aucun résultat." #~ msgid "Google says: Error: %s." #~ msgstr "Google dit : Erreur : %s." #~ msgid "from language" #~ msgstr "de la langue" #~ msgid "Valid languages are: %L" #~ msgstr "Les langues valides sont : %L" #~ msgid "to language" #~ msgstr "vers la langue" #~ msgid "Google's calculator didn't come up with anything." #~ msgstr "La calculatrice Google ne donne aucun résultat." limnoria-2020.03.17/plugins/Google/locales/it.po0000644000175000017500000001754113634634532020622 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2012-03-15 20:55+0100\n" "Last-Translator: skizzhg \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:39 msgid "" "The Google plugin has the functionality to watch for URLs\n" " that match a specific pattern. (We call this a snarfer)\n" " When supybot sees such a URL, it will parse the web page\n" " for information and reply with the results." msgstr "" "Il plugin Google ha la funzione di leggere gli URL che corrispondono ad\n" " un modello specifico (viene chiamato snarfer).\n" " Quando supybot vede un URL, analizza la pagina web per\n" " ottenere informazioni e rispondere con dei risultati." #: config.py:43 msgid "Do you want the Google search snarfer enabled by default?" msgstr "Vuoi che lo snarfer per la ricerca in Google sia abilitato di default?" #: config.py:89 #, docstring msgid "Value must be 1 <= n <= 8" msgstr "Il valore deve essere compreso tra 1 e 8 (incluso)" #: 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" " Referer will be generated in the following format:\n" " http://$server/$botName" msgstr "" "Determina l'URL che sarà inviato a Google come Referer per le ricerche.\n" " Se questo valore è vuoto, verrà generato un Referer nel seguente formato:\n" " http://$server/$botName" #: config.py:105 msgid "" "Determines whether the search snarfer is\n" " enabled. If so, messages (even unaddressed ones) beginning with the word\n" " 'google' will result in the first URL Google returns being sent to the\n" " channel." msgstr "" "Determina se lo snarfer per la ricerca è abilitato. In caso lo sia, i messaggi\n" " (anche quelli non diretti) che iniziano con la parola \"google\" invieranno\n" " in canale il primo URL che Google restituisce." #: config.py:110 msgid "" "Determines whether the word 'google' in the\n" " bot's output will be made colorful (like Google's logo)." msgstr "" "Determina se la parola \"google\" nell'output del bot sarà colorata (come il logo di Google)." #: config.py:113 msgid "Determines whether results are bolded." msgstr "Determina se i risultati saranno in grassetto." #: config.py:115 msgid "" "Determines the maximum number of results returned\n" " from the google command." msgstr "" "Determina il numero massimo di risultati restituiti dal comando google." #: config.py:118 msgid "" "Determines what default language is used in\n" " searches. If left empty, no specific language will be requested." msgstr "" "Determina la lingua predefinita usata nelle ricerche. Se lasciata vuota,\n" " non sarà richiesta nessuna lingua specifica." #: config.py:118 msgid "en" msgstr "it" #: config.py:121 msgid "" "Determines what level of search filtering to use\n" " by default. 'active' - most filtering, 'moderate' - default filtering,\n" " 'off' - no filtering" msgstr "" "Determina quale livello di filtraggio usare in modo predefinito. \"active\":\n" " filtra tutto; \"moderate\": filtro predefinito; \"off\": filtro disattivato." #: plugin.py:101 #, docstring msgid "" "Perform a search using Google's AJAX API.\n" " search(\"search phrase\", options={})\n" "\n" " Valid options are:\n" " smallsearch - True/False (Default: False)\n" " filter - {active,moderate,off} (Default: \"moderate\")\n" " language - Restrict search to documents in the given language\n" " (Default: \"lang_en\")\n" " " msgstr "" "Esegue una ricerca usando l'API AJAX di Google.\n" " search(\"search phrase\", options={})\n" "\n" " Le opzioni valide sono:\n" " smallsearch - True/False (Default: False)\n" " filter - {active,moderate,off} (Default: \"moderate\")\n" " language - Restringe la ricerca alla lingua specificata\n" " (Default: \"lang_en\")\n" " " #: plugin.py:141 plugin.py:200 msgid "We broke The Google!" msgstr "Abbiamo rotto Google!" #: plugin.py:161 msgid "No matches found." msgstr "Nessun risultato trovato." #: plugin.py:167 #, docstring msgid "" "[--snippet] \n" "\n" " Does a google search, but only returns the first result.\n" " If option --snippet is given, returns also the page text snippet.\n" " " msgstr "" "[--snippet] \n" "\n" " Effettua una ricerca su Google restituendo solo il primo risultato.\n" " Se l'opzione --snippet è specificata, riporta anche un frammento di testo della pagina.\n" " " #: plugin.py:184 msgid "Google found nothing." msgstr "Google non ha trovato nulla." #: plugin.py:189 #, docstring msgid "" " [--{filter,language} ]\n" "\n" " Searches google.com for the given string. As many results as can fit\n" " are included. --language accepts a language abbreviation; --filter\n" " accepts a filtering level ('active', 'moderate', 'off').\n" " " msgstr "" " [--{filter,language} ]\n" "\n" " Ricerca la data stringa su google.com fornendo tutti i risultati possibili.\n" " --language accetta un'abbreviazione di lingua; --filter un livello di filtraggio\n" " ('active', 'moderate', 'off').\n" " " #: plugin.py:212 #, docstring msgid "" "\n" "\n" " Returns a link to the cached version of if it is available.\n" " " msgstr "" "\n" "\n" " Restituisce un link della versione in cache di , se disponibile.\n" " " #: plugin.py:223 msgid "Google seems to have no cache for that site." msgstr "Google sembra non avere cache per questo sito." #: plugin.py:228 #, docstring msgid "" " [ ...]\n" "\n" " Returns the results of each search, in order, from greatest number\n" " of results to least.\n" " " msgstr "" " [ ...]\n" "\n" " Restituisce i risultati di ogni ricerca in ordine decrescente in base al numero di risultati.\n" " " #: plugin.py:252 #, docstring msgid "" " [to] \n" "\n" " Returns translated from into .\n" " Beware that translating to or from languages that use multi-byte\n" " characters may result in some very odd results.\n" " " msgstr "" " [to] \n" "\n" " Restituisce tradotto . Attenzione\n" " che tradurre da o verso lingue che usano caratteri multibyte\n" " può produrre risultati molto strani.\n" " " #: plugin.py:271 msgid "from language" msgstr "da lingua" #: plugin.py:272 plugin.py:281 msgid "Valid languages are: %L" msgstr "Le lingue valide sono: %L" #: plugin.py:280 msgid "to language" msgstr "a lingua" #: plugin.py:314 #, docstring msgid "^google\\s+(.*)$" msgstr "^google\\s+(.*)$" #: plugin.py:343 #, docstring msgid "" "\n" "\n" " Uses Google's calculator to calculate the value of .\n" " " msgstr "" "\n" "\n" " Utilizza la calcolatrice di Google per calcolare il valore di .\n" " " #: plugin.py:370 msgid "Google says: Error: %s." msgstr "Google dice: errore: %s." #: plugin.py:377 #, docstring msgid "" "\n" "\n" " Looks up on Google.\n" " " msgstr "" "\n" "\n" " Cerca su Google.\n" " " #: plugin.py:391 msgid "Google's phonebook didn't come up with anything." msgstr "La rubrica di Google non ha fornito alcun risultato." limnoria-2020.03.17/plugins/Google/plugin.py0000644000175000017500000003632113634634532020071 0ustar valval00000000000000### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2008-2010, James McCoy # 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 re import sys import json import supybot.conf as conf import supybot.utils as utils import supybot.world as world from supybot.commands import * import supybot.utils.minisix as minisix import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Google') class Google(callbacks.PluginRegexp): """This is a simple plugin to provide access to the Google services we all know and love from our favorite IRC bot.""" threaded = True callBefore = ['Web'] regexps = ['googleSnarfer'] _colorGoogles = {} def _getColorGoogle(self, m): s = m.group(1) ret = self._colorGoogles.get(s) if not ret: L = list(s) L[0] = ircutils.mircColor(L[0], 'blue')[:-1] L[1] = ircutils.mircColor(L[1], 'red')[:-1] L[2] = ircutils.mircColor(L[2], 'yellow')[:-1] L[3] = ircutils.mircColor(L[3], 'blue')[:-1] L[4] = ircutils.mircColor(L[4], 'green')[:-1] L[5] = ircutils.mircColor(L[5], 'red') ret = ''.join(L) self._colorGoogles[s] = ret return ircutils.bold(ret) _googleRe = re.compile(r'\b(google)\b', re.I) def outFilter(self, irc, msg): if msg.command == 'PRIVMSG' and \ self.registryValue('colorfulFilter', msg.channel, irc.network): s = msg.args[1] s = re.sub(self._googleRe, self._getColorGoogle, s) msg = ircmsgs.privmsg(msg.args[0], s, msg=msg) return msg _decode_re = re.compile(r'
(?P.*?)</div><div class="(\w| )+">(?P<breadcrumbs>.*?)</div></a></div>(?P<content><div class="(\w| )+">.*?</div></div>)', re.DOTALL | re.MULTILINE) @classmethod def decode(cls, text): matches = cls._decode_re.finditer(text) results = [] for match in matches: r = match.groupdict() r['url'] = utils.web.urlunquote(utils.web.htmlToText(r['url'].split('&')[0])) results.append(r) return results _gsearchUrl = 'https://www.google.com/search' def search(self, query, channel, network, options={}): """search("search phrase", options={}) Valid options are: smallsearch - True/False (Default: False) filter - {active,moderate,off} (Default: "moderate") language - Restrict search to documents in the given language (Default: "lang_en") """ self.log.warning('The Google plugin search is deprecated since ' 'Google closed their public API and will be removed in a ' 'future release. Please consider switching to an other ' 'plugin for your searches, like ' '<https://github.com/Hoaas/Supybot-plugins/tree/master/DuckDuckGo>, ' '<https://github.com/joulez/GoogleCSE>, or ' '<https://github.com/GLolol/SupyPlugins/tree/master/DDG>.') ref = self.registryValue('referer') if not ref: ref = 'http://%s/%s' % (dynamic.irc.server, dynamic.irc.nick) headers = dict(utils.web.defaultHeaders) headers['Referer'] = ref opts = {'q': query, 'gbv': '2'} for (k, v) in options.items(): if k == 'smallsearch': if v: opts['rsz'] = 'small' else: opts['rsz'] = 'large' elif k == 'filter': opts['safe'] = v elif k == 'language': opts['hl'] = v defLang = self.registryValue('defaultLanguage', channel, network) if 'hl' not in opts and defLang: opts['hl'] = defLang.strip('lang_') if 'safe' not in opts: opts['safe'] = self.registryValue('searchFilter', channel, network) if 'rsz' not in opts: opts['rsz'] = 'large' text = utils.web.getUrl('%s?%s' % (self._gsearchUrl, utils.web.urlencode(opts)), headers=headers).decode('utf8') return text def formatData(self, data, bold=True, max=0, onetoone=False): data = self.decode(data) results = [] if max: data = data[:max] for result in data: title = utils.web.htmlToText(result['title']\ .encode('utf-8')) url = result['url'] if minisix.PY2: url = url.encode('utf-8') if title: if bold: title = ircutils.bold(title) results.append(format('%s: %u', title, url)) else: results.append(url) if minisix.PY2: repl = lambda x:x if isinstance(x, unicode) else unicode(x, 'utf8') results = list(map(repl, results)) if not results: return [_('No matches found.')] elif onetoone: return results else: return [minisix.u('; ').join(results)] @internationalizeDocstring def lucky(self, irc, msg, args, opts, text): """[--snippet] <search> Does a google search, but only returns the first result. If option --snippet is given, returns also the page text snippet. """ opts = dict(opts) data = self.search(text, msg.channel, irc.network, {'smallsearch': True}) data = self.decode(data) if data: url = data[0]['url'] if 'snippet' in opts: snippet = data[0]['content'] snippet = " | " + utils.web.htmlToText(snippet, tagReplace='') else: snippet = "" result = url + snippet irc.reply(result) else: irc.reply(_('Google found nothing.')) lucky = wrap(lucky, [getopts({'snippet':'',}), 'text']) @internationalizeDocstring def google(self, irc, msg, args, optlist, text): """<search> [--{filter,language} <value>] Searches google.com for the given string. As many results as can fit are included. --language accepts a language abbreviation; --filter accepts a filtering level ('active', 'moderate', 'off'). """ if 'language' in optlist and optlist['language'].lower() not in \ conf.supybot.plugins.Google.safesearch.validStrings: irc.errorInvalid('language') data = self.search(text, msg.channel, irc.network, dict(optlist)) bold = self.registryValue('bold', msg.channel, irc.network) max = self.registryValue('maximumResults', msg.channel, irc.network) # We don't use supybot.reply.oneToOne here, because you generally # do not want @google to echo ~20 lines of results, even if you # have reply.oneToOne enabled. onetoone = self.registryValue('oneToOne', msg.channel, irc.network) for result in self.formatData(data, bold=bold, max=max, onetoone=onetoone): irc.reply(result) google = wrap(google, [getopts({'language':'something', 'filter':''}), 'text']) @internationalizeDocstring def cache(self, irc, msg, args, url): """<url> Returns a link to the cached version of <url> if it is available. """ data = self.search(url, msg.channel, irc.network, {'smallsearch': True}) if data: m = data[0] if m['cacheUrl']: url = m['cacheUrl'].encode('utf-8') irc.reply(url) return irc.error(_('Google seems to have no cache for that site.')) cache = wrap(cache, ['url']) _fight_re = re.compile(r'id="resultStats"[^>]*>(?P<stats>[^<]*)') @internationalizeDocstring def fight(self, irc, msg, args): """<search string> <search string> [<search string> ...] Returns the results of each search, in order, from greatest number of results to least. """ channel = msg.channel network = irc.network results = [] for arg in args: text = self.search(arg, channel, network, {'smallsearch': True}) i = text.find('id="resultStats"') stats = utils.web.htmlToText(self._fight_re.search(text).group('stats')) if stats == '': results.append((0, args)) continue count = ''.join(filter('0123456789'.__contains__, stats)) results.append((int(count), arg)) results.sort() results.reverse() if self.registryValue('bold', channel, network): bold = ircutils.bold else: bold = repr s = ', '.join([format('%s: %i', bold(s), i) for (i, s) in results]) irc.reply(s) def _translate(self, sourceLang, targetLang, text): headers = dict(utils.web.defaultHeaders) headers['User-Agent'] = ('Mozilla/5.0 (X11; U; Linux i686) ' 'Gecko/20071127 Firefox/2.0.0.11') sourceLang = utils.web.urlquote(sourceLang) targetLang = utils.web.urlquote(targetLang) text = utils.web.urlquote(text) result = utils.web.getUrlFd('http://translate.googleapis.com/translate_a/single' '?client=gtx&dt=t&sl=%s&tl=%s&q=' '%s' % (sourceLang, targetLang, text), headers).read().decode('utf8') while ',,' in result: result = result.replace(',,', ',null,') while '[,' in result: result = result.replace('[,', '[') data = json.loads(result) try: language = data[2] except: language = 'unknown' if data[0]: return (''.join(x[0] for x in data[0]), language) else: return (_('No translations found.'), language) @internationalizeDocstring def translate(self, irc, msg, args, sourceLang, targetLang, text): """<source language> [to] <target language> <text> Returns <text> translated from <source language> into <target language>. <source language> and <target language> take language codes (not language names), which are listed here: https://cloud.google.com/translate/docs/languages """ (text, language) = self._translate(sourceLang, targetLang, text) irc.reply(text, language) translate = wrap(translate, ['something', 'to', 'something', 'text']) def googleSnarfer(self, irc, msg, match): r"^google\s+(.*)$" if not self.registryValue('searchSnarfer', msg.channel, irc.network): return searchString = match.group(1) data = self.search(searchString, msg.channel, irc.network, {'smallsearch': True}) if data['responseData']['results']: url = data['responseData']['results'][0]['unescapedUrl'] irc.reply(url, prefixNick=False) googleSnarfer = urlSnarfer(googleSnarfer) def _googleUrl(self, s, channel, network): s = utils.web.urlquote_plus(s) url = r'http://%s/search?q=%s' % \ (self.registryValue('baseUrl', channel, network), s) return url _calcRe1 = re.compile(r'<span class="cwcot".*?>(.*?)</span>', re.I) _calcRe2 = re.compile(r'<div class="vk_ans.*?>(.*?)</div>', re.I | re.S) _calcRe3 = re.compile(r'<div class="side_div" id="rhs_div">.*?<input class="ucw_data".*?value="(.*?)"', re.I) @internationalizeDocstring def calc(self, irc, msg, args, expr): """<expression> Uses Google's calculator to calculate the value of <expression>. """ url = self._googleUrl(expr, msg.channel, irc.network) h = {"User-Agent":"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36"} html = utils.web.getUrl(url, headers=h).decode('utf8') match = self._calcRe1.search(html) if not match: match = self._calcRe2.search(html) if not match: match = self._calcRe3.search(html) if not match: irc.reply("I could not find an output from Google Calc for: %s" % expr) return else: s = match.group(1) else: s = match.group(1) else: s = match.group(1) # do some cleanup of text s = re.sub(r'<sup>(.*)</sup>⁄<sub>(.*)</sub>', r' \1/\2', s) s = re.sub(r'<sup>(.*)</sup>', r'^\1', s) s = utils.web.htmlToText(s) irc.reply("%s = %s" % (expr, s)) calc = wrap(calc, ['text']) _phoneRe = re.compile(r'Phonebook.*?<font size=-1>(.*?)<a href') @internationalizeDocstring def phonebook(self, irc, msg, args, phonenumber): """<phone number> Looks <phone number> up on Google. """ url = self._googleUrl(phonenumber, msg.channel, irc.network) html = utils.web.getUrl(url).decode('utf8') m = self._phoneRe.search(html) if m is not None: s = m.group(1) s = s.replace('<b>', '') s = s.replace('</b>', '') s = utils.web.htmlToText(s) irc.reply(s) else: irc.reply(_('Google\'s phonebook didn\'t come up with anything.')) phonebook = wrap(phonebook, ['text']) Class = Google # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Google/test.py����������������������������������������������������������0000644�0001750�0001750�00000007646�13634634532�017562� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2008-2009, James Vega # 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 GoogleTestCase(ChannelPluginTestCase): plugins = ('Google', 'Config') if network: def testCalcHandlesMultiplicationSymbol(self): self.assertNotRegexp('google calc seconds in a century', r'215') 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') def testHtmlHandled(self): self.assertNotRegexp('google calc ' 'the speed of light ' 'in microns / fortnight', '<sup>') self.assertNotRegexp('google calc ' 'the speed of light ' 'in microns / fortnight', '×') def testSearch(self): self.assertNotError('google foo') self.assertRegexp('google dupa', r'dupa') # Unicode check self.assertNotError('google ae') def testUrlDecode(self): self.assertRegexp( 'google site:http://www.urbandictionary.com carajo land', '\x02Urban Dictionary: carajo land\x02: ' r'https?://www.urbandictionary.com/define.php\?term=carajo%20land') def testLucky(self): self.assertResponse('lucky Hacker News', 'https://news.ycombinator.com/') def testSearchFormat(self): self.assertRegexp('google foo', '<https?://.*>') self.assertNotError('config reply.format.url %s') self.assertRegexp('google foo', 'https?://.*') self.assertNotRegexp('google foo', '<https?://.*>') def testSearchOneToOne(self): self.assertRegexp('google dupa', ';') self.assertNotError('config plugins.Google.oneToOne True') self.assertNotRegexp('google dupa', ';') def testFight(self): self.assertRegexp('fight supybot moobot', r'.*supybot.*: \d+') self.assertNotError('fight ... !') def testTranslate(self): self.assertRegexp('translate en es hello world', 'Hola mundo') def testCalcDoesNotHaveExtraSpaces(self): self.assertNotRegexp('google calc 1000^2', r'\s+,\s+') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Hashes/�����������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016221� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Hashes/__init__.py������������������������������������������������������0000644�0001750�0001750�00000004735�13634634532�020335� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2017, Ken Spencer # 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. ### """ Provides various hash-related commands. """ 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__ = "%%VERSION%%" __author__ = supybot.Author('Ken Spencer', 'kspencer/IotaSpencer', 'ken@electrocode.net') __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������limnoria-2020.03.17/plugins/Hashes/config.py��������������������������������������������������������0000644�0001750�0001750�00000004230�13634634532�020031� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2017, Ken Spencer # 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 from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Hashes') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Hashes', True) String = conf.registerPlugin('Hashes') ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Hashes/plugin.py��������������������������������������������������������0000644�0001750�0001750�00000007645�13634634532�020077� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2017, Ken Spencer # 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 hashlib import supybot.conf as conf import supybot.registry as registry import supybot.utils as utils from supybot.commands import * import supybot.plugins as plugins import supybot.commands as commands import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Hashes') class Hashes(callbacks.Plugin): """Provides hash or encryption related commands""" @internationalizeDocstring def md5(self, irc, msg, args, text): """<text> Returns the md5 hash of a given string. """ irc.reply(hashlib.md5(text.encode('utf8')).hexdigest()) md5 = wrap(md5, ['text']) @internationalizeDocstring def sha(self, irc, msg, args, text): """<text> Returns the SHA1 hash of a given string. """ irc.reply(hashlib.sha1(text.encode('utf8')).hexdigest()) sha = wrap(sha, ['text']) @internationalizeDocstring def sha256(self, irc, msg, args, text): """<text> Returns a SHA256 hash of the given string. """ irc.reply(hashlib.sha256(text.encode('utf8')).hexdigest()) sha256 = wrap(sha256, ['text']) @internationalizeDocstring def sha512(self, irc, msg, args, text): """<text> Returns a SHA512 hash of the given string. """ irc.reply(hashlib.sha512(text.encode('utf8')).hexdigest()) sha512 = wrap(sha512, ['text']) @internationalizeDocstring def algorithms(self, irc, msg, args): """<takes no arguments> Returns the list of available algorithms.""" irc.reply(utils.str.format("%L", hashlib.algorithms_available)) if hasattr(hashlib, 'algorithms_available'): algorithms = wrap(algorithms) @internationalizeDocstring def mkhash(self, irc, msg, args, algorithm, text): """<algorithm> <text> Returns TEXT after it has been hashed with ALGORITHM. See the 'algorithms' command in this plugin to return the algorithms available on this system.""" if algorithm not in hashlib.algorithms_available: irc.error("Algorithm not available.") else: irc.reply(hashlib.new(algorithm, text.encode('utf8')).hexdigest()) if hasattr(hashlib, 'algorithms_available'): mkhash = wrap(mkhash, ['something', 'text']) Class = Hashes �������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Hashes/test.py����������������������������������������������������������0000644�0001750�0001750�00000005226�13634634532�017551� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2017, Ken Spencer # 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 re import hashlib from supybot.test import * import supybot.utils as utils brown_fox = 'The quick brown fox jumped over the lazy dog' class HashesTestCase(PluginTestCase): plugins = ('Hashes',) def testHashes(self): self.assertResponse('md5 %s' % brown_fox, '08a008a01d498c404b0c30852b39d3b8') self.assertResponse('sha %s' % brown_fox, 'f6513640f3045e9768b239785625caa6a2588842') self.assertResponse('sha256 %s' % brown_fox, '7d38b5cd25a2baf85ad3bb5b9311383e671a8a142eb302b324d4a5fba8748c69') self.assertResponse('sha512 %s' % brown_fox, 'db25330cfa5d14eaadf11a6263371cfa0e70fcd7a63a433b91f2300ca25d45b66a7b50d2f6747995c8fa0ff365b28974792e7acd5624e1ddd0d66731f346f0e7') if hasattr(hashlib, 'algorithms_available'): def testMkhash(self): self.assertResponse('mkhash md5 %s' % brown_fox, '08a008a01d498c404b0c30852b39d3b8') self.assertError('mkhash NonExistant %s' % brown_fox) self.assertNotError('algorithms') else: print("Hashes: Skipping algorithms/mkhash tests as 'hashlib.algorithms_available' is not available.") ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Herald/�����������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016205� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Herald/__init__.py������������������������������������������������������0000644�0001750�0001750�00000004513�13634634532�020313� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Greets users who join the channel with a recognized hostmask with a nice little greeting. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload reload(plugin) # In case we're being reloaded. if world.testing: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Herald/config.py��������������������������������������������������������0000644�0001750�0001750�00000007427�13634634532�020030� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Herald') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Herald', True) Herald = conf.registerPlugin('Herald') conf.registerChannelValue(Herald, 'heralding', registry.Boolean(True, _("""Determines whether messages will be sent to the channel when a recognized user joins; basically enables or disables the plugin."""))) conf.registerGlobalValue(Herald, 'requireCapability', registry.String('', _("""Determines what capability (if any) is required to add/change/remove the herald of another user."""))) conf.registerChannelValue(Herald, 'throttle', registry.PositiveInteger(600, _("""Determines the minimum number of seconds between heralds."""))) conf.registerChannelValue(Herald.throttle, 'afterPart', registry.NonNegativeInteger(0, _("""Determines the minimum number of seconds after parting that the bot will not herald the person when they rejoin."""))) conf.registerChannelValue(Herald.throttle, 'afterSplit', registry.NonNegativeInteger(60, _("""Determines the minimum number of seconds after a netsplit that the bot will not herald the users that split."""))) conf.registerChannelValue(Herald, 'default', registry.String('', _("""Sets the default herald to use. If a user has a personal herald specified, that will be used instead. If set to the empty string, the default herald will be disabled."""))) conf.registerChannelValue(Herald.default, 'notice', registry.Boolean(True, _("""Determines whether the default herald will be sent as a NOTICE instead of a PRIVMSG."""))) conf.registerChannelValue(Herald.default, 'public', registry.Boolean(False, _("""Determines whether the default herald will be sent publicly."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Herald/locales/���������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017627� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Herald/locales/fi.po����������������������������������������������������0000644�0001750�0001750�00000016244�13634634532�020566� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: Herald plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 14:04+EET\n" "PO-Revision-Date: 2014-12-20 14:07+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.10\n" #: config.py:46 msgid "" "Determines whether messages will be sent to the\n" " channel when a recognized user joins; basically enables or disables the\n" " plugin." msgstr "" "Määrittää lähetetäänkö viestejä kanavalle, kun\n" " tunnistettu käyttäjä liittyy; perusteellisesti ottaa tai poistaa " "käytöstä\n" " tämän lisäosan." #: config.py:50 msgid "" "Determines what capability (if any) is required to\n" " add/change/remove the herald of another user." msgstr "" "Määrittää mikä valtuus (jos mikään) on vaadittu\n" " toisen käyttäjän airueen lisäämisessä/muuttamisessa/poistamisessa." #: config.py:53 msgid "" "Determines the minimum number of seconds\n" " between heralds." msgstr "" "Määrittää minimi määrän sekunteja\n" " airueiden välillä." #: config.py:56 msgid "" "Determines the minimum number of seconds\n" " after parting that the bot will not herald the person when they\n" " rejoin." msgstr "" "Määrittää minimi määrän sekunteja, jolloin\n" " henkilön poistumisen jälkeen botti ei toimi airueena, kun hän\n" " palaa." #: config.py:60 msgid "" "Determines the minimum number of seconds\n" " after a netsplit that the bot will not herald the users that split." msgstr "" "Määritt'' minimi määrän sekunteja netsplitin jälkeen\n" " jolloin botti ei toimi airueena käyttäjille, jotka splittaantuivat." #: config.py:63 msgid "" "Sets the default herald to use. If a user has a\n" " personal herald specified, that will be used instead. If set to the " "empty\n" " string, the default herald will be disabled." msgstr "" "Asettaa oletusairueen käytettäväksi. Jos käyttäjällä on oma\n" " henkilökohtainen airue määritettynä, sitä käytetään sen sijaan. Jos " "asetettu tyhjäksi\n" " merkkiketjuksi, oletus airue poistetaan käytöstä." #: config.py:67 msgid "" "Determines whether the default herald will be\n" " sent as a NOTICE instead of a PRIVMSG." msgstr "" "Määrittää lähetetäänkö oletus airue NOTICEna\n" " PRIVMSG:gen sijaan." #: config.py:70 msgid "" "Determines whether the default herald will be\n" " sent publicly." msgstr "" "Määrittää lähetetäänkö oletus airut \n" " julkisesti." #: plugin.py:58 msgid "" "This plugin allows you to set welcome messages (heralds) to people who\n" " are recognized by the bot when they join a channel." msgstr "" "Tämä plugini sallii tervetuloviestien (airueiden) asettamisen käyttäjille, " "jotka botti tunnistaa heidän liittyessään kanavalle." #: plugin.py:145 #, fuzzy msgid "" "[<channel>] [--remove|<msg>]\n" "\n" " If <msg> is given, sets the default herald to <msg>. A <msg> of " "\"\"\n" " will remove the default herald. If <msg> is not given, returns the\n" " current default herald. <channel> is only necessary if the message\n" " isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] [--remove|<viesti>]\n" "\n" " Jos <viesti> on annettu, asettaa oletusairueen <viestiksi>. " "<Viesti>\"\"\n" " poistaa oletus airueen. Jos <viesti> ei anneta, palauttaa\n" " nykyisen oletusairueen. <Kanava> on vaadittu vain jos viestiä ei " "lähetetä kanavalla itsellään." #: plugin.py:164 msgid "I do not have a default herald set for %s." msgstr "Minulla ei ole oletus airuetta asetettuna %s:lle." #: plugin.py:172 msgid "" "[<channel>] [<user|nick>]\n" "\n" " Returns the current herald message for <user> (or the user\n" " <nick|hostmask> is currently identified or recognized as). If " "<user>\n" " is not given, defaults to the user giving the command. <channel>\n" " is only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] [<käyttäjä|nimimerkki>]\n" "\n" " Palauttaa nykyisen airue viestin <käyttäjlle> (tai käyttäjälle\n" " <nimimerkissä|hostmaskissa> on tunnistetty). Jos <käyttäjää>\n" " ei ole annettu, se on oletuksenena komennon antanut henkilö. " "<Kanava>\n" " on vaadittu vain jos viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:183 msgid "I have no herald for %s." msgstr "Minulla ei ole airuetta %s:lle." #: plugin.py:203 msgid "" "[<channel>] <user|nick> <msg>\n" "\n" " Sets the herald message for <user> (or the user <nick|hostmask> is\n" " currently identified or recognized as) to <msg>. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] <käyttäjä|nimimerkki> <viesti>\n" "\n" " Asettaa <käyttäjän> airueen (tai käyttäjän, johon <nimimerkki|" "hostmask> \n" " tällä hetkellä on tunnistautunut, tai tunnistettu) <viestiksi>. " "<Kanava> on\n" " vaadittu vain jos viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:216 msgid "" "[<channel>] [<user|nick>]\n" "\n" " Removes the herald message set for <user>, or the user\n" " <nick|hostmask> is currently identified or recognized as. If " "<user>\n" " is not given, defaults to the user giving the command.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<kanava>] [<käyttäjä|nimimerkki>]\n" "\n" " Poistaa airue viestin, joka on asetettu <käyttäjälle>, tai\n" " <nimimerkille|hostmaskille> joka on tällä hetkellä tunnistettu tai " "tunnistautunut. Jos <käyttäjää>\n" " ei ole annettu, se on oletuksena komennon antanut käyttäjä.\n" " <Kanava> on vaadittu vain jos viestiä ei lähetetä kanavalla\n" " itsellään.\n" " " #: plugin.py:229 msgid "I have no herald for that user." msgstr "Minulla ei ole airuetta tuolle käyttäjälle." #: plugin.py:234 msgid "" "[<channel>] [<user|nick>] <regexp>\n" "\n" " Changes the herald message for <user>, or the user <nick|hostmask> " "is\n" " currently identified or recognized as, according to <regexp>. If\n" " <user> is not given, defaults to the calling user. <channel> is " "only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] [<käyttäjä|nimimerkki>] <säännöllinen lauseke>\n" "\n" " Vaihtaa <käyttäjän> airueen, tai käyttäjän <nick|hostmask> joka\n" " on tunnistautunut tai tunnistettu, <säännöllisen lausekkeen " "mukaan>. Jos\n" " <käyttäjää> ei ole annettu, se on oletuksena komennon antanut " "käyttäjä. <kanava> on vaadittu vain, jos\n" " viestiä ei lähetetä kanavalla itsellään.\n" " " ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Herald/locales/fr.po����������������������������������������������������0000644�0001750�0001750�00000014444�13634634532�020577� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2010-10-17 15:21+CEST\n" "PO-Revision-Date: 2014-07-05 00:11+0200\n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\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" #: config.py:46 msgid "" "Determines whether messages will be sent to the\n" " channel when a recognized user joins; basically enables or disables the\n" " plugin." msgstr "Détermine si les messages seront envoyés sur le canal lorsqu'un utilisateur reconnu arrive ; active ou désactive basiquement le plugin." #: config.py:50 msgid "" "Determines what capability (if any) is required to\n" " add/change/remove the herald of another user." msgstr "Détermine quelle capacité (s'il en est une) est requise pour ajouter/changer/supprimer le message d'annonce d'un autre utilisateur." #: config.py:53 msgid "" "Determines the minimum number of seconds\n" " between heralds." msgstr "Détermine le nombre minimum de secondes entre deux annonces." #: config.py:56 msgid "" "Determines the minimum number of seconds\n" " after parting that the bot will not herald the person when they\n" " rejoin." msgstr "Détermine le nombre minimum de secondes après qu'un utilisateur soit parti pour que le bot puisse l'annoncer à nouveau quand il revient." #: config.py:60 msgid "" "Determines the minimum number of seconds\n" " after a netsplit that the bot will not herald the users that split." msgstr "Détermine le nombre minimum de secondes après un netsplit durant lequel le bot n'annoncera pas les utilisateurs qui ont splitté." #: config.py:63 msgid "" "Sets the default herald to use. If a user has a\n" " personal herald specified, that will be used instead. If set to the empty\n" " string, the default herald will be disabled." msgstr "Définit le message d'annonce par défaut à utiliser. Si un utilisateur a un message d'annonce personnel spécifié, il peut être utilisé à la place. Si définit à une chaîne vide, le message d'annonce sera désactivé." #: config.py:67 msgid "" "Determines whether the default herald will be\n" " sent as a NOTICE instead of a PRIVMSG." msgstr "Détermine si le message d'annonce sera envoyé en NOTICE plutôt qu'en PRIVMSG." #: config.py:70 msgid "" "Determines whether the default herald will be\n" " sent publicly." msgstr "Détermine si le message d'annonce sera envoyé publiquement." #: plugin.py:143 msgid "" "[<channel>] [--remove|<msg>]\n" "\n" " If <msg> is given, sets the default herald to <msg>. A <msg> of \"\"\n" " will remove the default herald. If <msg> is not given, returns the\n" " current default herald. <channel> is only necessary if the message\n" " isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] [--remove|<message>]\n" "\n" "Si le <message> est donné, définit le message d'annonce par défaut pour être le <message>. Un <message> de la forme \"\" supprimera le message d'annonce par défaut. Si le <message> n'est pas donné, retourne le message d'annonce par défaut actuel. <canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:162 msgid "I do not have a default herald set for %s." msgstr "Je n'ai pas de message d'annonce par défaut pour %s." #: plugin.py:170 msgid "" "[<channel>] [<user|nick>]\n" "\n" " Returns the current herald message for <user> (or the user\n" " <nick|hostmask> is currently identified or recognized as). If <user>\n" " is not given, defaults to the user giving the command. <channel>\n" " is only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] <utilisateur|nick>]\n" "\n" "Retourne le message d'annonce courant pour l'<utilisateur> (ou l'utilisateur désigné par le <nick>). Si l'<utilisateur> n'est pas donné, cela vaut par défaut l'utilisateur donnant la commande. <canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:181 msgid "I have no herald for %s." msgstr "Je n'ai pas de message d'annonce pour %s." #: plugin.py:201 msgid "" "[<channel>] <user|nick> <msg>\n" "\n" " Sets the herald message for <user> (or the user <nick|hostmask> is\n" " currently identified or recognized as) to <msg>. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] <utilisateur|nick> <message>\n" "\n" "Définit le <message> d'annonce de l'<utilisateur> (ou la personne désignée par <nick>). <canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:214 msgid "" "[<channel>] [<user|nick>]\n" "\n" " Removes the herald message set for <user>, or the user\n" " <nick|hostmask> is currently identified or recognized as. If <user>\n" " is not given, defaults to the user giving the command.\n" " <channel> is only necessary if the message isn't sent in the channel\n" " itself.\n" " " msgstr "" "[<canal>] [<utilisateur|nick>]\n" "\n" "Supprime le message d'annonce de l'<utilisateur> (ou de l'utilisateur désigné par le <nick>). <canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:227 msgid "I have no herald for that user." msgstr "Je n'ai pas de message d'annonce pour cet utilisateur." #: plugin.py:232 msgid "" "[<channel>] [<user|nick>] <regexp>\n" "\n" " Changes the herald message for <user>, or the user <nick|hostmask> is\n" " currently identified or recognized as, according to <regexp>. If\n" " <user> is not given, defaults to the calling user. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] [<utilisateur|nick>] [<expression régulière>]\n" "\n" "Change le message d'annonce de l'<utilisateur> (ou de l'utilisateur désigné par le <nick>), en fonction de l'<expression régulière>. Si l'<utilisateur> n'est pas donné, cea correspond par défaut de l'utilisateur appelant la commande. <canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Herald/locales/it.po����������������������������������������������������0000644�0001750�0001750�00000014456�13634634532�020607� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2014-07-05 00:06+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:46 msgid "" "Determines whether messages will be sent to the\n" " channel when a recognized user joins; basically enables or disables the\n" " plugin." msgstr "" "Determina se i messaggi verranno inviati nel canale in cui entra un utente\n" " riconosciuto; in pratica abilita o disabilita il plugin." #: config.py:50 msgid "" "Determines what capability (if any) is required to\n" " add/change/remove the herald of another user." msgstr "" "Determina quale capacità (eventuale) è richiesta per aggiungere, modificare\n" " e rimuovere l'annuncio di un altro utente." #: config.py:53 msgid "" "Determines the minimum number of seconds\n" " between heralds." msgstr "" "Determina il numero minimo di secondi tra un annuncio e l'altro." #: config.py:56 msgid "" "Determines the minimum number of seconds\n" " after parting that the bot will not herald the person when they\n" " rejoin." msgstr "" "Determina il numero minimo di secondi dall'uscita di un utente in cui\n" " il bot non invierà l'annuncio al suo rientro." #: config.py:60 msgid "" "Determines the minimum number of seconds\n" " after a netsplit that the bot will not herald the users that split." msgstr "" "Determina il numero minimo di secondi dopo un netsplit in cui il bot\n" " non invierà l'annuncio agli utenti coinvolti." #: config.py:63 msgid "" "Sets the default herald to use. If a user has a\n" " personal herald specified, that will be used instead. If set to the empty\n" " string, the default herald will be disabled." msgstr "" "Imposta l'annuncio predefinito da utilizzare. Se un utente ha un annuncio personalizzato,\n" " verrà usato quello. Se impostato ad una stringa vuota, il predefinito sarà disabilitato." #: config.py:67 msgid "" "Determines whether the default herald will be\n" " sent as a NOTICE instead of a PRIVMSG." msgstr "" "Determina se l'annuncio predefinito verrà inviato tramite NOTICE anziché PRIVMSG." #: config.py:70 msgid "" "Determines whether the default herald will be\n" " sent publicly." msgstr "" "Determina se l'annuncio predefinito verrà inviato pubblicamente." #: plugin.py:143 #, docstring msgid "" "[<channel>] [--remove|<msg>]\n" "\n" " If <msg> is given, sets the default herald to <msg>. A <msg> of \"\"\n" " will remove the default herald. If <msg> is not given, returns the\n" " current default herald. <channel> is only necessary if the message\n" " isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] [--remove|<messaggio>]\n" "\n" " Se <messaggio> è fornito, imposta l'annuncio predefinito a <messaggio>;\n" " un <messaggio> nella forma \"\" rimuoverà il predefinito. Se <messaggio>\n" " non è specificato, restituisce l'attuale annuncio. <canale> è necessario\n" " solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:162 msgid "I do not have a default herald set for %s." msgstr "Non ho un annuncio predefinito per %s." #: plugin.py:170 #, docstring msgid "" "[<channel>] [<user|nick>]\n" "\n" " Returns the current herald message for <user> (or the user\n" " <nick|hostmask> is currently identified or recognized as). If <user>\n" " is not given, defaults to the user giving the command. <channel>\n" " is only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] [<utente|nick>]\n" "\n" " Restituisce l'attuale annuncio per <utente> (o <nick|hostmask> con cui\n" " è identificato al momento). Se <utente> non è specificato, passa a quello\n" " che ha dato il comando. <canale> è necessario solo se il messaggio non\n" " viene inviato nel canale stesso.\n" " " #: plugin.py:181 msgid "I have no herald for %s." msgstr "Non ho annunci per %s." #: plugin.py:201 #, docstring msgid "" "[<channel>] <user|nick> <msg>\n" "\n" " Sets the herald message for <user> (or the user <nick|hostmask> is\n" " currently identified or recognized as) to <msg>. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] <utente|nick> <messaggio>\n" "\n" " Imposta l'annuncio per <utente> (o <nick|hostmask> con cui è identificato\n" " al momento) a <messaggio>. <canale> è necessario solo se il messaggio\n" " non viene inviato nel canale stesso.\n" " " #: plugin.py:214 #, docstring msgid "" "[<channel>] [<user|nick>]\n" "\n" " Removes the herald message set for <user>, or the user\n" " <nick|hostmask> is currently identified or recognized as. If <user>\n" " is not given, defaults to the user giving the command.\n" " <channel> is only necessary if the message isn't sent in the channel\n" " itself.\n" " " msgstr "" "[<canale>] [<utente|nick>]\n" "\n" " Rimuove l'annuncio per <utente> o <nick|hostmask> con cui è identificato\n" " al momento. Se <utente> non è specificato, passa a quello che ha dato il comando.\n" " <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:227 msgid "I have no herald for that user." msgstr "Non ho annunci per questo utente." #: plugin.py:232 #, docstring msgid "" "[<channel>] [<user|nick>] <regexp>\n" "\n" " Changes the herald message for <user>, or the user <nick|hostmask> is\n" " currently identified or recognized as, according to <regexp>. If\n" " <user> is not given, defaults to the calling user. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] [<utente|nick>] <regexp>\n" "\n" " Modifica l'annuncio per <utente>, o <nick|hostmask> con cui è identificato\n" " al momento, in base a <regexp>. Se <utente> non è specificato, passa a quello\n" " che ha dato il comando. <canale> è necessario solo se il messaggio non viene\n" " inviato nel canale stesso.\n" " " ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Herald/plugin.py��������������������������������������������������������0000644�0001750�0001750�00000024646�13634634532�020063� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import os import time import supybot.conf as conf import supybot.utils as utils import supybot.world as world import supybot.ircdb as ircdb from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.utils.structures import TimeoutQueue from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Herald') filename = conf.supybot.directories.data.dirize('Herald.db') class HeraldDB(plugins.ChannelUserDB): def serialize(self, v): return [v] def deserialize(self, channel, id, L): if len(L) != 1: raise ValueError return L[0] class Herald(callbacks.Plugin): """This plugin allows you to set welcome messages (heralds) to people who are recognized by the bot when they join a channel.""" def __init__(self, irc): self.__parent = super(Herald, self) self.__parent.__init__(irc) self.db = HeraldDB(filename) world.flushers.append(self.db.flush) self.lastParts = plugins.ChannelUserDictionary() splitTimeout = conf.supybot.plugins.Herald.throttle.afterSplit self.splitters = TimeoutQueue(splitTimeout) self.lastHerald = plugins.ChannelUserDictionary() def die(self): if self.db.flush in world.flushers: world.flushers.remove(self.db.flush) self.db.close() self.__parent.die() def doQuit(self, irc, msg): # We want to observe netsplits and keep from heralding users rejoining # after one. if ircmsgs.isSplit(msg): self.splitters.enqueue(msg.nick) try: id = ircdb.users.getUserId(msg.prefix) self.splitters.enqueue(id) except KeyError: pass def doJoin(self, irc, msg): if ircutils.strEqual(irc.nick, msg.nick): return # It's us. if msg.nick in self.splitters: self.log.debug('Not heralding %s, recent split.', msg.nick) return # Recently split. channel = msg.args[0] irc = callbacks.SimpleProxy(irc, msg) if self.registryValue('heralding', channel, irc.network): try: id = ircdb.users.getUserId(msg.prefix) if id in self.splitters: self.log.debug('Not heralding id #%s, recent split.', id) return herald = self.db[channel, id] except KeyError: default = self.registryValue('default', channel, irc.network) if default: default = ircutils.standardSubstitute(irc, msg, default) msgmaker = ircmsgs.privmsg if self.registryValue('default.notice', channel, irc.network): msgmaker = ircmsgs.notice target = msg.nick if self.registryValue('default.public', channel, irc.network): target = channel irc.queueMsg(msgmaker(target, default)) return now = time.time() throttle = self.registryValue('throttle', channel, irc.network) if now - self.lastHerald.get((channel, id), 0) > throttle: if (channel, id) in self.lastParts: i = self.registryValue('throttle.afterPart', channel, irc.network) if now - self.lastParts[channel, id] < i: return self.lastHerald[channel, id] = now herald = ircutils.standardSubstitute(irc, msg, herald) irc.reply(herald, prefixNick=False) def doPart(self, irc, msg): try: id = self._getId(irc, msg.prefix) self.lastParts[msg.args[0], id] = time.time() except KeyError: pass def _getId(self, irc, userNickHostmask): try: id = ircdb.users.getUserId(userNickHostmask) except KeyError: if not ircutils.isUserHostmask(userNickHostmask): hostmask = irc.state.nickToHostmask(userNickHostmask) id = ircdb.users.getUserId(hostmask) else: raise KeyError return id @internationalizeDocstring def default(self, irc, msg, args, channel, optlist, text): """[<channel>] [--remove|<msg>] If <msg> is given, sets the default herald to <msg>. A <msg> of "" will remove the default herald. If <msg> is not given, returns the current default herald. <channel> is only necessary if the message isn't sent in the channel itself. """ if optlist and text: raise callbacks.ArgumentError for (option, foo) in optlist: if option == 'remove': self.setRegistryValue('default', '', channel) irc.replySuccess() return if text: self.setRegistryValue('default', text, channel) irc.replySuccess() else: resp = self.registryValue('default', channel, irc.network) or \ _('I do not have a default herald set for %s.') % channel irc.reply(resp) default = wrap(default, ['channel', getopts({'remove': ''}), additional('text')]) @internationalizeDocstring def get(self, irc, msg, args, channel, user): """[<channel>] [<user|nick>] Returns the current herald message for <user> (or the user <nick|hostmask> is currently identified or recognized as). If <user> is not given, defaults to the user giving the command. <channel> is only necessary if the message isn't sent in the channel itself. """ try: herald = self.db[channel, user.id] irc.reply(herald) except KeyError: irc.error(_('I have no herald for %s.') % user.name) get = wrap(get, ['channel', first('otherUser', 'user')]) def _preCheck(self, irc, msg, user): capability = self.registryValue('requireCapability') if capability: try: u = ircdb.users.getUser(msg.prefix) except KeyError: irc.errorNotRegistered(Raise=True) else: if u != user: if not ircdb.checkCapability(msg.prefix, capability): irc.errorNoCapability(capability, Raise=True) # I chose not to make <user|nick> optional in this command because # if it's not a valid username (e.g., if the user tyops and misspells a # username), it may be nice not to clobber the user's herald. @internationalizeDocstring def add(self, irc, msg, args, channel, user, herald): """[<channel>] <user|nick> <msg> Sets the herald message for <user> (or the user <nick|hostmask> is currently identified or recognized as) to <msg>. <channel> is only necessary if the message isn't sent in the channel itself. """ self._preCheck(irc, msg, user) self.db[channel, user.id] = herald irc.replySuccess() add = wrap(add, ['channel', 'otherUser', 'text']) @internationalizeDocstring def remove(self, irc, msg, args, channel, user): """[<channel>] [<user|nick>] Removes the herald message set for <user>, or the user <nick|hostmask> is currently identified or recognized as. If <user> is not given, defaults to the user giving the command. <channel> is only necessary if the message isn't sent in the channel itself. """ self._preCheck(irc, msg, user) try: del self.db[channel, user.id] irc.replySuccess() except KeyError: irc.error(_('I have no herald for that user.')) remove = wrap(remove, ['channel', first('otherUser', 'user')]) @internationalizeDocstring def change(self, irc, msg, args, channel, user, changer): """[<channel>] [<user|nick>] <regexp> Changes the herald message for <user>, or the user <nick|hostmask> is currently identified or recognized as, according to <regexp>. If <user> is not given, defaults to the calling user. <channel> is only necessary if the message isn't sent in the channel itself. """ self._preCheck(irc, msg, user) s = self.db[channel, user.id] newS = changer(s) self.db[channel, user.id] = newS irc.replySuccess() change = wrap(change, ['channel', first('otherUser', 'user'), 'regexpReplacer']) Class = Herald # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Herald/test.py����������������������������������������������������������0000644�0001750�0001750�00000003321�13634634532�017527� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * class HeraldTestCase(PluginTestCase): plugins = ('Herald',) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Internet/���������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016576� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Internet/__init__.py����������������������������������������������������0000644�0001750�0001750�00000004727�13634634532�020713� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Provides some internet-related commands. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {supybot.authors.jamessan: ['whois']} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������limnoria-2020.03.17/plugins/Internet/config.py������������������������������������������������������0000644�0001750�0001750�00000004701�13634634532�020411� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Internet') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Internet', True) Internet = conf.registerPlugin('Internet') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Internet, 'someConfigVariableName', # registry.Boolean(False, _("""Help for someConfigVariableName."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������limnoria-2020.03.17/plugins/Internet/locales/�������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�020220� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Internet/locales/fi.po��������������������������������������������������0000644�0001750�0001750�00000004422�13634634532�021152� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: Internet plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 13:19+EET\n" "PO-Revision-Date: 2014-12-20 13:25+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.10\n" #: plugin.py:43 #, fuzzy msgid "" "Provides commands to query DNS, search WHOIS databases,\n" " and convert IPs to hex." msgstr "" "Tarjoaa komennot DNS-kyselyihin, hakemaan WHOIS-tietokannoista ja " "muuttamaan\n" " IP-osoitteet hexoiksi." #: plugin.py:48 msgid "" "<host|ip>\n" "\n" " Returns the ip of <host> or the reverse DNS hostname of <ip>.\n" " " msgstr "" "<isäntä|ip>\n" "\n" " Palauttaa <isännän> ip:een tai <ip:een> käänteisen isäntänimen.\n" " " #: plugin.py:55 plugin.py:70 msgid "Host not found." msgstr "Isäntää ei löytynyt." #: plugin.py:82 msgid "" "<domain>\n" "\n" " Returns WHOIS information on the registration of <domain>.\n" " " msgstr "" "<verkkotunnus>\n" "\n" " Palauttaa WHOIS tiedot <verkkotunnusten> rekisteröimisestä.\n" " " #: plugin.py:88 msgid "domain" msgstr "verkkotunnus" #: plugin.py:128 msgid "updated %s" msgstr "päivitetty %s" #: plugin.py:131 msgid "registered %s" msgstr "rekisteröity %s" #: plugin.py:134 msgid "expires %s" msgstr "vanhenee %s" #: plugin.py:154 msgid " <registered at %s>" msgstr " <rekisteröity aikaan %s>" #: plugin.py:156 msgid " <registered by %s>" msgstr " <rekisteröinyt %s>" #: plugin.py:161 msgid "%s%s is %L." msgstr "%s%s on %L." #: plugin.py:164 msgid "I couldn't find such a domain." msgstr "En voi löytää sellaista verkkotunnusta." #: plugin.py:169 msgid "" "<ip>\n" "\n" " Returns the hexadecimal IP for that IP.\n" " " msgstr "" "<ip>\n" "\n" " Palauttaa IP:een heksadesimaalisen IP:een IP:lle.\n" " " #~ msgid "Add the help for \"help Internet\" here." #~ msgstr "Lisää ohje \"help Internet:ille\" tähän." ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Internet/locales/fr.po��������������������������������������������������0000644�0001750�0001750�00000003346�13634634532�021167� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2010-10-17 15:20+CEST\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\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:43 msgid "Add the help for \"help Internet\" here." msgstr "" #: plugin.py:47 msgid "" "<host|ip>\n" "\n" " Returns the ip of <host> or the reverse DNS hostname of <ip>.\n" " " msgstr "" "<hôte|ip>\n" "\n" "Retourne l'ip de l'<hôte>, ou le reverse DNS de l'<ip>" #: plugin.py:54 #: plugin.py:61 #: plugin.py:65 msgid "Host not found." msgstr "Hôte non trouvé." #: plugin.py:77 msgid "" "<domain>\n" "\n" " Returns WHOIS information on the registration of <domain>.\n" " " msgstr "" "<domaine>\n" "\n" "Retourne les informations du WHOIS sur le <domaine>." #: plugin.py:83 msgid "domain" msgstr "domaine" #: plugin.py:112 msgid "updated %s" msgstr "mis à jour le %s" #: plugin.py:115 msgid "registered %s" msgstr "enregistré le %s" #: plugin.py:118 msgid "expires %s" msgstr "expire le %s" #: plugin.py:138 msgid " <registered at %s>" msgstr " <enregistré le %s>" #: plugin.py:140 msgid " <registered by %s>" msgstr " <enregistré par %s>" #: plugin.py:145 msgid "%s%s is %L." msgstr "%s%s est %L" #: plugin.py:148 msgid "I couldn't find such a domain." msgstr "Je ne peux trouver ce domaine." #: plugin.py:153 msgid "" "<ip>\n" "\n" " Returns the hexadecimal IP for that IP.\n" " " msgstr "" "<ip>\n" "\n" "Retourne l'IP hexadécimale pour cette IP." ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Internet/locales/it.po��������������������������������������������������0000644�0001750�0001750�00000003402�13634634532�021165� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-12 14:14+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugin.py:42 #, docstring msgid "Add the help for \"help Internet\" here." msgstr "" #: plugin.py:46 #, docstring msgid "" "<host|ip>\n" "\n" " Returns the ip of <host> or the reverse DNS hostname of <ip>.\n" " " msgstr "" "<host|ip>\n" "\n" " Restituisce l'ip di <host> o il DNS inverso di <ip>.\n" " " #: plugin.py:53 plugin.py:60 plugin.py:64 msgid "Host not found." msgstr "Host non trovato." #: plugin.py:76 #, docstring msgid "" "<domain>\n" "\n" " Returns WHOIS information on the registration of <domain>.\n" " " msgstr "" "<dominio>\n" "\n" " Restituisce le informazioni WHOIS sulla registrazione di <dominio>.\n" " " #: plugin.py:82 msgid "domain" msgstr "dominio" #: plugin.py:111 msgid "updated %s" msgstr "aggiornato il %s" #: plugin.py:114 msgid "registered %s" msgstr "registrato il %s" #: plugin.py:117 msgid "expires %s" msgstr "scade il %s" #: plugin.py:137 msgid " <registered at %s>" msgstr " <registrato il %s>" #: plugin.py:139 msgid " <registered by %s>" msgstr " <registrato da %s>" #: plugin.py:144 msgid "%s%s is %L." msgstr "%s%s è %L." #: plugin.py:147 msgid "I couldn't find such a domain." msgstr "Non riesco a trovare un dominio." #: plugin.py:152 #, docstring msgid "" "<ip>\n" "\n" " Returns the hexadecimal IP for that IP.\n" " " msgstr "" "<ip>\n" "\n" " Restituisce l'IP esadecimale per questo IP.\n" " " ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Internet/plugin.py������������������������������������������������������0000644�0001750�0001750�00000021213�13634634532�020437� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Jeremiah Fincher # Copyright (c) 2010-2011, James McCoy # 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 time import socket import telnetlib import supybot.conf as conf import supybot.utils as utils from supybot.commands import * from supybot.utils.iter import any import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Internet') class Internet(callbacks.Plugin): """Provides commands to query DNS, search WHOIS databases, and convert IPs to hex.""" threaded = True @internationalizeDocstring def dns(self, irc, msg, args, host): """<host|ip> Returns the ip of <host> or the reverse DNS hostname of <ip>. """ if utils.net.isIP(host): hostname = socket.getfqdn(host) if hostname == host: irc.reply(_('Host not found.')) else: irc.reply(hostname) else: try: ips = socket.getaddrinfo(host, None) ips = map(lambda x:x[4][0], ips) ordered_unique_ips = [] unique_ips = set() for ip in ips: if ip not in unique_ips: ordered_unique_ips.append(ip) unique_ips.add(ip) irc.reply(format('%L', ordered_unique_ips)) except socket.error: irc.reply(_('Host not found.')) dns = wrap(dns, ['something']) _domain = ['Domain Name', 'Server Name', 'domain'] _netrange = ['NetRange', 'inetnum'] _registrar = ['Sponsoring Registrar', 'Registrar', 'source'] _netname = ['NetName', 'inetname'] _updated = ['Last Updated On', 'Domain Last Updated Date', 'Updated Date', 'Last Modified', 'changed', 'last-modified'] _created = ['Created On', 'Domain Registration Date', 'Creation Date', 'created', 'RegDate'] _expires = ['Expiration Date', 'Domain Expiration Date'] _status = ['Status', 'Domain Status', 'status'] @internationalizeDocstring def whois(self, irc, msg, args, domain): """<domain> Returns WHOIS information on the registration of <domain>. """ if utils.net.isIP(domain): whois_server = 'whois.arin.net' usertld = 'ipaddress' elif '.' in domain: usertld = domain.split('.')[-1] whois_server = '%s.whois-servers.net' % usertld else: usertld = None whois_server = 'whois.iana.org' try: sock = utils.net.getSocket(whois_server, vhost=conf.supybot.protocols.irc.vhost(), vhostv6=conf.supybot.protocols.irc.vhostv6(), ) sock.connect((whois_server, 43)) except socket.error as e: irc.error(str(e)) return sock.settimeout(5) if usertld == 'com': sock.send(b'=') elif usertld == 'ipaddress': sock.send(b'n + ') sock.send(domain.encode('ascii')) sock.send(b'\r\n') s = b'' end_time = time.time() + 5 try: while end_time>time.time(): time.sleep(0.1) s += sock.recv(4096) except socket.error: pass sock.close() server = netrange = netname = registrar = updated = created = expires = status = '' for line in s.splitlines(): line = line.decode('utf8').strip() if not line or ':' not in line: continue if not server and any(line.startswith, self._domain): server = ':'.join(line.split(':')[1:]).strip().lower() # Let's add this check so that we don't respond with info for # a different domain. E.g., doing a whois for microsoft.com # and replying with the info for microsoft.com.wanadoodoo.com if server != domain: server = '' continue if not netrange and any(line.startswith, self._netrange): netrange = ':'.join(line.split(':')[1:]).strip() if not server and not netrange: continue if not registrar and any(line.startswith, self._registrar): registrar = ':'.join(line.split(':')[1:]).strip() elif not netname and any(line.startswith, self._netname): netname = ':'.join(line.split(':')[1:]).strip() elif not updated and any(line.startswith, self._updated): s = ':'.join(line.split(':')[1:]).strip() updated = _('updated %s') % s elif not created and any(line.startswith, self._created): s = ':'.join(line.split(':')[1:]).strip() created = _('registered %s') % s elif not expires and any(line.startswith, self._expires): s = ':'.join(line.split(':')[1:]).strip() expires = _('expires %s') % s elif not status and any(line.startswith, self._status): status = ':'.join(line.split(':')[1:]).strip().lower() if not status: status = 'unknown' try: t = telnetlib.Telnet('whois.pir.org', 43) except socket.error as e: irc.error(str(e)) return t.write(b'registrar ') t.write(registrar.split('(')[0].strip().encode('ascii')) t.write(b'\n') s = t.read_all() url = '' for line in s.splitlines(): line = line.decode('ascii').strip() if not line: continue if line.startswith('Email'): url = _(' <registered at %s>') % line.split('@')[-1] elif line.startswith('Registrar Organization:'): url = _(' <registered by %s>') % line.split(':')[1].strip() elif line == 'Not a valid ID pattern': url = '' if (server or netrange) and status: entity = server or 'Net range %s%s' % \ (netrange, ' (%s)' % netname if netname else '') info = filter(None, [status, created, updated, expires]) s = format(_('%s%s is %L.'), entity, url, info) irc.reply(s) else: irc.error(_('I couldn\'t find such a domain.')) whois = wrap(whois, ['lowered']) @internationalizeDocstring def hexip(self, irc, msg, args, ip): """<ip> Returns the hexadecimal IP for that IP. """ ret = "" if utils.net.isIPV4(ip): quads = ip.split('.') for quad in quads: i = int(quad) ret += '%02X' % i else: octets = ip.split(':') for octet in octets: if octet: i = int(octet, 16) ret += '%04X' % i else: missing = (8 - len(octets)) * 4 ret += '0' * missing irc.reply(ret) hexip = wrap(hexip, ['ip']) Internet = internationalizeDocstring(Internet) Class = Internet # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Internet/test.py��������������������������������������������������������0000644�0001750�0001750�00000004510�13634634532�020121� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Jeremiah Fincher # Copyright (c) 2010, James McCoy # 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 InternetTestCase(PluginTestCase): plugins = ('Internet',) if network: def testDns(self): self.assertNotError('dns slashdot.org') self.assertResponse('dns does.not.exist.', 'Host not found.') def testWhois(self): self.assertNotError('internet whois ohio-state.edu') self.assertNotError('internet whois microsoft.com') self.assertNotError('internet whois inria.fr') self.assertNotError('internet whois slime.com.au') self.assertNotError('internet whois 8.8.8.8') self.assertNotError('internet whois net') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Karma/������������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016041� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Karma/__init__.py�������������������������������������������������������0000644�0001750�0001750�00000004652�13634634532�020153� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Plugin for handling Karma stuff for a channel. """ 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__ = "" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Karma/config.py���������������������������������������������������������0000644�0001750�0001750�00000007455�13634634532�017665� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Karma') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Karma', True) Karma = conf.registerPlugin('Karma') conf.registerChannelValue(Karma, 'simpleOutput', registry.Boolean(False, _("""Determines whether the bot will output shorter versions of the karma output when requesting a single thing's karma."""))) conf.registerChannelValue(Karma, 'incrementChars', registry.SpaceSeparatedListOfStrings(['++'], _("""A space separated list of characters to increase karma."""))) conf.registerChannelValue(Karma, 'decrementChars', registry.SpaceSeparatedListOfStrings(['--'], _("""A space separated list of characters to decrease karma."""))) conf.registerChannelValue(Karma, 'response', registry.Boolean(False, _("""Determines whether the bot will reply with a success message when something's karma is increased or decreased."""))) conf.registerChannelValue(Karma, 'rankingDisplay', registry.Integer(3, _("""Determines how many highest/lowest karma things are shown when karma is called with no arguments."""))) conf.registerChannelValue(Karma, 'mostDisplay', registry.Integer(25, _("""Determines how many karma things are shown when the most command is called."""))) conf.registerChannelValue(Karma, 'allowSelfRating', registry.Boolean(False, _("""Determines whether users can adjust the karma of their nick."""))) conf.registerChannelValue(Karma, 'allowUnaddressedKarma', registry.Boolean(True, _("""Determines whether the bot will increase/decrease karma without being addressed."""))) conf.registerChannelValue(Karma, 'onlyNicks', registry.Boolean(False, _("""Determines whether the bot will only increase/decrease karma for nicks in the current channel."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Karma/locales/����������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017463� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Karma/locales/fi.po�����������������������������������������������������0000644�0001750�0001750�00000015022�13634634532�020413� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Karma plugin in Limnoria. # Copyright (C) 2011-2014 Limnoria # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011-2014. # msgid "" msgstr "" "Project-Id-Version: Karma plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 13:30+EET\n" "PO-Revision-Date: 2014-12-20 13:50+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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-Generator: Poedit 1.6.10\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: config.py:46 msgid "" "Determines whether the bot will output shorter\n" " versions of the karma output when requesting a single thing's karma." msgstr "" "Määrittää tulostaako botti lyhyemmän version\n" " karma ulostulosta, kun pyydetään yhden asian karmaa." #: config.py:49 msgid "" "A space separated list of\n" " characters to increase karma." msgstr "Välilyönneillä eroitettu lista merkeistä, jotka nostavat karmaa." #: config.py:52 msgid "" "A space separated list of\n" " characters to decrease karma." msgstr "Välilyöneillä eroitettu lista merkeistä, jotka laskevat karmaa." #: config.py:55 msgid "" "Determines whether the bot will reply with a\n" " success message when something's karma is increased or decreased." msgstr "" "Määrittää vastaako botti onnistumisviestillä, kun\n" " jonkin karmaa on nostettu tai laskettu." #: config.py:58 msgid "" "Determines how many highest/lowest karma things\n" " are shown when karma is called with no arguments." msgstr "" "Määrittää kuinka monta korkeinta/matalinta karma asiaa\n" " näytetään, kun karma pyydetään ilman parametriä." #: config.py:61 msgid "" "Determines how many karma things are shown when\n" " the most command is called." msgstr "" "Määrittää kuinka monta karma asiaa näytetään, kun\n" " kun \"most\" komento on pyydetty.'" #: config.py:64 msgid "" "Determines whether users can adjust the karma\n" " of their nick." msgstr "" "Määrittää pystyvätkö käyttäjän määrittämään\n" " nimimerkkinsä karman." #: config.py:67 msgid "" "Determines whether the bot will\n" " increase/decrease karma without being addressed." msgstr "" "Määrittää nostaako/vähentääkö botti karmaa\n" " ilman, että sille tarkoitetaan viestejä." #: plugin.py:222 msgid "Provides a simple tracker for setting Karma (thing++, thing--)." msgstr "Tarjoaa yksinkertaisen Karman seuraajan (jokin++, jokin--)." #: plugin.py:241 msgid "%(thing)s's karma is now %(karma)i" msgstr "%(thing)in karma on nyt %(karma)i" #: plugin.py:255 plugin.py:264 msgid "You're not allowed to adjust your own karma." msgstr "Sinä et saa määrittää omaa karmaasi." #: plugin.py:291 msgid "" "[<channel>] [<thing> ...]\n" "\n" " Returns the karma of <thing>. If <thing> is not given, returns the " "top\n" " N karmas, where N is determined by the config variable\n" " supybot.plugins.Karma.rankingDisplay. If one <thing> is given, " "returns\n" " the details of its karma; if more than one <thing> is given, " "returns\n" " the total karma of each of the things. <channel> is only necessary\n" " if the message isn't sent on the channel itself.\n" " " msgstr "" "[<Kanava>] [<asia> ...]\n" "\n" " Palauttaa <asian> karman. Jos <asiaa> ei ole annettu, palauttaa " "TOP\n" " N karmat, missä N on asetusarvon\n" " supybot.plugins.Karma.rankingDisplay määrittämä. Jos yksi <asia> on " "annettu, palauttaa\n" " tiedot sen karmasta; jos useampi kuin yksi <asia> on annettu, " "tarkistaa jokaisen asian\n" " yhteiskarman. <Kanava> on vaadittu vain, jos\n" " viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:304 msgid "%s has neutral karma." msgstr "%s:llä on neutraali karma." #: plugin.py:311 msgid "" "Karma for %q has been increased %n and decreased %n for a total karma of %s." msgstr "Karma %q:lle on noussut %n ja laskenut %n yhteiskarmalle %s." #: plugin.py:313 plugin.py:314 msgid "time" msgstr "aika" #: plugin.py:327 msgid "I didn't know the karma for any of those things." msgstr "Minä en tiennyt yhtäkään noiden asioiden karmoista." #: plugin.py:337 plugin.py:366 msgid "I have no karma for this channel." msgstr "Minulla ei ole karmaa tälle kanavalle." #: plugin.py:342 msgid " You (%s) are ranked %i out of %i." msgstr "Sinä olet rankingissa (%s) %i %i:stä." #: plugin.py:346 msgid "Highest karma: %L. Lowest karma: %L.%s" msgstr "Korkein karma: %L. Alhaisin karma: %L.%s" #: plugin.py:354 msgid "" "[<channel>] {increased,decreased,active}\n" "\n" " Returns the most increased, the most decreased, or the most active\n" " (the sum of increased and decreased) karma things. <channel> is " "only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] {increased,decreased,active}\n" "\n" " Palauttaa eniten nousseen (\"increased\"), eniten laskeneen " "(\"decreased\"), tai aktiivisimman (\"active\")\n" " (nousseiden ja laskeneiden) karma asiat. <Kanava> on vaadittu vain, " "jos\n" " viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:372 msgid "" "[<channel>] [<name>]\n" "\n" " Resets the karma of <name> to 0. If <name> is not given, resets\n" " everything.\n" " " msgstr "" "[<kanava>] <nimi>\n" "\n" " Asettaa <nimen> karman arvoksi 0. Jos <nimeä> ei ole annettu, asettaa " "kaiken\n" " arvoksi 0." #: plugin.py:383 msgid "" "[<channel>] <filename>\n" "\n" " Dumps the Karma database for <channel> to <filename> in the bot's\n" " data directory. <channel> is only necessary if the message isn't " "sent\n" " in the channel itself.\n" " " msgstr "" "[<kanava>] <tiedostonimi>\n" "\n" " Tallentaa <kanavan> <tiedostonimeen> botin\n" " \"data\" hakemistoon. <Kanava> on vaadittu vain, jos viestiä ei " "lähetetä\n" " kanavalla itsellään.\n" " " #: plugin.py:395 msgid "" "[<channel>] <filename>\n" "\n" " Loads the Karma database for <channel> from <filename> in the bot's\n" " data directory. <channel> is only necessary if the message isn't " "sent\n" " in the channel itself.\n" " " msgstr "" "[<kanava>] <tiedostonimi>\n" "\n" " Lataa Karma tietokannan <kanavalle> <tiedostonimestä> botin\n" " \"data\" hakemistosta. <Kanava> on vaadittu vain, jos viestiä ei " "lähetetä\n" " kanavalla itsellään.\n" " " ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Karma/locales/fr.po�����������������������������������������������������0000644�0001750�0001750�00000013012�13634634532�020421� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-01-22 13:46+CET\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: Limnoria <progval@gmail.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" "Language: fr\n" #: config.py:46 msgid "" "Determines whether the bot will output shorter\n" " versions of the karma output when requesting a single thing's karma." msgstr "" "Détermine si le bot donnera comme sortie une version plus courte du karma " "lorsqu'on lui demandera un seul karma." #: config.py:49 msgid "" "Determines whether the bot will reply with a\n" " success message when something's karma is increased or decreased." msgstr "" "Détermine si le bot répondra avec un message de succès si un karma est " "augmenté ou diminué." #: config.py:52 msgid "" "Determines how many highest/lowest karma things\n" " are shown when karma is called with no arguments." msgstr "" "Détermine combien de plus haut/bas karmas sont affichés lorsque karma est " "appelé sans argument." #: config.py:55 msgid "" "Determines how many karma things are shown when\n" " the most command is called." msgstr "" "Détermine combien de karmas sont affichés lorsque la commande « most » est " "appelée." #: config.py:58 msgid "" "Determines whether users can adjust the karma\n" " of their nick." msgstr "Détermine si les utilisateurs peuvent ajuster le karma de leur nick." #: config.py:61 msgid "" "Determines whether the bot will\n" " increase/decrease karma without being addressed." msgstr "" "Détermine si le bot augmentera/diminuera le karma sans que l'on s'adresse à " "lui." #: plugin.py:234 msgid "%(thing)s's karma is now %(karma)i" msgstr "Le karma de %(thing)s est maintenant %(karma)i" #: plugin.py:254 plugin.py:263 msgid "You're not allowed to adjust your own karma." msgstr "Vous n'êtes pas autorisé à modifier votre propre karma." #: plugin.py:284 msgid "" "[<channel>] [<thing> ...]\n" "\n" " Returns the karma of <thing>. If <thing> is not given, returns the " "top\n" " N karmas, where N is determined by the config variable\n" " supybot.plugins.Karma.rankingDisplay. If one <thing> is given, " "returns\n" " the details of its karma; if more than one <thing> is given, " "returns\n" " the total karma of each of the things. <channel> is only necessary\n" " if the message isn't sent on the channel itself.\n" " " msgstr "" "[<canal>] [<objet> ...]\n" "\n" "Retourne le karma de l'<objet>. Si l'<objet> n'est pas donné, retourne les " "trois premiers et derniers karmas. Si une <chose> est donnée, retourne les " "détails de son karma ; si plus d'une <chose> est donnée, retourne le karma " "total de chacune de ces choses. Le <canal> n'est nécessaire que si la " "commande n'est pas envoyée sur le canal lui-même." #: plugin.py:297 msgid "%s has neutral karma." msgstr "%s a un karma neutre." #: plugin.py:304 msgid "" "Karma for %q has been increased %n and decreased %n for a total karma of %s." msgstr "" "Le karma de %q a été augmenté %n fois et diminué %n fois, pour un karma " "total de %s." #: plugin.py:306 plugin.py:307 msgid "time" msgstr "<empty>" #: plugin.py:320 msgid "I didn't know the karma for any of those things." msgstr "Je ne connais le karma d'aucune de ces choses." #: plugin.py:330 plugin.py:359 msgid "I have no karma for this channel." msgstr "Je n'ai pas de karma pour ce canal." #: plugin.py:335 msgid " You (%s) are ranked %i out of %i." msgstr " Vous (%s) êtes #%i sur %i" #: plugin.py:339 msgid "Highest karma: %L. Lowest karma: %L.%s" msgstr "Plus haut karma : %L. Plus bas karma : %L.%s" #: plugin.py:347 msgid "" "[<channel>] {increased,decreased,active}\n" "\n" " Returns the most increased, the most decreased, or the most active\n" " (the sum of increased and decreased) karma things. <channel> is " "only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] {increased,decreased,active}\n" "\n" "Retourne le plus augmenté (increased), le plus descendu (decreased), ou le " "plus actif (la somme des montées et descentes) des karmas. <canal> n'est " "nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:365 msgid "" "[<channel>] <name>\n" "\n" " Resets the karma of <name> to 0.\n" " " msgstr "" "[<canal>] <nom>\n" "\n" "Redéfinit le karma de <nom> à 0." #: plugin.py:375 msgid "" "[<channel>] <filename>\n" "\n" " Dumps the Karma database for <channel> to <filename> in the bot's\n" " data directory. <channel> is only necessary if the message isn't " "sent\n" " in the channel itself.\n" " " msgstr "" "[<canal>] <nom de fichier>\n" "\n" "Exporte la base de données des Karma du <canal> dans le <nom de fichier> " "dans le répertoire de données du bot. <canal> n'est nécessaire que si le " "message n'est pas envoyé sur le canal lui-même." #: plugin.py:387 msgid "" "[<channel>] <filename>\n" "\n" " Loads the Karma database for <channel> from <filename> in the bot's\n" " data directory. <channel> is only necessary if the message isn't " "sent\n" " in the channel itself.\n" " " msgstr "" "[<canal>] <nom de fichier>\n" "\n" "Charge la base de données des Karma du <canal> du <nom de fichier> dans le " "répertoire de données du bot. <canal> n'est nécessaire que si le message " "n'est pas envoyé sur le canal lui-même." ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Karma/locales/it.po�����������������������������������������������������0000644�0001750�0001750�00000013033�13634634532�020431� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-28 10:43+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:46 msgid "" "Determines whether the bot will output shorter\n" " versions of the karma output when requesting a single thing's karma." msgstr "" "Determina se il bot mostrerà una versione più corta del karma\n" " quando viene richiesto per un singolo oggetto." #: config.py:49 msgid "" "Determines whether the bot will reply with a\n" " success message when something's karma is increased or decreased." msgstr "" "Determina se il bot risponderà con un messaggio di successo quando\n" " viene aumentato o diminuito il karma di qualcosa." #: config.py:52 msgid "" "Determines how many highest/lowest karma things\n" " are shown when karma is called with no arguments." msgstr "" "Determina quanti karma più/meno vengono mostrati quando richiamato senza argomenti.\n" #: config.py:55 msgid "" "Determines how many karma things are shown when\n" " the most command is called." msgstr "" "Determina quanti karma vengono mostrati richiamando il comando \"most\"." #: config.py:58 msgid "" "Determines whether users can adjust the karma\n" " of their nick." msgstr "" "Determina se gli utenti possano modificare il karma del loro nick." #: config.py:61 msgid "" "Determines whether the bot will\n" " increase/decrease karma without being addressed." msgstr "" "Determina se il bot aumenterà o diminuirà il karma senza essere richiamato." #: plugin.py:254 plugin.py:263 msgid "You're not allowed to adjust your own karma." msgstr "Non ti è permesso di modificare il tuo karma." #: plugin.py:284 #, docstring msgid "" "[<channel>] [<thing> ...]\n" "\n" " Returns the karma of <thing>. If <thing> is not given, returns the top\n" " N karmas, where N is determined by the config variable\n" " supybot.plugins.Karma.rankingDisplay. If one <thing> is given, returns\n" " the details of its karma; if more than one <thing> is given, returns\n" " the total karma of each of the things. <channel> is only necessary\n" " if the message isn't sent on the channel itself.\n" " " msgstr "" "[<canale>] [<oggetto> ...]\n" "\n" " Riporta il karma di <oggetto>. Se <oggetto> non è fornito, restituisce i primi\n" " N karma, dove N è determinato dalla variabile supybot.plugins.Karma.rankingDisplay.\n" " Se viene specificato un <oggetto>, riporta i dettagli del suo karma; se ne vengono \n" " indicati più di uno, riporta il numero totale di karma di ciascuno degli oggetti.\n" " <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:297 msgid "%s has neutral karma." msgstr "%s ha un karma neutro." #: plugin.py:304 msgid "Karma for %q has been increased %n and decreased %n for a total karma of %s." msgstr "Il karma per %q è stato aumentato di %n e diminuito di %n per un totale di %s." #: plugin.py:306 plugin.py:307 msgid "time" msgstr "volta" #: plugin.py:320 msgid "I didn't know the karma for any of those things." msgstr "Non conosco il karma di nessuno di questi oggetti." #: plugin.py:330 plugin.py:359 msgid "I have no karma for this channel." msgstr "Non ho karma per questo canale." #: plugin.py:335 msgid " You (%s) are ranked %i out of %i." msgstr " %s, sei valutato %i su %i." #: plugin.py:339 msgid "Highest karma: %L. Lowest karma: %L.%s" msgstr "Karma più alto: %L. Karma più basso: %L.%s" #: plugin.py:347 #, docstring msgid "" "[<channel>] {increased,decreased,active}\n" "\n" " Returns the most increased, the most decreased, or the most active\n" " (the sum of increased and decreased) karma things. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] {increased,decreased,active}\n" "\n" " Riporta il karma maggiormente aumentato (increased), diminuito (decreased)\n" ", o più attivo (active) (la somma di aumentato e diminuito). <canale> è \n" " necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:365 #, docstring msgid "" "[<channel>] <name>\n" "\n" " Resets the karma of <name> to 0.\n" " " msgstr "" "[<cannle>] <nome>\n" "\n" " Azzera i karma di <nome>.\n" " " #: plugin.py:375 #, docstring msgid "" "[<channel>] <filename>\n" "\n" " Dumps the Karma database for <channel> to <filename> in the bot's\n" " data directory. <channel> is only necessary if the message isn't sent\n" " in the channel itself.\n" " " msgstr "" "[<canale>] <nomefile>\n" "\n" " Esporta il database dei karma di <canale> in <nomefile> nella directory dei dati\n" " del bot. <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:387 #, docstring msgid "" "[<channel>] <filename>\n" "\n" " Loads the Karma database for <channel> from <filename> in the bot's\n" " data directory. <channel> is only necessary if the message isn't sent\n" " in the channel itself.\n" " " msgstr "" "[<canale>] <filename>\n" "\n" " Carica il database dei karma di <canale> da <nomefile> nella directory dei dati\n" " <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Karma/plugin.py���������������������������������������������������������0000644�0001750�0001750�00000042116�13634634532�017707� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # Copyright (c) 2010, James McCoy # 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 os import sys import csv import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.utils.minisix as minisix import supybot.plugins as plugins import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Karma') import sqlite3 def checkAllowShell(irc): if not conf.supybot.commands.allowShell(): irc.error('This command is not available, because ' 'supybot.commands.allowShell is False.', Raise=True) class SqliteKarmaDB(object): def __init__(self, filename): self.dbs = ircutils.IrcDict() self.filename = filename def close(self): for db in self.dbs.values(): db.close() def _getDb(self, channel): filename = plugins.makeChannelFilename(self.filename, channel) if filename in self.dbs: return self.dbs[filename] if os.path.exists(filename): db = sqlite3.connect(filename, check_same_thread=False) if minisix.PY2: db.text_factory = str self.dbs[filename] = db return db db = sqlite3.connect(filename, check_same_thread=False) if minisix.PY2: db.text_factory = str self.dbs[filename] = db cursor = db.cursor() cursor.execute("""CREATE TABLE karma ( id INTEGER PRIMARY KEY, name TEXT, normalized TEXT UNIQUE ON CONFLICT IGNORE, added INTEGER, subtracted INTEGER )""") db.commit() def p(s1, s2): return int(ircutils.nickEqual(s1, s2)) db.create_function('nickeq', 2, p) return db def get(self, channel, thing): db = self._getDb(channel) thing = thing.lower() cursor = db.cursor() cursor.execute("""SELECT added, subtracted FROM karma WHERE normalized=?""", (thing,)) results = cursor.fetchall() if len(results) == 0: return None else: return list(map(int, results[0])) def gets(self, channel, things): db = self._getDb(channel) cursor = db.cursor() normalizedThings = dict(list(zip([s.lower() for s in things], things))) criteria = ' OR '.join(['normalized=?'] * len(normalizedThings)) sql = """SELECT name, added-subtracted FROM karma WHERE %s ORDER BY added-subtracted DESC""" % criteria cursor.execute(sql, list(normalizedThings.keys())) L = [(name, int(karma)) for (name, karma) in cursor.fetchall()] for (name, _) in L: del normalizedThings[name.lower()] neutrals = list(normalizedThings.values()) neutrals.sort() return (L, neutrals) def top(self, channel, limit): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT name, added-subtracted FROM karma 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 ?""", (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=?""", (thing,)) results = cursor.fetchall() if len(results) == 0: return None karma = int(results[0][0]) cursor.execute("""SELECT COUNT(*) FROM karma WHERE added-subtracted > ?""", (karma,)) rank = int(cursor.fetchone()[0]) return rank+1 def size(self, channel): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT COUNT(*) FROM karma""") return int(cursor.fetchone()[0]) def increment(self, channel, name): db = self._getDb(channel) cursor = db.cursor() normalized = name.lower() cursor.execute("""INSERT INTO karma VALUES (NULL, ?, ?, 0, 0)""", (name, normalized,)) cursor.execute("""UPDATE karma SET added=added+1 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, ?, ?, 0, 0)""", (name, normalized,)) cursor.execute("""UPDATE karma SET subtracted=subtracted+1 WHERE normalized=?""", (normalized,)) db.commit() def most(self, channel, kind, limit): if kind == 'increased': orderby = 'added' elif kind == 'decreased': orderby = 'subtracted' elif kind == 'active': orderby = 'added+subtracted' else: raise ValueError('invalid kind') sql = """SELECT name, %s FROM karma ORDER BY %s DESC LIMIT %s""" % \ (orderby, orderby, limit) db = self._getDb(channel) cursor = db.cursor() cursor.execute(sql) return [(name, int(i)) for (name, i) in cursor.fetchall()] def clear(self, channel, name=None): db = self._getDb(channel) cursor = db.cursor() if name: normalized = name.lower() cursor.execute("""DELETE FROM karma WHERE normalized=?""", (normalized,)) else: cursor.execute("""DELETE FROM karma""") db.commit() def dump(self, channel, filename): filename = conf.supybot.directories.data.dirize(filename) fd = utils.file.AtomicFile(filename) out = csv.writer(fd) db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT name, added, subtracted FROM karma""") for (name, added, subtracted) in cursor.fetchall(): out.writerow([name, added, subtracted]) fd.close() def load(self, channel, filename): filename = conf.supybot.directories.data.dirize(filename) fd = open(filename) reader = csv.reader(fd) db = self._getDb(channel) cursor = db.cursor() cursor.execute("""DELETE FROM karma""") for (name, added, subtracted) in reader: normalized = name.lower() cursor.execute("""INSERT INTO karma VALUES (NULL, ?, ?, ?, ?)""", (name, normalized, added, subtracted,)) db.commit() fd.close() KarmaDB = plugins.DB('Karma', {'sqlite3': SqliteKarmaDB}) class Karma(callbacks.Plugin): """Provides a simple tracker for setting Karma (thing++, thing--).""" callBefore = ('Factoids', 'MoobotFactoids', 'Infobot') def __init__(self, irc): self.__parent = super(Karma, self) self.__parent.__init__(irc) self.db = KarmaDB() def die(self): self.__parent.die() self.db.close() def _normalizeThing(self, thing): assert thing if thing[0] == '(' and thing[-1] == ')': thing = thing[1:-1] return thing def _respond(self, irc, channel, thing, karma): if self.registryValue('response', channel, irc.network): irc.reply(_('%(thing)s\'s karma is now %(karma)i') % {'thing': thing, 'karma': karma}) else: irc.noReply() def _doKarma(self, irc, msg, channel, thing): inc = self.registryValue('incrementChars', channel, irc.network) dec = self.registryValue('decrementChars', channel, irc.network) onlynicks = self.registryValue('onlyNicks', channel, irc.network) karma = '' for s in inc: if thing.endswith(s): thing = thing[:-len(s)] # Don't reply if the target isn't a nick if onlynicks and thing.lower() not in map(ircutils.toLower, irc.state.channels[channel].users): return if ircutils.strEqual(thing, msg.nick) and \ not self.registryValue('allowSelfRating', channel, irc.network): irc.error(_('You\'re not allowed to adjust your own karma.')) return self.db.increment(channel, self._normalizeThing(thing)) karma = self.db.get(channel, self._normalizeThing(thing)) for s in dec: if thing.endswith(s): thing = thing[:-len(s)] if onlynicks and thing.lower() not in map(ircutils.toLower, irc.state.channels[channel].users): return if ircutils.strEqual(thing, msg.nick) and \ not self.registryValue('allowSelfRating', channel, irc.network): irc.error(_('You\'re not allowed to adjust your own karma.')) return self.db.decrement(channel, self._normalizeThing(thing)) karma = self.db.get(channel, self._normalizeThing(thing)) if karma: self._respond(irc, channel, thing, karma[0]-karma[1]) def invalidCommand(self, irc, msg, tokens): if msg.channel and tokens: thing = ' '.join(tokens) self._doKarma(irc, msg, msg.channel, thing) def doPrivmsg(self, irc, msg): # We don't handle this if we've been addressed because invalidCommand # will handle it for us. This prevents us from accessing the db twice # and therefore crashing. if not (msg.addressed or msg.repliedTo): if msg.channel and \ not ircmsgs.isCtcp(msg) and \ self.registryValue('allowUnaddressedKarma', msg.channel, irc.network): irc = callbacks.SimpleProxy(irc, msg) thing = msg.args[1].rstrip() self._doKarma(irc, msg, msg.channel, thing) @internationalizeDocstring def karma(self, irc, msg, args, channel, things): """[<channel>] [<thing> ...] Returns the karma of <thing>. If <thing> is not given, returns the top N karmas, where N is determined by the config variable supybot.plugins.Karma.rankingDisplay. If one <thing> is given, returns the details of its karma; if more than one <thing> is given, returns the total karma of each of the things. <channel> is only necessary if the message isn't sent on the channel itself. """ if len(things) == 1: name = things[0] t = self.db.get(channel, name) if t is None: irc.reply(format(_('%s has neutral karma.'), name)) else: (added, subtracted) = t total = added - subtracted if self.registryValue('simpleOutput', channel, irc.network): s = format('%s: %i', name, total) else: s = format(_('Karma for %q has been increased %n and ' 'decreased %n for a total karma of %s.'), name, (added, _('time')), (subtracted, _('time')), total) irc.reply(s) elif len(things) > 1: (L, neutrals) = self.db.gets(channel, things) if L: s = format('%L', [format('%s: %i', *t) for t in L]) if neutrals: neutral = format('. %L %h neutral karma', neutrals, len(neutrals)) s += neutral irc.reply(s + '.') else: irc.reply(_('I didn\'t know the karma for any of those ' 'things.')) else: # No name was given. Return the top/bottom N karmas. limit = self.registryValue('rankingDisplay', channel, irc.network) highest = [format('%q (%s)', s, t) for (s, t) in self.db.top(channel, limit)] lowest = [format('%q (%s)', s, t) for (s, t) in self.db.bottom(channel, limit)] if not (highest and lowest): irc.error(_('I have no karma for this channel.')) return rank = self.db.rank(channel, msg.nick) if rank is not None: total = self.db.size(channel) rankS = format(_(' You (%s) are ranked %i out of %i.'), msg.nick, rank, total) else: rankS = '' s = format(_('Highest karma: %L. Lowest karma: %L.%s'), highest, lowest, rankS) irc.reply(s) karma = wrap(karma, ['channel', any('something')]) _mostAbbrev = utils.abbrev(['increased', 'decreased', 'active']) @internationalizeDocstring def most(self, irc, msg, args, channel, kind): """[<channel>] {increased,decreased,active} Returns the most increased, the most decreased, or the most active (the sum of increased and decreased) karma things. <channel> is only necessary if the message isn't sent in the channel itself. """ L = self.db.most(channel, kind, self.registryValue('mostDisplay', channel, irc.network)) if L: L = [format('%q: %i', name, i) for (name, i) in L] irc.reply(format('%L', L)) else: irc.error(_('I have no karma for this channel.')) most = wrap(most, ['channel', ('literal', ['increased', 'decreased', 'active'])]) @internationalizeDocstring def clear(self, irc, msg, args, channel, name): """[<channel>] [<name>] Resets the karma of <name> to 0. If <name> is not given, resets everything. """ self.db.clear(channel, name or None) irc.replySuccess() clear = wrap(clear, [('checkChannelCapability', 'op'), optional('text')]) @internationalizeDocstring def dump(self, irc, msg, args, channel, filename): """[<channel>] <filename> Dumps the Karma database for <channel> to <filename> in the bot's data directory. <channel> is only necessary if the message isn't sent in the channel itself. """ checkAllowShell(irc) self.db.dump(channel, filename) irc.replySuccess() dump = wrap(dump, [('checkCapability', 'owner'), 'channeldb', 'filename']) @internationalizeDocstring def load(self, irc, msg, args, channel, filename): """[<channel>] <filename> Loads the Karma database for <channel> from <filename> in the bot's data directory. <channel> is only necessary if the message isn't sent in the channel itself. """ checkAllowShell(irc) self.db.load(channel, filename) irc.replySuccess() load = wrap(load, [('checkCapability', 'owner'), 'channeldb', 'filename']) Class = Karma # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Karma/test.py�����������������������������������������������������������0000644�0001750�0001750�00000023276�13634634532�017376� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * import sqlite3 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 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): 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 testClear(self): self.assertNoResponse('foo++', 1) self.assertRegexp('karma foo', '1') self.assertNotError('karma clear foo') self.assertRegexp('karma foo', 'neutral') 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 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, 'is now') self.assertSnarfRegexp('foo%s' % m, 'is now') #self.assertNoResponse('foo bar%s' % m) #self.assertSnarfNoResponse('foo bar%s' % m) self.assertRegexp('(foo bar)%s' % m, 'is now') self.assertSnarfRegexp('(foo bar)%s' % m, 'is now') finally: karma.response.setValue(resp) karma.allowUnaddressedKarma.setValue(unaddressed) def testOnlyNicks(self): # We use this to join a dummy user to test upon msg = ircmsgs.join(self.channel, prefix='hello!foo@bar') self.irc.feedMsg(msg) karma = conf.supybot.plugins.Karma resp = karma.response() onlynicks = karma.onlyNicks() try: karma.onlynicks.setValue(True) karma.response.setValue(True) self.assertSnarfNoResponse('abcd++') self.assertSnarfRegexp('hello--', 'is now') self.assertSnarfNoResponse('abcd--') self.assertSnarfRegexp('hello++', 'is now') finally: karma.onlynicks.setValue(onlynicks) karma.response.setValue(resp) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Lart/�������������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�015710� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Lart/__init__.py��������������������������������������������������������0000644�0001750�0001750�00000004674�13634634532�020026� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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. ### """ This plugin keeps a database of larts, and larts with it. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.strike __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������limnoria-2020.03.17/plugins/Lart/config.py����������������������������������������������������������0000644�0001750�0001750�00000005113�13634634532�017521� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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 from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Lart') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Lart', True) Lart = conf.registerPlugin('Lart') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Lart, 'someConfigVariableName', # registry.Boolean(False, _("""Help for someConfigVariableName."""))) conf.registerChannelValue(Lart, 'showIds', registry.Boolean(False, _("""Determines whether the bot will show the ids of a lart when the lart is given."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Lart/locales/�����������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017332� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Lart/locales/fi.po������������������������������������������������������0000644�0001750�0001750�00000004135�13634634532�020265� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen, <mikaela.suomalainen@outlook.com> 2011. # msgid "" msgstr "" "Project-Id-Version: Lart plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 11:59+EET\n" "PO-Revision-Date: 2014-12-20 12:04+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.10\n" #: config.py:49 msgid "" "Determines whether the bot will show the ids\n" " of a lart when the lart is given." msgstr "" "Määrittää näyttääkö botti lartin id:eet\n" " , kun lart annetaan." #: plugin.py:39 #, fuzzy msgid "" "Provides an implementation of the Luser Attitude Readjustment Tool\n" " for users." msgstr "Tarjoaa käyttäjille Luuserin Asenteen Uudelleenmääritys Työkalun." #: plugin.py:50 msgid "Larts must contain $who." msgstr "Larttien täytyy sisältää $who." #: plugin.py:54 msgid "" "[<channel>] [<id>] <who|what> [for <reason>]\n" "\n" " Uses the Luser Attitude Readjustment Tool on <who|what> (for " "<reason>,\n" " if given). If <id> is given, uses that specific lart. <channel> " "is\n" " only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] [<id>] <kuka|mikä> [for <syystä>]\n" "\n" " Käyttää \"Luser Attitude Readjustment Tool\" <keneen|mihint> (for " "<syystä>,\n" " jos annettu). Jos <id> on annettu, käyttää sitä tiettyä Larttia. " "<kanava> on\n" " vaadittu vain jos viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:68 msgid "There is no lart with id #%i." msgstr "Tuolla ID:llä ei ole larttia #%i." #: plugin.py:73 msgid "There are no larts in my database for %s." msgstr "Minun tietokannassani ei ole larttia %s:lle." #: plugin.py:79 msgid "trying to dis me" msgstr "yrittämässä dissata minua" #: plugin.py:87 msgid " for " msgstr "syystä" �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Lart/locales/fr.po������������������������������������������������������0000644�0001750�0001750�00000003267�13634634532�020303� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2010-10-17 15:21+CEST\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\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" #: config.py:49 msgid "" "Determines whether the bot will show the ids\n" " of a lart when the lart is given." msgstr "Détermine si le bot affichera les idées d'un lart lorsqu'il est donné." #: plugin.py:48 msgid "Larts must contain $who." msgstr "Les larts doivent contenir $who." #: plugin.py:52 msgid "" "[<channel>] [<id>] <who|what> [for <reason>]\n" "\n" " Uses the Luser Attitude Readjustment Tool on <who|what> (for <reason>,\n" " if given). If <id> is given, uses that specific lart. <channel> is\n" " only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] [<id>] [<qui|quoi> [for <raison>]\n" "\n" "Utilise le Luser Attitude Readjustment Tool sur <qui|quoi> (pour la <raison>, si elle est donnée). Si l'<id> est donné, utilise un LART spécifique. <canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:66 msgid "There is no lart with id #%i." msgstr "Il n'y a pas de lart d'id #%i." #: plugin.py:71 msgid "There are no larts in my database for %s." msgstr "Il n'y a pas de lart dans ma base de données pour %s." #: plugin.py:77 msgid "trying to dis me" msgstr "essaye de me manquer de respect" #: plugin.py:85 msgid " for " msgstr " pour " �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Lart/locales/it.po������������������������������������������������������0000644�0001750�0001750�00000003201�13634634532�020274� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-12 14:41+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:49 msgid "" "Determines whether the bot will show the ids\n" " of a lart when the lart is given." msgstr "" "Determina se il bot mostrerà gli id\n" " di un lart quando questo viene dato." #: plugin.py:48 msgid "Larts must contain $who." msgstr "I lart devono contenere $who." #: plugin.py:52 #, docstring msgid "" "[<channel>] [<id>] <who|what> [for <reason>]\n" "\n" " Uses the Luser Attitude Readjustment Tool on <who|what> (for <reason>,\n" " if given). If <id> is given, uses that specific lart. <channel> is\n" " only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] [<id>] <chi|cosa> [per <motivo>]\n" "\n" " Utilizza il Luser Attitude Readjustment Tool su <chi|cosa> (per <motivo>,\n" " se fornito). Se <id> viene dato, usa quello specifico lart. <canale> è\n" " necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:66 msgid "There is no lart with id #%i." msgstr "Non c'è nessun lart con l'id #%i." #: plugin.py:71 msgid "There are no larts in my database for %s." msgstr "Non ci sono lart per %s nel mio database." #: plugin.py:77 msgid "trying to dis me" msgstr "cercando di mancarmi di rispetto" #: plugin.py:85 msgid " for " msgstr " per" �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Lart/plugin.py����������������������������������������������������������0000644�0001750�0001750�00000007767�13634634532�017573� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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 re from supybot.commands import * import supybot.plugins as plugins import supybot.ircutils as ircutils from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Lart') class Lart(plugins.ChannelIdDatabasePlugin): """Provides an implementation of the Luser Attitude Readjustment Tool for users.""" _meRe = re.compile(r'\bme\b', re.I) _myRe = re.compile(r'\bmy\b', re.I) def _replaceFirstPerson(self, s, nick): s = self._meRe.sub(nick, s) s = self._myRe.sub('%s\'s' % nick, s) return s def addValidator(self, irc, text): if '$who' not in text: irc.error(_('Larts must contain $who.'), Raise=True) @internationalizeDocstring def lart(self, irc, msg, args, channel, id, text): """[<channel>] [<id>] <who|what> [for <reason>] Uses the Luser Attitude Readjustment Tool on <who|what> (for <reason>, if given). If <id> is given, uses that specific lart. <channel> is only necessary if the message isn't sent in the channel itself. """ if ' for ' in text: (target, reason) = list(map(str.strip, text.split(' for ', 1))) else: (target, reason) = (text, '') if id is not None: try: lart = self.db.get(channel, id) except KeyError: irc.error(format(_('There is no lart with id #%i.'), id)) return else: lart = self.db.random(channel) if not lart: irc.error(format(_('There are no larts in my database ' 'for %s.'), channel)) return text = lart.text if ircutils.strEqual(target, irc.nick): target = msg.nick reason = self._replaceFirstPerson(_('trying to dis me'), irc.nick) else: target = self._replaceFirstPerson(target, msg.nick) reason = self._replaceFirstPerson(reason, msg.nick) if target.endswith('.'): target = target.rstrip('.') text = text.replace('$who', target) if reason: text += _(' for ') + reason if self.registryValue('showIds', channel, irc.network): text += format(' (#%i)', lart.id) irc.reply(text, action=True) lart = wrap(lart, ['channeldb', optional('id'), 'text']) Class = Lart # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������limnoria-2020.03.17/plugins/Lart/test.py������������������������������������������������������������0000644�0001750�0001750�00000004753�13634634532�017244� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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 LartTestCase(ChannelPluginTestCase): plugins = ('Lart', 'User') def setUp(self): ChannelPluginTestCase.setUp(self) # Create a valid user to use self.prefix = 'mf!bar@baz' self.irc.feedMsg(ircmsgs.privmsg(self.nick, 'register tester moo', prefix=self.prefix)) m = self.irc.takeMsg() # Response to register. def testAdd(self): self.assertError('lart add foo') # needs $who self.assertNotError('lart add smacks $who') def testLart(self): self.assertError('lart foo') # no praises! self.assertNotError('lart add smacks $who') self.assertAction('lart foo', 'smacks foo') def testMeInReason(self): self.assertNotError('lart add makes $who sit by me') self.assertAction('lart foo', 'makes foo sit by me') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������limnoria-2020.03.17/plugins/Later/������������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016055� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Later/__init__.py�������������������������������������������������������0000644�0001750�0001750�00000005056�13634634532�020166� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Informal notes, mostly for compatibility with other bots. Based entirely on nicks, it's an easy way to tell users who refuse to register notes when they arrive later. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Later/config.py���������������������������������������������������������0000644�0001750�0001750�00000006451�13634634532�017674� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Later') def configure(advanced): # This will be called by setup.py to configure this module. Advanced is # a bool that specifies whether the user identified themself 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('Later', True) Later = conf.registerPlugin('Later') conf.registerGlobalValue(Later, 'maximum', registry.NonNegativeInteger(0, _("""Determines the maximum number of messages to be queued for a user. If this value is 0, there is no maximum. """))) conf.registerGlobalValue(Later, 'private', registry.Boolean(False, _("""Determines whether users will be notified in the first place in which they're seen, or in private."""))) conf.registerGlobalValue(Later, 'tellOnJoin', registry.Boolean(False, _("""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."""))) conf.registerGroup(Later, 'format') conf.registerGlobalValue(Later.format, 'senderHostname', registry.Boolean(False, _("""Determines whether senders' hostname will be shown in messages (instead of just the nick)."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Later/locales/����������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017477� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Later/locales/de.po�����������������������������������������������������0000644�0001750�0001750�00000012564�13634634532�020431� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2011-08-10 11:28+CEST\n" "PO-Revision-Date: 2011-11-02 00:00+0100\n" "Last-Translator: Florian Besser <fbesser@gmail.com>\n" "Language-Team: German <fbesser@gmail.com>\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: German\n" "X-Poedit-Country: GERMANY\n" #: config.py:45 msgid "" "Determines the maximum number of\n" " messages to be queued for a user. If this value is 0, there is no maximum.\n" " " msgstr "Legt die maximalanzahl von Nachrichten fest die in einer Warteschlange abgelegt werden. Falls dieser Wert 0 ist, gibt es keine begrenzung." #: config.py:49 msgid "" "Determines whether users will be notified in\n" " the first place in which they're seen, or in private." msgstr "Legt fest ob der Benutzer dort benachrichtig wird wo er zuerst gesehen wird, oder privat." #: 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 "Legt fest ob ein Benutzer benachrichtig werden soll sobald er einen Kanal des Bots betritt, oder nach dem senden einer Nachricht." #: 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 "Legt die maximal Anzahl an Tagen fest, die eine Nachricht in der Warteschlange bleibt. Nach dieser Zeit wird die Nachricht gelöscht. Falls dieser Wert 0 ist gibt es keine Begrenzung." #: plugin.py:46 msgid "" "Used to do things later; currently, it only allows the sending of\n" " nick-based notes. Do note (haha!) that these notes are *not* private\n" " and don't even pretend to be; if you want such features, consider using the\n" " Note plugin." msgstr "Wird benutzt um Sachen später zu machen, momentan kann man nur Notizen an andere Nutzer senden. Beachte, dass diese Notizen *nicht* privat sind und auch nicht sein wollen; falls du soetwas sucht, überlege das Note Plugin zu benutzen." #: plugin.py:84 msgid "%s ago" msgstr "vor %s" #: plugin.py:86 msgid "just now" msgstr "jetzt" #: 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 "" "Validiert den Nick nach der IRC RFC 2812 spec.\n" "\n" "Referenz: http://tools.ietf.org/rfcmarkup?doc=2812#section-2.3.1\n" "\n" "Manche Klienten fügen bei der Tabvervollständigung 'addressierungs' Zeichen hinzu, sowie ':' oder','. Wir probieren das zu korregieren indem wir das letzte Zeichen abschneiden.\n" "\n" "Falls der Nick nicht korrigiert werden kann, wird False zurückgegeben, andererseits der (möglicherweise gekürzte) Nick." #: plugin.py:151 msgid "" "<nick> <text>\n" "\n" " Tells <nick> <text> the next time <nick> is in seen. <nick> can\n" " contain wildcard characters, and the first matching nick will be\n" " given the note.\n" " " msgstr "" "<Nick> <Text>\n" "\n" "Sagt <Nick> <Text>, sobald <Nick> das nächste Mal gesehen wird. <Nick> kann Platzhalter enthalten, und der erste Nick der darauf passt wird die Notiz bekommen." #: plugin.py:159 msgid "I can't send notes to myself." msgstr "Ich kann keine Notizen an mich selbst senden." #: plugin.py:169 msgid "That person's message queue is already full." msgstr "Die Warteschlange dieser person ist bereits voll." #: plugin.py:174 msgid "" "[<nick>]\n" "\n" " If <nick> is given, replies with what notes are waiting on <nick>,\n" " otherwise, replies with the nicks that have notes waiting for them.\n" " " msgstr "" "[<Nick>]\n" "\n" "Falls <Nick> angegeben wird, wird mit den Notizen geantwortet die auf <Nick> waren, andernfalls wird mit den Nicks geanwortet die Notizen in der Warteschlange haben." #: plugin.py:185 msgid "I have no notes for that nick." msgstr "Ich habe keine Notizen für diesen Nick." #: plugin.py:190 msgid "I currently have notes waiting for %L." msgstr "Ich habe momentan keine Notizen in der Warteschlange für %L." #: plugin.py:193 msgid "I have no notes waiting to be delivered." msgstr "Ich habe keine Notizen, die darauf warten zugestellt zu werden." #: plugin.py:198 msgid "" "<nick>\n" "\n" " Removes the notes waiting on <nick>.\n" " " msgstr "" "<Nick>\n" "\n" "Entfernt die wartenden Notizen für <Nick>." #: plugin.py:207 msgid "There were no notes for %r" msgstr "Da waren keine Nachrichten für %r" #: plugin.py:212 msgid "" "<nick>\n" "\n" " Removes the latest note you sent to <nick>.\n" " " msgstr "" "<Nick>\n" "\n" "Entfern die letzte Notiz die du an <Nick> gesendet hast." #: plugin.py:217 msgid "There are no note waiting for %s." msgstr "Es gibt keine Notiz, die auf %s wartet." #: plugin.py:228 msgid "There are no note from you waiting for %s." msgstr "Es gibt keine Notiz von dir, die auf %s wartet." #: plugin.py:252 msgid "Sent %s: <%s> %s" msgstr "%s gesendet: <%s> %s" ��������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Later/locales/fi.po�����������������������������������������������������0000644�0001750�0001750�00000013661�13634634532�020436� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Later plugin in Limnoria. # Copyright (C) 2011 Limnoria # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: \n" "POT-Creation-Date: 2014-03-22 12:41+EET\n" "PO-Revision-Date: 2014-03-22 15:33+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\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-Generator: Poedit 1.5.4\n" #: config.py:45 msgid "" "Determines the maximum number of\n" " messages to be queued for a user. If this value is 0, there is no " "maximum.\n" " " msgstr "" "Määrittää maksimi määrän\n" " viestejä, jotka ovat jonossa käyttäjälle. Jos tämä arvo on 0, maksimia " "ei ole.\n" " " #: config.py:49 msgid "" "Determines whether users will be notified in\n" " the first place in which they're seen, or in private." msgstr "" "Määrittää huomautetaanko käyttäjiä heti\n" " siinä paikassa kun heidät on nähty vai yksityisesti." #: 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 "" "Määrittää huomautetaanko käyttäjiä kun liittyessä\n" " mille tahansa kanavalle. jolla botti on vai vain viestiä lähetettäessä." #: 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 "" "Määrittää maksimi määrän päiviä, jonka aikana\n" " viesti on jonossa käyttäjälle. Sen jälkeen kun tämä aika on kulunut,\n" " viesti poistetaan. Jos tämä arvo on 0, maksimia ei ole." #: plugin.py:46 msgid "" "Used to do things later; currently, it only allows the sending of\n" " nick-based notes. Do note (haha!) that these notes are *not* private\n" " and don't even pretend to be; if you want such features, consider using " "the\n" " Note plugin." msgstr "" "Tottunut tekemään asioita myöhemmin; tällä hetkellä, se sallii vain " "nimimerkki-pohjaisten\n" " muistiinpanojen lähettämisen. Muista (haha!), että nämä muistiimpanot " "*eivät* ole yksityisiä\n" " eivätkä edes esitä olevansa; jos tahdot sellaisia toimintoja, harkitse\n" " Note lisäosan käyttöä." #: plugin.py:86 msgid "just now" msgstr "juuri nyt" #: plugin.py:106 #, fuzzy 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 "" "Varmista ninimerkki IRC RFC 2812 vaatimusten mukaan.\n" "\n" " Viittaukset: http://tools.ietf.org/rfcmarkup?doc=2812#section-2.3.1\n" "\n" " Jotkut IRC asiakasohjelmat' tabulaattoritäyttävät toimintoja " "liittyen 'osoite' merkkeihin\n" " nimimerkeissä, kuten ':' tai ','. Me yritämme oikaista tämän " "trimmaamalla\n" " merkin lopusta.\n" "\n" " Jos nimimerkki on viallinen, palauttaa Falsen, muutoin palauttaa\n" " (mahdollisesti trimmatun) nimimerkin.\n" " " #: plugin.py:151 #, fuzzy msgid "" "<nick> <text>\n" "\n" " Tells <nick> <text> the next time <nick> is seen. <nick> can\n" " contain wildcard characters, and the first matching nick will be\n" " given the note.\n" " " msgstr "" "<nimimerkki> <teksti>\n" "\n" " Kertoo <nimimerkille> <tekstin> seuraavalla kerralla, kun " "<nimimerkki> nähdään. <Nimimerkki> voi\n" " sisältää jokerimerkkejä ja ensimmäiselle täsmäävälle nimimerkille\n" " annetaan muistiinpano.\n" " " #: plugin.py:159 msgid "I can't send notes to myself." msgstr "En voi lähettää muistiinpanoja itselleni." #: plugin.py:169 msgid "That person's message queue is already full." msgstr "Tuon henkilön viestijono on jo täynnä." #: plugin.py:174 msgid "" "[<nick>]\n" "\n" " If <nick> is given, replies with what notes are waiting on <nick>,\n" " otherwise, replies with the nicks that have notes waiting for them.\n" " " msgstr "" "[<nimimerkki>]\n" "\n" " Jos <nimimerkki> on annettu, vastaa niillä muistiinpanoja, jotka " "odottaavat <nimimerkillä>,\n" " muutoin, vastaa nimimerkeillä, joilla on odottavia muistiinpanoja.\n" " " #: plugin.py:185 msgid "I have no notes for that nick." msgstr "Minulla ei ole muistiinpanoja odottamassa tuota nimimerkkiä." #: plugin.py:190 msgid "I currently have notes waiting for %L." msgstr "Minulla on tällä hetkellä muistiinpanoja odottamassa %L:ää.." #: plugin.py:193 msgid "I have no notes waiting to be delivered." msgstr "Minulla ei ole muistiinpanoja odottamassa toimitetuksi tulemista." #: plugin.py:198 msgid "" "<nick>\n" "\n" " Removes the notes waiting on <nick>.\n" " " msgstr "" "<nimimerkki>\n" "\n" " Poistaa muistiinpanot, jotka odottavat <nimimerkillä>.\n" " " #: plugin.py:207 msgid "There were no notes for %r" msgstr " %r:lle ei ollut muistiinpanoja." #: plugin.py:212 msgid "" "<nick>\n" "\n" " Removes the latest note you sent to <nick>.\n" " " msgstr "" "<nimimerkki>\n" "\n" " Poistaa viimeisimmän muistiinpanon, jonka olet lähettänyt " "<nimimerkille>.\n" " " #: plugin.py:217 msgid "There are no note waiting for %s." msgstr "%s:lle ei ole odottavia muistiinpanoja." #: plugin.py:228 msgid "There are no note from you waiting for %s." msgstr "Sinulla ei ole odottavia muistiinpanoja %s:lle." #: plugin.py:252 msgid "Sent %s: <%s> %s" msgstr "Lähetetty %s: <%s> %s" #~ msgid "%s ago" #~ msgstr "%s sitten" �������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Later/locales/fr.po�����������������������������������������������������0000644�0001750�0001750�00000011544�13634634532�020445� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-01-22 13:38+CET\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: Limnoria <progval@gmail.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" "Language: fr\n" #: config.py:45 msgid "" "Determines the maximum number of\n" " messages to be queued for a user. If this value is 0, there is no " "maximum.\n" " " msgstr "" "Détermine le nombre maximum de messages en attente d'un utilisateur. Si la " "valeur est 0, il n'y a pas de maximum" #: config.py:49 msgid "" "Determines whether users will be notified in\n" " 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" " nick-based notes. Do note (haha!) that these notes are *not* private\n" " and don't even pretend to be; if you want such features, consider using " "the\n" " Note plugin." msgstr "" "Utilisé pour faire des choses plus tard ; actuellement, il n'autorise que " "les notes basées sur des nicks. Notez (haha !) que ces notes ne sont *pas* " "privées, et qu'elle ne sont pas conçues pour l'être ; si vous voulez une " "telle fonctionnalité, utilisez le plugin Note." #: plugin.py:86 msgid "just now" msgstr "à l'instant" #: 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 "" "<nick> <text>\n" "\n" " Tells <nick> <text> the next time <nick> is seen. <nick> can\n" " contain wildcard characters, and the first matching nick will be\n" " given the note.\n" " " msgstr "" "<nick> <texte>\n" "\n" "Dit le <texte> à <nick> la prochaine fois qu'iel est vu-e. <nick> peut contenir " "des jokers, et le premier nick correspondant recevra la note." #: plugin.py:159 msgid "I can't send notes to myself." msgstr "Je ne peux m'envoyer de notes à moi-même." #: 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:174 msgid "" "[<nick>]\n" "\n" " If <nick> is given, replies with what notes are waiting on <nick>,\n" " otherwise, replies with the nicks that have notes waiting for them.\n" " " msgstr "" "[<nick>]\n" "\n" "Si le <nick> est donné, répond avec les notes en attente pour <nick> ; " "sinon, répond avec les nicks ayant des notes en attente." #: plugin.py:185 msgid "I have no notes for that nick." msgstr "Je n'ai pas de note pour ce nick." #: plugin.py:190 msgid "I currently have notes waiting for %L." msgstr "J'ai actuellement des notes en attente pour %L." #: plugin.py:193 msgid "I have no notes waiting to be delivered." msgstr "Je n'ai pas de note à délivrer." #: plugin.py:198 msgid "" "<nick>\n" "\n" " Removes the notes waiting on <nick>.\n" " " msgstr "" "<nick>\n" "\n" "Supprime les notes en attente pour <nick>." #: plugin.py:207 msgid "There were no notes for %r" msgstr "Il n'y a pas de note pour %r" #: plugin.py:212 msgid "" "<nick>\n" "\n" " Removes the latest note you sent to <nick>.\n" " " msgstr "" "<nick>\n" "\n" "Supprime la dernière note que vous avez envoyée à <nick>." #: plugin.py:217 msgid "There are no note waiting for %s." msgstr "Il n'y a pas de note en attente de %r" #: plugin.py:228 msgid "There are no note from you waiting for %s." msgstr "Il n'y a pas de note de vous en attente pour %s." #: plugin.py:252 msgid "Sent %s: <%s> %s" msgstr "Envoyé %s : <%s> %s" #~ msgid "%s ago" #~ msgstr "il y a %s" ������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Later/locales/it.po�����������������������������������������������������0000644�0001750�0001750�00000012735�13634634532�020455� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-08-10 14:27+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:45 msgid "" "Determines the maximum number of\n" " messages to be queued for a user. If this value is 0, there is no maximum.\n" " " msgstr "" "Determina il numero massimo di messaggi da mettere in coda per un utente.\n" " Se questo valore è uguale a 0, non c'è un limite massimo.\n" " " #: config.py:49 msgid "" "Determines whether users will be notified in\n" " the first place in which they're seen, or in private." msgstr "" "Determina se gli utenti riceveranno una nota nel primo luogo in cui sono visti o in privato." #: 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 "" "Determina se gli utenti riceveranno una nota entrando in qualsiasi canale\n" " in cui è presente il bot o solo dopo aver inviato un messaggio." #: 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 "" "Determina il numero massimo di giorni che un messaggio rimarrà in coda per un utente. Allo scadere\n" " di questo tempo il messaggio verrà cancellato. Se questo valore è uguale a 0, non c'è un limite massimo.\n" #: plugin.py:46 #, docstring msgid "" "Used to do things later; currently, it only allows the sending of\n" " nick-based notes. Do note (haha!) that these notes are *not* private\n" " and don't even pretend to be; if you want such features, consider using the\n" " Note plugin." msgstr "" "Utilizzato per fare cose in seguito; attualmente permette solo l'invio di note basate\n" " sul nick. Nota (haha!) che queste note *non* sono private e non sono state progettate\n" " per esserlo; se vuoi questa caratteristica considera l'utilizzo del plugin Note." #: plugin.py:84 msgid "%s ago" msgstr "%s fa" #: plugin.py:86 msgid "just now" msgstr "proprio ora" #: 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 "" "Convalida il nick secondo la specifica IRC RFC 2812.\n" "\n" " Riferimento: http://tools.ietf.org/rfcmarkup?doc=2812#section-2.3.1\n" "\n" " Alcuni client IRC hanno l'autocompletamento del nick che aggiunge caratteri di\n" " \"indirizzamento\" come \":\" o \",\" alla fine. Si cerca di correggere questo\n" " comportamento tagliando un carattere al fondo.\n" "\n" " Se il nick non è valido restituisce False, altrimenti lo riporta (possibilmente tagliato).\n" " " #: plugin.py:151 #, docstring msgid "" "<nick> <text>\n" "\n" " Tells <nick> <text> the next time <nick> is in seen. <nick> can\n" " contain wildcard characters, and the first matching nick will be\n" " given the note.\n" " " msgstr "" "<nick> <testo>\n" "\n" " Riferisce <testo> a <nick> la prima volta che lo vede. <nick> può contenere\n" " caratteri jolly, il primo che corrisponde riceverà la notifica.\n" " " #: plugin.py:159 msgid "I can't send notes to myself." msgstr "Non posso inviare note a me stesso." #: plugin.py:169 msgid "That person's message queue is already full." msgstr "La coda dei messaggi di questo utente è già piena." #: plugin.py:174 #, docstring msgid "" "[<nick>]\n" "\n" " If <nick> is given, replies with what notes are waiting on <nick>,\n" " otherwise, replies with the nicks that have notes waiting for them.\n" " " msgstr "" "[<nick>]\n" "\n" " Se <nick> è fornito, risponde con le note in coda per <nick>,\n" " altrimenti con i nick che hanno note in coda.\n" " " #: plugin.py:185 msgid "I have no notes for that nick." msgstr "Non ho note per questo nick." #: plugin.py:190 msgid "I currently have notes waiting for %L." msgstr "Al momento non ho note in coda per %L." #: plugin.py:193 msgid "I have no notes waiting to be delivered." msgstr "Non ho note in attesa di essere consegnate." #: plugin.py:198 #, docstring msgid "" "<nick>\n" "\n" " Removes the notes waiting on <nick>.\n" " " msgstr "" "<nick>\n" "\n" " Rimuove le note in coda per <nick>.\n" " " #: plugin.py:207 msgid "There were no notes for %r" msgstr "Non ci sono note per %r" #: plugin.py:212 #, docstring msgid "" "<nick>\n" "\n" " Removes the latest note you sent to <nick>.\n" " " msgstr "" "<nick>\n" "\n" " Rimuove l'ultima nota inviata a <nick>.\n" " " #: plugin.py:217 msgid "There are no note waiting for %s." msgstr "Non ci sono note in attesa per %s." #: plugin.py:228 msgid "There are no note from you waiting for %s." msgstr "Non ci sono note in attesa per %s da te inviate." #: plugin.py:252 msgid "Sent %s: <%s> %s" msgstr "Inviata %s: <%s> %s" �����������������������������������limnoria-2020.03.17/plugins/Later/plugin.py���������������������������������������������������������0000644�0001750�0001750�00000023233�13634634532�017722� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Jeremiah Fincher # Copyright (c) 2010, James McCoy # 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 csv import time import codecs import datetime import supybot.log as log import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Later') class QueueIsFull(Exception): pass 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 and don't even pretend to be; if you want such features, consider using the Note plugin.""" def __init__(self, irc): self.__parent = super(Later, self) self.__parent.__init__(irc) self._notes = ircutils.IrcDict() self.wildcards = [] self.filename = conf.supybot.directories.data.dirize('Later.db') self._openNotes() def die(self): self._flushNotes() def _flushNotes(self): fd = utils.file.AtomicFile(self.filename) writer = csv.writer(fd) for (nick, notes) in self._notes.items(): for (time, whence, text) in notes: writer.writerow([nick, time, whence, text]) fd.close() def _openNotes(self): try: fd = codecs.open(self.filename, encoding='utf8') except EnvironmentError as e: self.log.warning('Couldn\'t open %s: %s', self.filename, e) return reader = csv.reader(fd) for (nick, time, whence, text) in reader: self._addNote(nick, whence, text, at=float(time), maximum=0) fd.close() def _timestamp(self, when): #format = conf.supybot.reply.format.time() diff = when - time.time() try: return utils.timeElapsed(diff, seconds=False) except ValueError: return _('just now') def _addNote(self, nick, whence, text, at=None, maximum=None): if at is None: at = time.time() if maximum is None: maximum = self.registryValue('maximum') try: notes = self._notes[nick] if maximum and len(notes) >= maximum: raise QueueIsFull() else: notes.append((at, whence, text)) except KeyError: self._notes[nick] = [(at, whence, text)] if set('?*!@') & set(nick): if nick not in self.wildcards: self.wildcards.append(nick) self._flushNotes() def _deleteExpired(self): expiry = self.registryValue('messageExpiry') curtime = time.time() nickremovals=[] for (nick, notes) in self._notes.items(): 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, nicks, text): """<nick1[,nick2[,...]]> <text> Tells each <nickX> <text> the next time <nickX> is seen. <nickX> can contain wildcard characters, and the first matching nick will be given the note. """ self._deleteExpired() validnicks = [] for nick in set(nicks): # Ignore duplicates if ircutils.strEqual(nick, irc.nick): irc.error(_('I can\'t send notes to myself.')) return validnicks.append(nick) full_queues = [] for validnick in validnicks: try: self._addNote(validnick, msg.prefix, text) except QueueIsFull: full_queues.append(validnick) if full_queues: irc.error(format( _('These recipients\' message queue are already full: %L'), full_queues)) else: irc.replySuccess() tell = wrap(tell, [commalist(first('nick', 'hostmask')), 'text']) @internationalizeDocstring def notes(self, irc, msg, args, nick): """[<nick>] If <nick> is given, replies with what notes are waiting on <nick>, otherwise, replies with the nicks that have notes waiting for them. """ if nick: if nick in self._notes: notes = [self._formatNote(when, whence, note) for (when, whence, note) in self._notes[nick]] irc.reply(format('%L', notes)) else: irc.error(_('I have no notes for that nick.')) else: nicks = self._notes.keys() if nicks: utils.sortBy(ircutils.toLower, nicks) irc.reply(format(_('I currently have notes waiting for %L.'), nicks)) else: irc.error(_('I have no notes waiting to be delivered.')) notes = wrap(notes, [additional('something')]) @internationalizeDocstring def remove(self, irc, msg, args, nick): """<nick> Removes the notes waiting on <nick>. """ try: del self._notes[nick] self._flushNotes() irc.replySuccess() except KeyError: irc.error(_('There were no notes for %r') % nick) remove = wrap(remove, [('checkCapability', 'admin'), 'something']) @internationalizeDocstring def undo(self, irc, msg, args, nick): """<nick> Removes the latest note you sent to <nick>. """ if nick not in self._notes: irc.error(_('There are no note waiting for %s.') % nick) return self._notes[nick].reverse() for note in self._notes[nick]: if ircutils.nickFromHostmask(note[1]) == msg.nick: self._notes[nick].remove(note) if len(self._notes[nick]) == 0: del self._notes[nick] self._flushNotes() irc.replySuccess() return irc.error(_('There are no note from you waiting for %s.') % nick) undo = wrap(undo, ['something']) def doPrivmsg(self, irc, msg): if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg): return notes = self._notes.pop(msg.nick, []) # Let's try wildcards. removals = [] for wildcard in self.wildcards: if ircutils.hostmaskPatternEqual(wildcard, msg.prefix): removals.append(wildcard) notes.extend(self._notes.pop(wildcard)) for removal in removals: self.wildcards.remove(removal) if notes: old_repliedto = msg.repliedTo irc = callbacks.SimpleProxy(irc, msg) private = self.registryValue('private') for (when, whence, note) in notes: s = self._formatNote(when, whence, note) irc.reply(s, private=private, prefixNick=not private) self._flushNotes() msg.tag('repliedTo', old_repliedto) def _formatNote(self, when, whence, note): if not self.registryValue('format.senderHostname'): whence = ircutils.nickFromHostmask(whence) return _('Sent %s: <%s> %s') % (self._timestamp(when), whence, note) 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: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Later/test.py�����������������������������������������������������������0000644�0001750�0001750�00000014254�13634634532�017406� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * import supybot.conf as conf import time class LaterTestCase(ChannelPluginTestCase): plugins = ('Later',) def testLaterWorksTwice(self): self.assertNotError('later tell foo bar') self.assertNotError('later tell foo baz') def testLaterRemove(self): self.assertNotError('later tell foo 1') self.assertNotError('later tell bar 1') self.assertRegexp('later notes', 'bar.*foo') self.assertNotError('later remove bar') self.assertNotRegexp('later notes', 'bar.*foo') self.assertRegexp('later notes', 'foo') def testLaterUndo(self): self.assertNotError('later tell foo 1') self.assertNotError('later tell bar 1') self.assertRegexp('later notes', 'bar.*foo') self.assertNotError('later undo foo') self.assertNotRegexp('later notes', 'bar.*foo') self.assertRegexp('later notes', 'bar') 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') conf.supybot.protocols.irc.strictRfc.setValue(origconf) def testWildcard(self): self.assertNotError('later tell foo* stuff') self.assertNotError('later tell bar,baz 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.assertEqual(str(m).strip(), 'PRIVMSG #test :foo: Sent just now: <test> stuff') def testHostmask(self): self.assertNotError('later tell foo*!*@baz stuff') self.assertNotError('later tell bar,baz more stuff') self.assertRegexp('later notes', 'bar.*foo') testPrefix = 'foo!bar@baz2' self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'something', prefix=testPrefix)) m = self.getMsg(' ') self.assertEqual(m, None) testPrefix = 'foo!bar@baz' self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'something', prefix=testPrefix)) m = self.getMsg(' ') self.assertEqual(str(m).strip(), 'PRIVMSG #test :foo: Sent just now: <test> stuff') 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') def testNoteSend(self): self.assertNotError('later tell foo stuff') self.assertNotError('later tell bar,baz 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.assertEqual(str(m).strip(), 'PRIVMSG #test :foo: Sent just now: <test> stuff') self.assertNotRegexp('later notes', 'foo') self.assertRegexp('later notes', 'bar') self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'something', prefix='baz!baz@qux')) m = self.getMsg(' ') self.assertEqual(str(m).strip(), 'PRIVMSG #test :baz: Sent just now: <test> more stuff') real_time = time.time def fake_time(): return real_time() + 62 time.time = fake_time self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'something', prefix='bar!baz@qux')) m = self.getMsg(' ') self.assertEqual(str(m).strip(), 'PRIVMSG #test :bar: Sent 1 minute ago: <test> more stuff') time.time = real_time def testSenderHostname(self): self.assertNotError('later tell foo stuff') testPrefix = 'foo!bar@baz' with conf.supybot.plugins.Later.format.senderHostname.context(True): self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'something', prefix=testPrefix)) m = self.getMsg(' ') self.assertEqual(str(m).strip(), 'PRIVMSG #test :foo: Sent just now: <%s> stuff' % self.prefix) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Limiter/����������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016413� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Limiter/__init__.py�����������������������������������������������������0000644�0001750�0001750�00000004671�13634634532�020526� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ This plugin handles channel limits (MODE +l). """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������limnoria-2020.03.17/plugins/Limiter/config.py�������������������������������������������������������0000644�0001750�0001750�00000006031�13634634532�020224� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Limiter') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Limiter', True) Limiter = conf.registerPlugin('Limiter') conf.registerChannelValue(Limiter, 'enable', registry.Boolean(False, _("""Determines whether the bot will maintain the channel limit to be slightly above the current number of people in the channel, in order to make clone/drone attacks harder."""))) conf.registerChannelValue(Limiter, 'minimumExcess', registry.PositiveInteger(5, _("""Determines the minimum number of free spots that will be saved when limits are being enforced. This should always be smaller than supybot.plugins.Limiter.limit.maximumExcess."""))) conf.registerChannelValue(Limiter, 'maximumExcess', registry.PositiveInteger(10, _("""Determines the maximum number of free spots that will be saved when limits are being enforced. This should always be larger than supybot.plugins.Limiter.limit.minimumExcess."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Limiter/locales/��������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�020035� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Limiter/locales/fi.po���������������������������������������������������0000644�0001750�0001750�00000005154�13634634532�020772� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: \n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-26 20:28+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: \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" #: config.py:46 msgid "" "Determines whether the bot will maintain the\n" " channel limit to be slightly above the current number of people in the\n" " channel, in order to make clone/drone attacks harder." msgstr "" "Määrittää ylläpitääkö botti kanava rajoitusta\n" " huomattavasti korkeammalla kuin nykyinen ihmisten määrä kanavalla\n" " tehdäkseen klooni/botti hyökkäykset vaikeammiksi." #: config.py:50 msgid "" "Determines the minimum number of free\n" " spots that will be saved when limits are being enforced. This should\n" " always be smaller than supybot.plugins.Limiter.limit.maximumExcess." msgstr "" "Määrittää minimimäärän vapaita paikkoja, jotka\n" " säästetään, kun rajajoja pakotetaan. Tämän pitäisi aina olla pienempi kuin\n" " supybot.plugins.Limiter.limit.maximumExcess." #: config.py:54 msgid "" "Determines the maximum number of free spots\n" " that will be saved when limits are being enforced. This should always be\n" " larger than supybot.plugins.Limiter.limit.minimumExcess." msgstr "" "Määrittää maksimi määrän vapaita paikkoja, jotka säästetään\n" " kun rajoja pakotetaan. Tämän pitäisi aina olla suurempi kuin\n" " supybot.plugins.Limiter.limit.minimumExcess." #: plugin.py:39 msgid "" "In order to use this plugin, its config values need to be properly\n" " setup. supybot.plugins.Limiter.enable needs to be set to True and\n" " supybot.plugins.Limiter.{maximumExcess,minimumExcess} should be set to\n" " values appropriate to your channel (if the defaults aren't satisfactory).\n" " Once these are set, and someone enters/leaves the channel, Supybot will\n" " start setting the proper +l modes.\n" " " msgstr "" "Käyttääkseen tätä lisäosaa, sen asetusarvot täytyy asettaa\n" " kunnolla. supybot.plugins.Limiter.enable täytyy asettaa todeksi (\"True\") ja\n" " supybot.plugins.Limiter.{maximumExcess,minimumExcess} \n" " arvot kanavallesi sopiviksi (jos oletukset eivät kelpaa).\n" " Kun nämä on asetettu, ja joku tulee/lähtee kanavalta, Supybotti alkaa\n" " asettamaan oikeita +l tiloja.\n" " " ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Limiter/locales/fr.po���������������������������������������������������0000644�0001750�0001750�00000005111�13634634532�020774� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2010-10-17 15:35+CEST\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\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" #: config.py:46 msgid "" "Determines whether the bot will maintain the\n" " channel limit to be slightly above the current number of people in the\n" " channel, in order to make clone/drone attacks harder." msgstr "Détermine si le bot maintiendra la limite de canal pour être juste au-dessus du nombre actuel de personnes, pour rendre les attaques de clones plus difficiles" #: config.py:50 msgid "" "Determines the minimum number of free\n" " spots that will be saved when limits are being enforced. This should\n" " always be smaller than supybot.plugins.Limiter.limit.maximumExcess." msgstr "Détermine le nombre minimum de 'places' libres à avoir, c'est à dire que la limite sera changées lorsque ce nombre sera atteint. Ce doit toujours être plus petit que supybot.plugins.Limiter.limit.maximumExcess." #: config.py:54 msgid "" "Determines the maximum number of free spots\n" " that will be saved when limits are being enforced. This should always be\n" " larger than supybot.plugins.Limiter.limit.minimumExcess." msgstr "Détermine le nombre maximum de 'places' libre, c'est à dire le nombre de place qu'il y aura juste après chaque changement de limite. Ce doit toujours être plus grand que supybot.plugins.Limiter.limit.minimumExcess." #: plugin.py:40 msgid "" "In order to use this plugin, its config values need to be properly\n" " setup. supybot.plugins.Limiter.enable needs to be set to True and\n" " supybot.plugins.Limiter.{maximumExcess,minimumExcess} should be set to\n" " values appropriate to your channel (if the defaults aren't satisfactory).\n" " Once these are set, and someone enters/leaves the channel, Supybot will\n" " start setting the proper +l modes.\n" " " msgstr "Pour utiliser ce plugin, ses variables de configuration doivent être configurées proprement. supybot.plugins.Limiter.enable doit être défini à True, et supybot.plugins.Limiter.{maximumExcess,minimumExcess} doivent être définis à la valeur appropriée piur votre canal (si les valeurs par défaut ne satisfont pas). Une fois qu'elles sont définies, et que quelqu'un entrera/partira du canal, Supybot commencera à définir les modes +l." �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Limiter/locales/hu.po���������������������������������������������������0000644�0001750�0001750�00000005155�13634634532�021011� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # msgid "" msgstr "" "Project-Id-Version: Limnoria Limiter\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-09-01 18:12+0200\n" "Last-Translator: nyuszika7h <litemininyuszika@gmail.com>\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" #: config.py:46 msgid "" "Determines whether the bot will maintain the\n" " channel limit to be slightly above the current number of people in the\n" " channel, in order to make clone/drone attacks harder." msgstr "Meghatározza, hogy a bot tartsa-e a csatorna korlátját kicsivel a jelenlegi emberek számával a csatornában, hogy a klóntámadásokat nehezebbé tegye." #: config.py:50 msgid "" "Determines the minimum number of free\n" " spots that will be saved when limits are being enforced. This should\n" " always be smaller than supybot.plugins.Limiter.limit.maximumExcess." msgstr "Meghatározza a szabad helyek minimum számát, amelyek meg lesznek mentve amikor a korlátok kényszerítve vannak. Ennek mindig kisebbnek kell lennie a supybot.plugins.Limiter.limit.maximumExcess-nél." #: config.py:54 msgid "" "Determines the maximum number of free spots\n" " that will be saved when limits are being enforced. This should always be\n" " larger than supybot.plugins.Limiter.limit.minimumExcess." msgstr "Meghatározza a szabad helyek maximum számát, amelyek meg lesznek mentve amikor a korlátok kényszerítve vannak. Ennek mindig nagyobbnak kell lennie a supybot.plugins.Limiter.limit.minimumExcess-nél." #: plugin.py:39 #, docstring msgid "" "In order to use this plugin, its config values need to be properly\n" " setup. supybot.plugins.Limiter.enable needs to be set to True and\n" " supybot.plugins.Limiter.{maximumExcess,minimumExcess} should be set to\n" " values appropriate to your channel (if the defaults aren't satisfactory).\n" " Once these are set, and someone enters/leaves the channel, Supybot will\n" " start setting the proper +l modes.\n" " " msgstr "A bővítmény használatához a konfigurációs értékeinek rendesen be kell lenniük állítva. A supybot.plugins.Limiter.enable True-ra kell, hogy legyen állítva, és a supybot.plugins.Limiter.{maximumExcess,minimumExcess}-nek a csatornának megfelelő értékekre kell lenniük állítva (ha az alapértelmezés nem kielégítő). Ahogy ezek be vannak állítva, és valaki be-/kilép a csatornáról/-ba, a Supybot elkezdi beállítani a megfelelő +l módokat." �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Limiter/locales/it.po���������������������������������������������������0000644�0001750�0001750�00000005011�13634634532�021000� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-15 13:37+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:46 msgid "" "Determines whether the bot will maintain the\n" " channel limit to be slightly above the current number of people in the\n" " channel, in order to make clone/drone attacks harder." msgstr "" "Determina se il bot manterrà il limite del canale appena al di sopra\n" " del numero di persone attualmente presenti, in modo da rendere più\n" " difficili gli attacchi di cloni o droni." #: config.py:50 msgid "" "Determines the minimum number of free\n" " spots that will be saved when limits are being enforced. This should\n" " always be smaller than supybot.plugins.Limiter.limit.maximumExcess." msgstr "" "Determina il numero minimo di posti liberi che verrà salvato quando saranno\n" " imposti i limiti. Ciò non dovrebbe essere inferiore al valore di\n" " supybot.plugins.Limiter.limit.maximumExcess." #: config.py:54 msgid "" "Determines the maximum number of free spots\n" " that will be saved when limits are being enforced. This should always be\n" " larger than supybot.plugins.Limiter.limit.minimumExcess." msgstr "" "Determina il numero massimo di posti liberi che verrà salvato quando saranno\n" " imposti i limiti. Ciò dovrebbe essere sempre maggiore del valore di\n" " supybot.plugins.Limiter.limit.minimumExcess." #: plugin.py:39 #, docstring msgid "" "In order to use this plugin, its config values need to be properly\n" " setup. supybot.plugins.Limiter.enable needs to be set to True and\n" " supybot.plugins.Limiter.{maximumExcess,minimumExcess} should be set to\n" " values appropriate to your channel (if the defaults aren't satisfactory).\n" " Once these are set, and someone enters/leaves the channel, Supybot will\n" " start setting the proper +l modes.\n" " " msgstr "" "Per utilizzare questo plugin, è necessario configurare correttamente i valori.\n" " supybot.plugins.Limiter.enable deve essere impostato a True e\n" " supybot.plugins.Limiter.{maximumExcess,minimumExcess} va impostato con i\n" " valori appropriati al canale (se i predefiniti non sono soddisfacenti).\n" " Una volta definiti, e qualcuno entra o esce dal canale, Supybot imposterà\n" " il mode +l corretto.\n" " " �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Limiter/plugin.py�������������������������������������������������������0000644�0001750�0001750�00000007035�13634634532�020262� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004-2005, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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.commands import * import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Limiter') class Limiter(callbacks.Plugin): """In order to use this plugin, its config values need to be properly setup. supybot.plugins.Limiter.enable needs to be set to True and supybot.plugins.Limiter.{maximumExcess,minimumExcess} should be set to values appropriate to your channel (if the defaults aren't satisfactory). Once these are set, and someone enters/leaves the channel, Supybot will start setting the proper +l modes. """ def _enforce(self, irc, limit): irc.queueMsg(limit) irc.noReply() def _enforceLimit(self, irc, channel): if self.registryValue('enable', channel, irc.network): maximum = self.registryValue('maximumExcess', channel, irc.network) minimum = self.registryValue('minimumExcess', channel, irc.network) assert maximum > minimum currentUsers = len(irc.state.channels[channel].users) currentLimit = irc.state.channels[channel].modes.get('l', 0) if currentLimit - currentUsers < minimum: self._enforce(irc, ircmsgs.limit(channel,currentUsers+maximum)) elif currentLimit - currentUsers > maximum: self._enforce(irc, ircmsgs.limit(channel,currentUsers+minimum)) def doJoin(self, irc, msg): if not ircutils.strEqual(msg.nick, irc.nick): irc = callbacks.SimpleProxy(irc, msg) self._enforceLimit(irc, msg.channel) doPart = doJoin doKick = doJoin def doQuit(self, irc, msg): for channel in msg.tagged('channels'): self._enforceLimit(irc, channel) Limiter = internationalizeDocstring(Limiter) Class = Limiter # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Limiter/test.py���������������������������������������������������������0000644�0001750�0001750�00000005426�13634634532�017745� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * class LimiterTestCase(ChannelPluginTestCase): plugins = ('Limiter',) config = {'supybot.plugins.Limiter.enable': True} def testEnforceLimit(self): origMin = conf.supybot.plugins.Limiter.minimumExcess() origMax = conf.supybot.plugins.Limiter.maximumExcess() try: conf.supybot.plugins.Limiter.minimumExcess.setValue(5) conf.supybot.plugins.Limiter.maximumExcess.setValue(10) self.irc.feedMsg(ircmsgs.join('#foo', prefix='foo!root@host')) m = self.irc.takeMsg() self.assertEqual(m, ircmsgs.limit('#foo', 1+10)) self.irc.feedMsg(ircmsgs.join('#foo', prefix='bar!root@host')) m = self.irc.takeMsg() self.assertFalse(m is not None) conf.supybot.plugins.Limiter.maximumExcess.setValue(7) self.irc.feedMsg(ircmsgs.part('#foo', prefix='bar!root@host')) m = self.irc.takeMsg() self.assertEqual(m, ircmsgs.limit('#foo', 1+5)) finally: conf.supybot.plugins.Limiter.minimumExcess.setValue(origMin) conf.supybot.plugins.Limiter.maximumExcess.setValue(origMax) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Math/�������������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�015677� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Math/__init__.py��������������������������������������������������������0000644�0001750�0001750�00000004564�13634634532�020013� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Various math-related commands. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {supybot.Author('Keith Jones', 'kmj', ''): ['convert']} from . import config from . import plugin from importlib import reload from .local import convertcore reload(plugin) # In case we're being reloaded. reload(convertcore) if world.testing: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Math/config.py����������������������������������������������������������0000644�0001750�0001750�00000004650�13634634532�017515� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Math') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Math', True) Math = conf.registerPlugin('Math') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Math, 'someConfigVariableName', # registry.Boolean(False, _("""Help for someConfigVariableName."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Math/local/�������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016771� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Math/local/__init__.py��������������������������������������������������0000644�0001750�0001750�00000000072�13634634532�021073� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Stub so local is a module, used for third-party modules ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Math/local/convertcore.py�����������������������������������������������0000644�0001750�0001750�00000132452�13634634532�021675� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#**************************************************************************** # This file has been modified from its original version. It has been # formatted to fit your irc bot. # # The original version is a nifty PyQt application written by Douglas Bell, # available at http://convertall.bellz.org/ # # Below is the original copyright. Doug Bell rocks. # The hijacker is Keith Jones, and he has no bomb in his shoe. # #**************************************************************************** import re import copy unitData = \ """ #***************************************************************************** #units.dat, the units data file, version 0.6.2 # # ConvertAll, a units conversion program # Copyright (C) 2016, Douglas W. Bell # # This is free software; you can redistribute it and/or modify it under the # terms of the GNU General Public License, Version 2. This program is # distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. #***************************************************************************** # # Units are defined by an optional quantity and an equivalent unit or unit # combination. A Python expression may be used for the quantity, but is # restricted to using only the following operators: *, /, +, -. # Beware of integer division truncation: be sure to use a float for at least # one of the values. # # The unit type must be placed in square brackets before a set of units. The # first comment after the equivalent unit will be put in parenthesis after the # unit name (usually used to give the full name of an abbreviated unit). The # next comment will be used in the program list's comment column; later # comments and full line comments are ignored. # # Non-linear units are indicated with an equivalent unit in square brackets, # followed by either equations or equivalency lists for the definition. For # equations, two are given, separated by a ';'. Both are functions of "x", the # first going from the unit to the equivalent unit and the second one in # reverse. Any valid Python expression returning a float (including the # functions in the math module) should work. The equivalency list is a Python # list of tuples giving points for linear interpolation. # # All units must reduce to primitive units, which are indicated by an # equivalent unit starting with '!'. A special "unitless" primitve unit # (usualty called "unit") has '!!' for an equivalent unit. Circular references # must also be avoided. # # Primitive units: kg, m, s, K, A, mol, cd, rad, sr, bit, unit # ############################################################################## # # mass units # [mass] kg = ! # kilogram kilogram = kg key = kg # # drug slang hectogram = 100 gram dekagram = 10 gram gram = 0.001 kg g = gram # gram decigram = 0.1 gram centigram = 0.01 gram milligram = 0.001 gram mg = milligram # milligram microgram = 0.001 mg tonne = 1000 kg # # metric metric ton = tonne megagram = tonne kilotonne = 1000 tonne # # metric gigagram = 1e9 gram teragram = 1e12 gram carat = 0.2 gram ct = carat # carat amu = 1.66053873e-27 kg # atomic mass atomic mass unit = amu pound = 0.45359237 kg # # avoirdupois lb = pound # pound # avoirdupois lbm = pound # pound # avoirdupois ounce = 1/16.0 pound # # avoirdupois oz = ounce # ounce # avoirdupois lid = ounce # # drug slang dram = 1/16.0 ounce # # avoirdupois pound troy = 5760 grain lb troy = pound troy # pound troy ounce troy = 1/12.0 lb troy oz troy = ounce troy # ounce troy ton = 2000 lb # # non-metric kiloton = 1000 ton # # non-metric long ton = 2240 lb # # Imperial ton imperial = long ton slug = lbf*s^2/ft stone = 14 lb grain = 1/7000.0 lb pennyweight = 24 grain hundredweight long = 112 lb # # Imperial hundredweight short = 100 lb # # US & Canada solar mass = 1.9891e30 kg # # length / distance units # [length] m = ! # meter meter = m metre = m dm = 0.1 m # decimeter decimeter = dm cm = 0.01 m # centimeter centimeter = cm mm = 0.001 m # millimeter millimeter = mm micrometer = 1e-6 m micron = micrometer nanometer = 1e-9 m nm = nanometer # nanometer dekameter = 10 m hectometer = 100 m km = 1000 m # kilometer kilometer = km megameter = 1000 km angstrom = 1e-10 m fermi = 1e-15 m # # nuclear sizes inch = 2.54 cm in = inch # inch inches = inch mil = 0.001 inch microinch = 1e-6 inch microinches = microinch foot = 12 inch ft = foot # foot feet = foot foot US survey = 1200/3937.0 m Cape foot = 1.033 foot yard = 3 ft yd = yard # yard mile = 5280 ft # # statute mile mi = mile # mile # statute mile nautical mile = 1852 m nmi = nautical mile # nautical mile mile US survey = 5280 foot US survey league = 3 mile chain = 66 ft chain US survey = 66 foot US survey link = 0.01 chain fathom = 6 ft cable = 0.1 nautical mile rod = 5.5 yard furlong = 40 rod hand = 4 inch cubit = 21.8 inch # # biblical unit point = 1/72.0 inch # # desktop publishing point pica = 12 point caliber = 1.0 inch # # bullet sizes rack unit = 1.75 in # # computing smoot = 67 inch football field = 100 yd marathon = 46145 yd mil Swedish = 10 km versta = 3500 ft # # Russian unit au = 1.49597870691e11 m # astronomical unit astronomical unit = au LD = 384400 km # lunar distance # astronomical lunar distance = LD # # astronomical distance light year = 365.25 light speed * day light minute = light speed * min light second = light speed * s parsec = 3.0856775813e16 m kiloparsec = 1000 parsec megaparsec = 1000 kiloparsec screw size = [in] 0.013*x + 0.06 ; (x - 0.06) / 0.013 \ # # Unified diameters, non-linear AWG Dia = [in] pow(92.0,(36-x)/39.0)/200.0 ; \ 36 - 39.0*log(200.0*x)/log(92.0) \ # American Wire Gauge \ # use -1, -2 for 00, 000; non-linear American Wire Gauge Dia = [in] pow(92.0,(36-x)/39.0)/200.0 ; \ 36 - 39.0*log(200.0*x)/log(92.0) \ # # use -1, -2 for 00, 000; non-linear British Std Wire Gauge = [in] [(-6, .500), (-5, .464), (-3, .400), \ (-2, .372), (3, .252), (6, .192), (10, .128), \ (14, .080), (19, .040), (23, .024), (26, .018), \ (28, .0148), (30, .0124), (39, .0052), \ (49, .0012), (50, .001)] \ # # use -1, -2 for 2/0, 3/0; non-linear standard gauge = [in] [(-5, .448350), (1, .269010), (14, .0747250), \ (16, .0597800), (17, .0538020), (20, .0358680), \ (26, .0179340), (31, .0104615), (36, .00672525), \ (38, .00597800)] # steel \ # Manufacturers Std. Gauge, non-linear zinc gauge = [in] [(1, .002), (10, .02), (15, .04), (19, .06), \ (23, .1), (24, .125), (27, .5), (28, 1)] \ # # sheet metal thickness, non-linear ring size = [in] 0.1018*x + 1.4216 ; (x - 1.4216) / 0.1018 \ # # US size, circum., non-linear shoe size mens = [in] x/3.0 + 7 + 1/3.0 ; (x - 7 - 1/3.0) * 3 \ # # US sizes, non-linear shoe size womens = [in] x/3.0 + 6 + 5/6.0 ; (x - 6 - 5/6.0) * 3 \ # # US sizes, non-linear # # time units # [time] s = ! # second sec = s # second second = s ms = 0.001 s # millisecond millisecond = ms microsecond = 1e-6 s ns = 1e-9 s # nanosecond nanosecond = ns minute = 60 s min = minute # minute hour = 60 min hr = hour # hour bell = 30 min # # naval definition watch = 4 hour watches = watch day = 24 hr week = 7 day wk = week # week fortnight = 14 day month = 1/12.0 year year = 365.242198781 day yr = year # year calendar year = 365 day decade = 10 year century = 100 year centuries = century millennium = 1000 year millennia = millennium [scheduling] man hour = 168/40.0 hour man week = 40 man hour man month = 1/12.0 man year man year = 52 man week # # temperature # [temperature] K = ! # Kelvin Kelvin = K deg K = K # Kelvin degree Kelvin = K C = [K] x + 273.15 ; x - 273.15 # Celsius # non-linear Celsius = [K] x + 273.15 ; x - 273.15 # # non-linear deg C = [K] x + 273.15 ; x - 273.15 # Celsius # non-linear degree Celsius = [K] x + 273.15 ; x - 273.15 # # non-linear R = 5/9.0 K # Rankine Rankine = R deg R = R # Rankine F = [R] x + 459.67 ; x - 459.67 # Fahrenheit # non-linear Fahrenheit = [R] x + 459.67 ; x - 459.67 # # non-linear deg F = [R] x + 459.67 ; x - 459.67 # Fahrenheit # non-linear degree Fahrenheit = [R] x + 459.67 ; x - 459.67 # # non-linear [temp. diff.] C deg = K # Celsius degree Celsius degree = C deg F deg = R # Fahrenheit deg. Fahrenheit degree = F deg # # electrical units # [current] A = ! # ampere ampere = A amp = A milliampere = 0.001 A milliamp = milliampere mA = milliampere # milliampere microampere = 0.001 mA kiloampere = 1000 A kA = kiloampere # kiloampere [charge] coulomb = A*s amp hour = A*hr mAh = 0.001 amp hour # milliamp hour milliamp hour = mAh [potential] volt = W/A V = volt # volt millivolt = 0.001 volt mV = millivolt # millivolt kilovolt = 1000 volt kV = kilovolt # kilovolt [resistance] ohm = V/A milliohm = 0.001 ohm microhm = 0.001 milliohm kilohm = 1000 ohm [conductance] siemens = A/V [capacitance] farad = coulomb/V millifarad = 0.001 farad microfarad = 0.001 millifarad nanofarad = 1e-9 farad picofarad = 1e-12 farad [magn. flux] weber = V*s Wb = weber # weber maxwell = 1e-8 Wb [inductance] henry = Wb/A H = henry # henry millihenry = 0.001 henry mH = millihenry # millihenry microhenry = 0.001 mH [flux density] tesla = Wb/m^2 T = tesla # tesla gauss = maxwell/cm^2 # # molecular units # [molecular qty] mol = ! # mole # gram mole mole = mol # # gram mole gram mole = mol kilomole = 1000 mol kmol = kilomole # kilomole pound mole = mol*lbm/gram lbmol = pound mole # pound mole [size of a mol] avogadro = gram/(amu*mol) # # Illumination units # [lum. intens.] cd = ! # candela candela = cd [luminous flux] lumen = cd * sr lm = lumen # lumen [illuminance] lux = lumen/m^2 footcandle = lumen/ft^2 metercandle = lumen/m^2 [luminance] lambert = cd/(pi*cm^2) millilambert = 0.001 lambert footlambert = cd/(pi*ft^2) # # angular units # [angle] radian = ! rad = radian # radian circle = 2 pi*radian turn = circle revolution = circle rev = revolution # revolution degree = 1/360.0 circle deg = degree # degree arc min = 1/60.0 degree # minute arc minute = arc min min arc = arc min # minute minute arc = arc min arc sec = 1/60.0 arc min # second arc second = arc sec sec arc = arc sec # second second arc = arc sec quadrant = 1/4.0 circle right angle = quadrant gradian = 0.01 quadrant # # solid angle units # [solid angle] sr = ! # steradian steradian = sr sphere = 4 pi*sr hemisphere = 1/2.0 sphere # # information units # [data] bit = ! kilobit = 1000 bit # # based on power of 10 megabit = 1000 kilobit # # based on power of 10 byte = 8 bit B = byte # byte kilobyte = 1024 byte # # based on power of 2 kB = kilobyte # kilobyte # based on power of 2 megabyte = 1024 kB # # based on power of 2 MB = megabyte # megabyte # based on power of 2 gigabyte = 1024 MB # # based on power of 2 GB = gigabyte # gigabyte # based on power of 2 terabyte = 1024 GB # # based on power of 2 TB = terabyte # terabyte # based on power of 2 petabyte = 1024 TB # # based on power of 2 PB = petabyte # petabyte # based on power of 2 kilobyte IEC std = 1000 byte # # based on power of 10 kB IEC std = kilobyte IEC std # kilobyte # based on power of 10 megabyte IEC std = 1000 kB IEC std # # based on power of 10 MB IEC std = megabyte IEC std # megabyte # based on power of 10 gigabyte IEC std = 1000 MB IEC std # # based on power of 10 GB IEC std = gigabyte IEC std # gigabyte # based on power of 10 terabyte IEC std = 1000 GB IEC std # # based on power of 10 TB IEC std = terabyte IEC std # terabyte # based on power of 10 petabyte IEC std = 1000 TB IEC std # # based on power of 10 PB IEC std = petabyte IEC std # petabyte # based on power of 10 kibibyte = 1024 byte KiB = kibibyte # kibibyte mebibyte = 1024 KiB MiB = mebibyte # mebibyte gibibyte = 1024 MiB GiB = gibibyte # gibibyte tebibyte = 1024 GiB TiB = tebibyte # tebibyte pebibyte = 1024 TiB PiB = pebibyte # pebibyte [data transfer] bps = bit/sec # bits / second kbps = 1000 bps # kilobits / sec. # based on power of 10 # # Unitless numbers # [quantity] unit = !! 1 = unit # unit pi = 3.14159265358979323846 unit pair = 2 unit hat trick = 3 unit # # sports dozen = 12 unit doz = dozen # dozen bakers dozen = 13 unit score = 20 unit gross = 144 unit great gross = 12 gross ream = 500 unit percent = 0.01 unit % = percent mill = 0.001 unit [interest rate] APR = [unit] log(1 + x/100) ; (exp(x) - 1)*100 \ # annual % rate # based on continuous compounding [concentration] proof = 1/200.0 unit # # alcohol content ppm = 1e-6 unit # parts per million parts per million = ppm ppb = 1e-9 unit # parts per billion parts per billion = ppb ppt = 1e-12 unit # parts per trillion parts per trillion = ppt karat = 1/24.0 unit # # gold purity carat gold = karat # # gold purity # # force units # [force] newton = kg*m/s^2 N = newton # newton dekanewton = 10 newton kilonewton = 1000 N kN = kilonewton # kilonewton meganewton = 1000 kN millinewton = 0.001 N dyne = cm*g/s^2 kg force = kg * gravity # kilogram f kgf = kg force # kilogram force kilogram force = kg force kp = kg force # kilopond kilopond = kg force gram force = g * gravity pound force = lbm * gravity # # avoirdupois lbf = pound force # pound force # avoirdupois ton force = ton * gravity ounce force = ounce * gravity ozf = ounce force # ounce force tonne force = tonne * gravity # # metric pdl = lbm * ft / sec^2 # poundal # Imperial force poundal = pdl # # Imperial force # # area units # [area] barn = 1e-28 m^2 # # particle physics are = 100 m^2 decare = 10 are dekare = 10 are hectare = 100 are stremma = 1000 m^2 acre = 10 chain^2 section = mile^2 township = 36 section homestead = 160 acre square perch = 30.25 yd^2 rood = 0.25 acre rai = 1600 m^2 # # Thai ngaan = 400 m^2 # # Thai circular inch = 1/4.0 pi*in^2 # # area of 1 inch circle circular mil = 1/4.0 pi*mil^2 # # area of 1 mil circle AWG Area = [in^2] pi/4*pow(pow(92.0,(36-x)/39.0)/200.0,2) ; \ 36 - 39.0*log(200.0*sqrt(x*4.0/pi))/log(92.0) \ # American Wire Gauge \ # use -1, -2 for 00, 000; non-linear American Wire Gauge Area = [in^2] pi/4*pow(pow(92.0,(36-x)/39.0)/200.0,2) ; \ 36 - 39.0*log(200.0*sqrt(x*4.0/pi))/log(92.0) \ # # use -1, -2 for 00, 000; non-linear # # volume units # [volume] cc = cm^3 # cubic centimeter cubic centimeter = cc liter = 1000 cc l = liter # liter litre = liter deciliter = 0.1 liter centiliter = 0.01 liter milliliter = cc ml = milliliter # milliliter microliter = 1e-6 liter dekaliter = 10 liter hectoliter = 100 liter kiloliter = 1000 liter kl = kiloliter # kiloliter megaliter = 1000 kiloliter gallon = 231 in^3 # # US liquid gal = gallon # gallon # US liquid quart = 1/4.0 gallon # # US liquid qt = quart # quart # US liquid pint = 1/2.0 quart # # US liquid pt = pint # pint # US liquid fluid ounce = 1/16.0 pint # # US fl oz = fluid ounce # fluid ounce # US ounce fluid = fluid ounce # # US fluid dram = 1/8.0 fluid ounce # # US minim = 1/480.0 fluid ounce # # US imperial gallon = 4.54609 liter imp gal = imperial gallon # imperial gallon gallon imperial = imperial gallon imperial quart = 1/4.0 imp gal imp qt = imperial quart # imperial quart quart imperial = imperial quart imperial pint = 1/8.0 imp gal imp pt = imperial pint # imperial pint pint imperial = imperial pint imperial fluid ounce = 1/160.0 imp gal imp fl oz = imperial fluid ounce # imperial fluid ounce imperial fluid dram = 1/8.0 imp fl oz imperial minim = 1/480.0 imp fl oz cup = 8 fl oz tablespoon = 1/16.0 cup tbsp = tablespoon # tablespoon teaspoon = 1/3.0 tbsp tsp = teaspoon # teaspoon barrel = 42 gallon bbl = barrel # barrel shot = 1.5 fl oz fifth = 1/5.0 gallon # # alcohol wine bottle = 750 ml magnum = 1.5 liter # # alcohol keg = 15.5 gallon # # beer hogshead wine = 63 gal hogshead beer = 54 gal bushel = 2150.42 in^3 peck = 1/4.0 bushel cord = 128 ft^3 board foot = ft^2*in board feet = board foot # # velocity units # [velocity] knot = nmi/hr kt = knot # knot light speed = 2.99792458e8 m/s mph = mi/hr # miles/hour kph = km/hr # kilometers/hour mach = 340.29 m/s # # speed sound at STP [rot. velocity] rpm = rev/min # rev/min rps = rev/sec # rev/sec # # flow rate units # [fluid flow] gph = gal/hr # gallons/hour gpm = gal/min # gallons/minute cfs = ft^3/sec # cu ft/second cfm = ft^3/min # cu ft/minute lpm = l/min # liter/min [gas flow] sccm = atm*cc/min # std cc/min # pressure * flow sccs = atm*cc/sec # std cc/sec # pressure * flow slpm = atm*l/min # std liter/min # pressure * flow slph = atm*l/hr # std liter/hour # pressure * flow scfh = atm*ft^3/hour # std cu ft/hour # pressure * flow scfm = atm*ft^3/min # std cu ft/min # pressure * flow # # pressure units # [pressure] Pa = N/m^2 # pascal pascal = Pa hPa = 100 Pa # hectopascal hectopascal = hPa kPa = 1000 Pa # kilopascal kilopascal = kPa MPa = 1000 kPa # megapascal megapascal = MPa GPa = 1000 MPa # gigapascal gigapascal = GPa atm = 101325 Pa # atmosphere atmosphere = atm bar = 1e5 Pa mbar = 0.001 bar # millibar millibar = mbar microbar = 0.001 mbar decibar = 0.1 bar kilobar = 1000 bar megabar = 1000 kilobar mm Hg = mm*density Hg*gravity millimeter of Hg = mm Hg torr = mm Hg micron of Hg = micron*density Hg*gravity in Hg = in*density Hg*gravity # inch of Hg inch of Hg = in Hg m water = m*density water*gravity # meter of H2O # fresh water m H2O = m water # meter of H2O # fresh water meter of water = m water # # fresh water in water = in*density water*gravity # inch of H2O # fresh water in H2O = in water # inch of H2O # fresh water inch of water = in water # # fresh water ft water = ft*density water*gravity # feet of H2O # fresh water ft H2O = ft water # feet of H20 # fresh water feet of water = ft water # # fresh water foot of head = ft water # # fresh water ft hd = ft water # foot of head # fresh water psi = lbf/in^2 # pound / sq inch pound per sq inch = psi ksi = 1000 psi # 1000 lb / sq inch # # density units # [density] density water = gram/cm^3 density sea water = 1.025 gram/cm^3 density Hg = 13.5950981 gram/cm^3 density air = 1.293 kg/m^3 # # at STP density steel = 0.283 lb/in^3 # # carbon steel density aluminum = 0.098 lb/in^3 density zinc = 0.230 lb/in^3 density brass = 0.310 lb/in^3 # # 80Cu-20Zn density copper = 0.295 lb/in^3 density iron = 0.260 lb/in^3 # # cast iron density nickel = 0.308 lb/in^3 density tin = 0.275 lb/in^3 density titanium = 0.170 lb/in^3 density silver = 0.379 lb/in^3 density nylon = 0.045 lb/in^3 density polycarbonate = 0.045 lb/in^3 # # energy units # [energy] joule = N*m J = joule # joule kilojoule = 1000 joule kJ = kilojoule # kilojoule megajoule = 1000 kilojoule gigajoule = 1000 megajoule millijoule = 0.001 joule mJ = millijoule # millijoule calorie = 4.1868 J cal = calorie # calorie kilocalorie = 1000 cal kcal = kilocalorie # kilocalorie calorie food = kilocalorie thermie = 1000 kcal Btu = cal*lb*R/(g*K) # British thermal unit British thermal unit = Btu therm = 100000 Btu erg = cm*dyne electronvolt = 1.602176462e-19 J eV = electronvolt # electronvolt kWh = kW*hour # kilowatt-hour kilowatt hour = kWh ton TNT = 4.184e9 J tonne oil equivalent = 41.868 gigajoule tonne coal equivalent = 7000000 kcal # # power units # [power] watt = J/s W = watt # watt kilowatt = 1000 W kW = kilowatt # kilowatt megawatt = 1000 kW MW = megawatt # megawatt gigawatt = 1000 MW GW = gigawatt # gigawatt milliwatt = 0.001 W horsepower = 550 ft*lbf/sec hp = horsepower # horsepower metric horsepower = 75 kgf*m/s ton refrigeration = 12000 Btu/hr MBH = 1000 Btu/hr # 1000 Btu/hr # # frequency # [frequency] hertz = unit/sec Hz = hertz # hertz millihertz = 0.001 Hz kilohertz = 1000 Hz kHz = kilohertz # kilohertz megahertz = 1000 kHz MHz = megahertz # megahertz gigahertz = 1000 MHz GHz = gigahertz # gigahertz # # radioactivity # [radioactivity] becquerel = unit/sec Bq = becquerel # becquerel curie = 3.7e10 Bq millicurie = 0.001 curie roentgen = 2.58e-4 coulomb/kg [radiation dose] gray = J/kg Gy = gray # gray centigray = 0.01 Gy rad. abs. dose = 0.01 Gy # # commonly rad sievert = J/kg # # equiv. dose millisievert = 0.001 sievert # # equiv. dose Sv = sievert # sievert # equiv. dose rem = 0.01 Sv # # roentgen equiv mammal millirem = 0.001 rem # # roentgen equiv mammal # # viscosity # [dyn viscosity] poise = g/(cm*s) P = poise # poise centipoise = 0.01 poise cP = centipoise # centipoise [kin viscosity] stokes = cm^2/s St = stokes # stokes centistokes = 0.01 stokes cSt = centistokes # centistokes # # misc. units # [acceleration] gravity = 9.80665 m/s^2 galileo = cm/s^2 [constant] gravity constant = 6.673e-11 N*m^2/kg^2 gas constant = 8.314472 J/(mol*K) # R [fuel consumpt.] mpg = mi/gal # miles/gallon mpg imp = mi/gallon imperial # miles/gallon imp liter per 100 km = [mpg] 3.785411784 / (x * 0.01609344) ; \ 3.785411784 / (x * 0.01609344) # # non-linear [permeability] darcy = 1 cm^2*centipoise/atm/s millidarcy = 0.001 darcy """ class UnitGroup: "Stores, updates and converts a group of units" maxDecPlcs = 8 def __init__(self, unitData, option): self.unitData = unitData self.option = option self.unitList = [] self.currentNum = 0 self.factor = 1.0 self.reducedList = [] self.linear = 1 def update(self, text, cursorPos=None): "Decode user entered text into units" self.unitList = self.parseGroup(text) if cursorPos != None: self.updateCurrentUnit(text, cursorPos) else: self.currentNum = len(self.unitList) - 1 def updateCurrentUnit(self, text, cursorPos): "Set current unit number" self.currentNum = len(re.findall('[\*/]', text[:cursorPos])) def currentUnit(self): "Return current unit if its a full match, o/w None" if self.unitList and self.unitList[self.currentNum].equiv: return self.unitList[self.currentNum] return None def currentPartialUnit(self): "Return unit with at least a partial match, o/w None" if not self.unitList: return None return self.unitData.findPartialMatch(self.unitList[self.currentNum]\ .name) def currentSortPos(self): "Return unit near current unit for sorting" if not self.unitList: return self.unitData[self.unitData.sortedKeys[0]] return self.unitData.findSortPos(self.unitList[self.currentNum]\ .name) def replaceCurrent(self, unit): "Replace the current unit with unit" if self.unitList: exp = self.unitList[self.currentNum].exp self.unitList[self.currentNum] = copy.copy(unit) self.unitList[self.currentNum].exp = exp else: self.unitList.append(copy.copy(unit)) def completePartial(self): "Replace a partial unit with a full one" if self.unitList and not self.unitList[self.currentNum].equiv: text = self.unitList[self.currentNum].name unit = self.unitData.findPartialMatch(text) if unit: exp = self.unitList[self.currentNum].exp self.unitList[self.currentNum] = copy.copy(unit) self.unitList[self.currentNum].exp = exp def moveToNext(self, upward): "Replace unit with adjacent one based on match or sort position" unit = self.currentSortPos() num = self.unitData.sortedKeys.index(unit.name.\ replace(' ', '')) \ + (upward and -1 or 1) if 0 <= num < len(self.unitData.sortedKeys): self.replaceCurrent(self.unitData[self.unitData.sortedKeys[num]]) def addOper(self, mult): "Add new operator & blank unit after current, * if mult is true" if self.unitList: self.completePartial() prevExp = self.unitList[self.currentNum].exp self.currentNum += 1 self.unitList.insert(self.currentNum, Unit('')) if (not mult and prevExp > 0) or (mult and prevExp < 0): self.unitList[self.currentNum].exp = -1 def changeExp(self, newExp): "Change the current unit's exponent" if self.unitList: self.completePartial() if self.unitList[self.currentNum].exp > 0: self.unitList[self.currentNum].exp = newExp else: self.unitList[self.currentNum].exp = -newExp def clearUnit(self): "Remove units" self.unitList = [] def parseGroup(self, text): "Return list of units from text string" unitList = [] parts = [part.strip() for part in re.split('([\*/])', text)] numerator = 1 while parts: unit = self.parseUnit(parts.pop(0)) if not numerator: unit.exp = -unit.exp if parts and parts.pop(0) == '/': numerator = not numerator unitList.append(unit) return unitList def parseUnit(self, text): "Return a valid or invalid unit with exponent from a text string" parts = text.split('^', 1) exp = 1 if len(parts) > 1: # has exponent try: exp = int(parts[1]) except ValueError: if parts[1].lstrip().startswith('-'): exp = -Unit.partialExp # tmp invalid exp else: exp = Unit.partialExp unitText = parts[0].strip().replace(' ', '') unit = copy.copy(self.unitData.get(unitText, None)) if not unit and unitText and unitText[-1] == 's' and not \ self.unitData.findPartialMatch(unitText): # check for plural unit = copy.copy(self.unitData.get(unitText[:-1], None)) if not unit: #unit = Unit(parts[0].strip()) # tmp invalid unit raise UnitDataError('%s is not a valid unit.' % (unitText)) unit.exp = exp return unit def unitString(self, unitList=None): "Return the full string for this group or a given group" if unitList == None: unitList = self.unitList[:] fullText = '' if unitList: fullText = unitList[0].unitText(0) numerator = 1 for unit in unitList[1:]: if (numerator and unit.exp > 0) \ or (not numerator and unit.exp < 0): fullText = '%s * %s' % (fullText, unit.unitText(1)) else: fullText = '%s / %s' % (fullText, unit.unitText(1)) numerator = not numerator return fullText def groupValid(self): "Return 1 if all unitself.reducedLists are valid" if not self.unitList: return 0 for unit in self.unitList: if not unit.unitValid(): return 0 return 1 def reduceGroup(self): "Update reduced list of units and factor" self.linear = 1 self.reducedList = [] self.factor = 1.0 if not self.groupValid(): return count = 0 tmpList = self.unitList[:] while tmpList: count += 1 if count > 5000: raise UnitDataError('Circular unit definition') unit = tmpList.pop(0) if unit.equiv in ('!', '!!'): self.reducedList.append(copy.copy(unit)) elif not unit.equiv: raise UnitDataError('Invalid conversion for "%s"' % unit.name) else: if unit.fromEqn: self.linear = 0 newList = self.parseGroup(unit.equiv) for newUnit in newList: newUnit.exp *= unit.exp tmpList.extend(newList) self.factor *= unit.factor**unit.exp self.reducedList.sort() tmpList = self.reducedList[:] self.reducedList = [] for unit in tmpList: if self.reducedList and unit == self.reducedList[-1]: self.reducedList[-1].exp += unit.exp else: self.reducedList.append(unit) self.reducedList = [unit for unit in self.reducedList if \ unit.name != 'unit' and unit.exp != 0] def categoryMatch(self, otherGroup): "Return 1 if unit types are equivalent" if not self.checkLinear() or not otherGroup.checkLinear(): return 0 return self.reducedList == otherGroup.reducedList and \ [unit.exp for unit in self.reducedList] \ == [unit.exp for unit in otherGroup.reducedList] def checkLinear(self): "Return 1 if linear or acceptable non-linear" if not self.linear: if len(self.unitList) > 1 or self.unitList[0].exp != 1: return 0 return 1 def compatStr(self): "Return string with reduced unit or linear compatability problem" if self.checkLinear(): return self.unitString(self.reducedList) return 'Cannot combine non-linear units' def convert(self, num, toGroup): "Return num of this group converted to toGroup" if self.linear: num *= self.factor else: num = self.nonLinearCalc(num, 1) * self.factor n2 = -1 if toGroup.linear: n2 = num / toGroup.factor else: n2 = toGroup.nonLinearCalc(num / toGroup.factor, 0) return n2 def nonLinearCalc(self, num, isFrom): "Return result of non-linear calculation" x = num try: if self.unitList[0].toEqn: # regular equations if isFrom: temp = float(eval(self.unitList[0].fromEqn)) return temp temp = float(eval(self.unitList[0].toEqn)) return temp data = list(eval(self.unitList[0].fromEqn)) # extrapolation list if isFrom: data = [(float(group[0]), float(group[1])) for group in data] else: data = [(float(group[1]), float(group[0])) for group in data] data.sort() pos = len(data) - 1 for i in range(len(data)): if num <= data[i][0]: pos = i break if pos == 0: pos = 1 y = (num-data[pos-1][0]) / float(data[pos][0]-data[pos-1][0]) \ * (data[pos][1]-data[pos-1][1]) + data[pos-1][1] return y except OverflowError: return 1e9999 except: raise UnitDataError('Bad equation for %s' % self.unitList[0].name) def convertStr(self, num, toGroup): "Return formatted string of converted number" return self.formatNumStr(self.convert(num, toGroup)) def formatNumStr(self, num): "Return num string formatted per options" decPlcs = self.option.intData('DecimalPlaces', 0, UnitGroup.maxDecPlcs) if self.option.boolData('SciNotation'): return ('%%0.%dE' % decPlcs) % num if self.option.boolData('FixedDecimals'): return ('%%0.%df' % decPlcs) % num return ('%%0.%dG' % decPlcs) % num class UnitDataError(Exception): pass class UnitData(dict): def __init__(self): dict.__init__(self) self.sortedKeys = [] def readData(self): "Read all unit data from file" types = [] typeUnits = {} lines = unitData.splitlines() for i in range(len(lines)): # join continuation lines delta = 1 while lines[i].rstrip().endswith('\\'): lines[i] = ''.join([lines[i].rstrip()[:-1], lines[i+delta]]) lines[i+delta] = '' delta += 1 units = [Unit(line) for line in lines if \ line.split('#', 1)[0].strip()] # remove comment lines typeText = '' for unit in units: # find & set headings if unit.name.startswith('['): typeText = unit.name[1:-1].strip() types.append(typeText) typeUnits[typeText] = [] unit.typeName = typeText units = [unit for unit in units if unit.equiv] # keep valid units for unit in units: self[unit.name.replace(' ', '')] = unit typeUnits[unit.typeName].append(unit.name) self.sortedKeys = list(self.keys()) self.sortedKeys.sort() if len(self.sortedKeys) < len(units): raise UnitDataError('Duplicate unit names found') return (types, typeUnits) def findPartialMatch(self, text): "Return first partially matching unit or None" text = text.replace(' ', '') if not text: return None for name in self.sortedKeys: if name.startswith(text): return self[name] return None def findSortPos(self, text): "Return unit whose abbrev comes immediately after text" text = text.replace(' ', '') for name in self.sortedKeys: if text <= name: return self[name] return self[self.sortedKeys[-1]] class Unit: "Reads and stores a single unit conversion" partialExp = 1000 def __init__(self, dataStr): dataList = dataStr.split('#') unitList = dataList.pop(0).split('=', 1) self.name = unitList.pop(0).strip() self.equiv = '' self.factor = 1.0 self.fromEqn = '' # used only for non-linear units self.toEqn = '' # used only for non-linear units if unitList: self.equiv = unitList[0].strip() if self.equiv[0] == '[': # used only for non-linear units try: self.equiv, self.fromEqn = re.match('\[(.*?)\](.*)', \ self.equiv).groups() if ';' in self.fromEqn: self.fromEqn, self.toEqn = self.fromEqn.split(';', 1) self.toEqn = self.toEqn.strip() self.fromEqn = self.fromEqn.strip() except AttributeError: raise UnitDataError('Bad equation for "%s"' % self.name) else: # split factor and equiv unit for linear parts = self.equiv.split(None, 1) if len(parts) > 1 and re.search('[^\d\.eE\+\-\*/]', parts[0]) \ == None: # only allowed digits and operators try: self.factor = float(eval(parts[0])) self.equiv = parts[1] except: pass self.comments = [comm.strip() for comm in dataList] self.comments.extend([''] * (2 - len(self.comments))) self.exp = 1 self.viewLink = [None, None] self.typeName = '' def description(self): "Return name and 1st comment (usu. full name) if applicable" if self.comments[0]: return '%s (%s)' % (self.name, self.comments[0]) return self.name def unitValid(self): "Return 1 if unit and exponent are valid" if self.equiv and -Unit.partialExp < self.exp < Unit.partialExp: return 1 return 0 def unitText(self, absExp=0): "Return text for unit name with exponent or absolute value of exp" exp = self.exp if absExp: exp = abs(self.exp) if exp == 1: return self.name if -Unit.partialExp < exp < Unit.partialExp: return '%s^%d' % (self.name, exp) if exp > 1: return '%s^' % self.name else: return '%s^-' % self.name def __cmp__(self, other): return cmp(self.name, other.name) def __lt__(self, other): return self.name < other.name def __eq__(self, other): return self.name == other.name ############################################################################ # Wrapper functionality # ############################################################################ # Parse the data file, and set everything up for conversion data = UnitData() (types, unitsByType) = data.readData() # At the moment, we're not handling options option = None # set up the objects for unit conversion fromUnit = UnitGroup(data, option) toUnit = UnitGroup(data, option) def convert(num, unit1, unit2): """ Convert from one unit to another num is the factor for the first unit. Raises UnitDataError for various errors. """ fromUnit.update(unit1) toUnit.update(unit2) fromUnit.reduceGroup() toUnit.reduceGroup() # Match up unit categories if not fromUnit.categoryMatch(toUnit): raise UnitDataError('unit categories did not match') return fromUnit.convert(num, toUnit) def units(type): """ Return comma separated string list of units of given type, or a list of types if the argument is not valid. """ if type in types: return '%s units: %s' % (type, ', '.join(unitsByType[type])) else: return 'valid types: ' + ', '.join(types) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Math/locales/�����������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017321� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Math/locales/fi.po������������������������������������������������������0000644�0001750�0001750�00000013321�13634634532�020251� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: Math plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 11:59+EET\n" "PO-Revision-Date: 2014-12-20 12:16+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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-Generator: Poedit 1.6.10\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: plugin.py:53 msgid "" "Provides commands to work with math, such as a calculator and\n" " a unit converter." msgstr "" "Tarjoaa komentoja matematiikan kanssa työskentelyyn, kuten laskimen ja " "yksikkömuuntimen." #: plugin.py:57 #, fuzzy msgid "" "<fromBase> [<toBase>] <number>\n" "\n" " Converts <number> from base <fromBase> to base <toBase>.\n" " If <toBase> is left out, it converts to decimal.\n" " " msgstr "" "<fromBase> [<tobase>] <numero>\n" "\n" " Muuntaa <numeron> from base <fromBase> to base <toBase>.\n" " Jos <toBase> jätetään pois, se muuntaa desimaaliksi..\n" " " #: plugin.py:68 #, fuzzy msgid "Invalid <number> for base %s: %s" msgstr "Viallinen <numero> baselle %s: %s" #: plugin.py:74 #, fuzzy msgid "Convert a decimal number to another base; returns a string." msgstr "Muuunna desimaaliluku; toiseksi baseksi; palauttaa merkkijonon ." #: plugin.py:95 msgid "" "Convert a number from any base, 2 through 36, to any other\n" " base, 2 through 36. Returns a string." msgstr "" "Muunna basesta mikä tahansa numero, 2:sta 36:teen, millä tahansa muulla\n" " basella , 2:sta 36:teen. Palauttaa merkkiketjun." #: plugin.py:169 #, fuzzy msgid "" "<math expression>\n" "\n" " Returns the value of the evaluated <math expression>. The syntax " "is\n" " Python syntax; the type of arithmetic is floating point. Floating\n" " point arithmetic is used in order to prevent a user from being able " "to\n" " crash to the bot with something like '10**10**10**10'. One " "consequence\n" " is that large values such as '10**24' might not be exact.\n" " " msgstr "" "<matemaattinen lauseke>\n" "\n" " Palauttaa kehittyneen <matemaattisen lausekkeen> arvon. Syntaksi\n" " on Pythonin syntaksi; aritmeettisen leijumispisteen tyyppi. Leijuvan " "pisteen\n" " aritmeettiä käytetään estämään käyttäjää kaatamasta bottia, " "jollakin\n" " kuin '10**10**10**10'. Yksi sattuma on suurilla arvoilla, kuin\n" " '10**24' ei ehkä ole tarkka.\n" " " #: plugin.py:180 msgid "" "There's no reason you should have fancy non-ASCII characters in your " "mathematical expression. Please remove them." msgstr "" "Ei ole mitään syytä miksi matemaattisessa ilmaisussasi pitäisi olla kivoja " "ei-ASCII merkkejä. Ole hyvä ja poista ne." #: plugin.py:185 plugin.py:239 msgid "" "There's really no reason why you should have underscores or brackets in your " "mathematical expression. Please remove them." msgstr "" "Ei todella ole mitään syytä miksi sinulla pitäisi olla alaviivoja tai " "sulkuja matemaattisessa lausekkeessasi. Ole hyvä ja poista ne." #: plugin.py:191 plugin.py:247 msgid "You can't use lambda in this command." msgstr "Et voi käyttää Lambdaa tässä komennossa." #: plugin.py:221 plugin.py:255 msgid "The answer exceeded %s or so." msgstr "Vastaus ylittää %s:än tai niin." #: plugin.py:223 plugin.py:257 msgid "Something in there wasn't a valid number." msgstr "Jokin siinä ei ole kelvollinen numero." #: plugin.py:225 plugin.py:259 msgid "%s is not a defined function." msgstr "%s ei ole määritetty funktio." #: plugin.py:232 msgid "" "<math expression>\n" "\n" " This is the same as the calc command except that it allows integer\n" " math, and can thus cause the bot to suck up CPU. Hence it requires\n" " the 'trusted' capability to use.\n" " " msgstr "" "<matemaattinen lauseke>\n" "\n" " Tämä on sama kuin calc komento, paitsi tämä sallii loputtoman\n" " matematiikan ja näin aiheuttaa botin imevän kaiken prosessorin " "suorituskyvyn. Tästä johtuen se vaatii\n" " 'trusted' valtuuden.\n" " " #: plugin.py:269 #, fuzzy msgid "" "<rpn math expression>\n" "\n" " Returns the value of an RPN expression.\n" " " msgstr "" "<rpn matemaattinen lauseke>\n" "\n" " Palauttaa RPN lausekkeen arvon.\n" " " #: plugin.py:294 msgid "Not enough arguments for %s" msgstr "Ei tarpeeksi parametrejä %s:lle." #: plugin.py:307 msgid "%q is not a defined function." msgstr "%q ei ole määritetty funktio." #: plugin.py:314 msgid "Stack: [%s]" msgstr "Pino: [%s]" #: plugin.py:318 msgid "" "[<number>] <unit> to <other unit>\n" "\n" " Converts from <unit> to <other unit>. If number isn't given, it\n" " defaults to 1. For unit information, see 'units' command.\n" " " msgstr "" "[<numero>] <yksikkö> to <toiseksi yksiköksi>\n" "\n" " Muuntaa <yksiköstä> to <toiseen yksikköön>. Jos numeroa ei ole " "annettu, se\n" " on oletuksena 1. Yksikkö tiedoille, katso 'units' komento.\n" " " #: plugin.py:348 msgid "" " [<type>]\n" "\n" " With no arguments, returns a list of measurement types, which can " "be\n" " passed as arguments. When called with a type as an argument, " "returns\n" " the units of that type.\n" " " msgstr "" " [<tyyppi>]\n" "\n" " Ilman parametrejä, palauttaa listan arviointi tyyppejä, joita " "voidaan\n" " käyttää parametreinä. Kun kutsuttu tyyppinä parametrissä, palaittaa\n" " sen tyyppiset yksiköt.\n" " " ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Math/locales/fr.po������������������������������������������������������0000644�0001750�0001750�00000012143�13634634532�020263� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-01-21 19:30+CET\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: Limnoria <progval@gmail.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" "Language: fr\n" #: plugin.py:55 msgid "" "<fromBase> [<toBase>] <number>\n" "\n" " Converts <number> from base <fromBase> to base <toBase>.\n" " If <toBase> is left out, it converts to decimal.\n" " " msgstr "" "<base 1> [<base 2>] <nombre>\n" "\n" "Converti le nombre d'une base à l'autre. La seconde base est la décimale par " "défaut." #: plugin.py:66 msgid "Invalid <number> for base %s: %s" msgstr "Nombre invalide pour la base %s : %s" #: plugin.py:72 msgid "Convert a decimal number to another base; returns a string." msgstr "Converti un nombre décimal dans une autre base, retourne une chaîne." #: plugin.py:93 msgid "" "Convert a number from any base, 2 through 36, to any other\n" " base, 2 through 36. Returns a string." msgstr "" "Convertir un nombre de n'importe quelle base, de 2 à 36, à n'importe quelle " "base, de 2 à 36." #: plugin.py:167 msgid "" "<math expression>\n" "\n" " Returns the value of the evaluated <math expression>. The syntax " "is\n" " Python syntax; the type of arithmetic is floating point. Floating\n" " point arithmetic is used in order to prevent a user from being able " "to\n" " crash to the bot with something like '10**10**10**10'. One " "consequence\n" " is that large values such as '10**24' might not be exact.\n" " " msgstr "" "<expression mathématique>\n" "\n" "Retourne la valeur de l'<expression mathématique> évaluée. La syntaxe est " "celle de Python ; le calcul est à virgule flottante. Les calculs à virgule " "flottante sont utilisés pour enmpêcher l'utilisateur de faire crasher le bot " "avec quelque chose du genre '10**10**10**10'. L'une des conséquences est que " "les grandes valeurs commandes '10**24' peuvent ne pas être exactes." #: plugin.py:178 msgid "" "There's no reason you should have fancy non-ASCII characters in your " "mathematical expression. Please remove them." msgstr "" "Il n'y a aucune réelle raison que vous mettiez des caractères non-ASCII dans " "vos expressions mathématiques. Merci de les retirer." #: plugin.py:183 plugin.py:237 msgid "" "There's really no reason why you should have underscores or brackets in your " "mathematical expression. Please remove them." msgstr "" "Il n'y a aucune réelle raison que vous mettiez des underscores ou des " "crochets dans vos expressions mathématiques. Merci de les retirer." #: plugin.py:189 plugin.py:245 msgid "You can't use lambda in this command." msgstr "Vous ne pouvez utiliser lambda dans cette commande." #: plugin.py:219 plugin.py:253 msgid "The answer exceeded %s or so." msgstr "La réponse dépasse %s." #: plugin.py:221 plugin.py:255 msgid "Something in there wasn't a valid number." msgstr "Quelque chose là-dedans n'est pas un nombre valide" #: plugin.py:223 plugin.py:257 msgid "%s is not a defined function." msgstr "%s n'est pas une fonction définie" #: plugin.py:230 msgid "" "<math expression>\n" "\n" " This is the same as the calc command except that it allows integer\n" " math, and can thus cause the bot to suck up CPU. Hence it requires\n" " the 'trusted' capability to use.\n" " " msgstr "" "<expression mathématique>\n" "\n" "Cette commande est la même que la commande 'calc', excepté qu'elle utilise " "des mathématiques entières, ce qui peut causer une surconsommation de CPU de " "la part du bot. C'est pourquoi elle requiert la capacité 'trusted'." #: plugin.py:267 msgid "" "<rpn math expression>\n" "\n" " Returns the value of an RPN expression.\n" " " msgstr "" "<expression mathématique NPI>\n" "\n" "Retourne la valeur de l'expression mathématique NPI." #: plugin.py:292 msgid "Not enough arguments for %s" msgstr "Pas assez d'arguments pour %s." #: plugin.py:305 msgid "%q is not a defined function." msgstr "%q n'est pas une fonction définie." #: plugin.py:312 msgid "Stack: [%s]" msgstr "Pile : [%s]" #: plugin.py:316 msgid "" "[<number>] <unit> to <other unit>\n" "\n" " Converts from <unit> to <other unit>. If number isn't given, it\n" " defaults to 1. For unit information, see 'units' command.\n" " " msgstr "" "[<nombre>] <unité> to <autre unité>\n" "\n" "Convertit de l'<unité> à l'<autre unité>. Si le nombre n'est pas donné, il " "vaut 1 par défaut. Pour plus d'informations sur les unités, utilisez la " "commande 'units'." #: plugin.py:346 msgid "" " [<type>]\n" "\n" " With no arguments, returns a list of measurement types, which can " "be\n" " passed as arguments. When called with a type as an argument, " "returns\n" " the units of that type.\n" " " msgstr "" "[<type>]\n" "\n" "Sans argument, retourne la liste des types de mesures, qui peuvent être " "passés en argument. Lors de l'appel avec un argument, retourne les unités de " "chaque type." �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Math/locales/hu.po������������������������������������������������������0000644�0001750�0001750�00000012027�13634634532�020271� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # msgid "" msgstr "" "Project-Id-Version: Limnoria Math\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-08-02 21:06+0200\n" "Last-Translator: nyuszika7h <litemininyuszika@gmail.com>\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" #: plugin.py:52 #, docstring msgid "" "<fromBase> [<toBase>] <number>\n" "\n" " Converts <number> from base <fromBase> to base <toBase>.\n" " If <toBase> is left out, it converts to decimal.\n" " " msgstr "" "<alapról> [<alapra>] <szám>\n" "\n" "Konvertálja <szám>-ot <alapról> alapról <alapra> alapra. Ha <alapra> nincs megadva, decimálisra konvertál." #: plugin.py:63 msgid "Invalid <number> for base %s: %s" msgstr "Érvénytelen <szám> %s alaphoz: %s" #: plugin.py:69 #, docstring msgid "Convert a decimal number to another base; returns a string." msgstr "Konvertál egy decimális számot egy másik alapra; kiír egy karakterláncot." #: plugin.py:90 #, docstring msgid "" "Convert a number from any base, 2 through 36, to any other\n" " base, 2 through 36. Returns a string." msgstr "" "Konvertál egy számot bármilyen alapról 2 és 36 között bármilyen másik bázisra 2 és 36 között. Kiír egy karakterláncot." #: plugin.py:157 #, docstring msgid "" "<math expression>\n" "\n" " Returns the value of the evaluated <math expression>. The syntax is\n" " Python syntax; the type of arithmetic is floating point. Floating\n" " point arithmetic is used in order to prevent a user from being able to\n" " crash to the bot with something like '10**10**10**10'. One consequence\n" " is that large values such as '10**24' might not be exact.\n" " " msgstr "" "<matematikai kifejezés>\n" "\n" "Kiírja a kiértékelt <matematikai kifejezés> értékét. A szintaxis Python szintaxis; a számtan típusa lebegőpontos. Lebegőpontos számtan használt azért, hogy megakadályozzuk a felhasználókat, hogy képesek legyenek előidézni a bot összeomlással valami olyannal, mint '10**10**10**10'. Egy következmény, hogy nagy értékek, mint a '10**24' lehet, hogy nem lesznek pontosak." #: plugin.py:166 plugin.py:220 msgid "There's really no reason why you should have underscores or brackets in your mathematical expression. Please remove them." msgstr "Tényleg nincs semmi ok, hogy miért legyenek aláhúzások vagy zárójelek a matamatikai kifejezésedben. Kérlek távolítsd el őket." #: plugin.py:172 plugin.py:228 msgid "You can't use lambda in this command." msgstr "Nem használhatsz lambdát ebben a parancsban." #: plugin.py:202 plugin.py:236 msgid "The answer exceeded %s or so." msgstr "A válasz meghaladta %s-t." #: plugin.py:204 plugin.py:238 msgid "Something in there wasn't a valid number." msgstr "Valami itt nem egy érvényes szám." #: plugin.py:206 plugin.py:240 msgid "%s is not a defined function." msgstr "%s nincs meghatározva függvényként." #: plugin.py:213 #, docstring msgid "" "<math expression>\n" "\n" " This is the same as the calc command except that it allows integer\n" " math, and can thus cause the bot to suck up CPU. Hence it requires\n" " the 'trusted' capability to use.\n" " " msgstr "" "<matematikai kifejezés>\n" "\n" "Ez ugyanaz, mint a calc parancs, kivéve, hogy engedélyez egész számos matematikát, és előidézheti, hogy a bot felszívjon sok CPU-t. Ezért a 'trusted' képesség szükséges a használatához." #: plugin.py:250 #, docstring msgid "" "<rpn math expression>\n" "\n" " Returns the value of an RPN expression.\n" " " msgstr "" "<rpn matematikai kifejezés>\n" "\n" "Kiírja egy RPN kifejezés értékét." #: plugin.py:275 msgid "Not enough arguments for %s" msgstr "Nincs elég paraméter %s-hoz." #: plugin.py:288 msgid "%q is not a defined function." msgstr "%q nincs meghatározva függvényként." #: plugin.py:295 msgid "Stack: [%s]" msgstr "Verem: [%s]" #: plugin.py:299 #, docstring msgid "" "[<number>] <unit> to <other unit>\n" "\n" " Converts from <unit> to <other unit>. If number isn't given, it\n" " defaults to 1. For unit information, see 'units' command.\n" " " msgstr "" "[<szám>] <mértékegység> to <másik mértékegység>\n" "\n" "<mértékegység>-ről <másik mértékegyég>-re konvertál. Ha a szám nem megadott, alapértelmezett értéke 1. Mértékegység információért lásd az 'units' parancsot." #: plugin.py:314 #, docstring msgid "" " [<type>]\n" "\n" " With no arguments, returns a list of measurement types, which can be\n" " passed as arguments. When called with a type as an argument, returns\n" " the units of that type.\n" " " msgstr "" "[<típus>]\n" "\n" "Paraméterek nélkül kiírja a mértékegységek listáját, amelyek megadhatók paraméterenként. Amikor egy típussal van meghívva paraméterként, kiírja a típus mértékegységeit." ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Math/locales/it.po������������������������������������������������������0000644�0001750�0001750�00000012052�13634634532�020267� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-21 17:27+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugin.py:52 #, docstring msgid "" "<fromBase> [<toBase>] <number>\n" "\n" " Converts <number> from base <fromBase> to base <toBase>.\n" " If <toBase> is left out, it converts to decimal.\n" " " msgstr "" "<daBase> [<aBase>] <numero>\n" "\n" " Converte un numero da una base a un'altra.\n" " Se <aBase> non è specificata, converte in decimale.\n" " " #: plugin.py:63 msgid "Invalid <number> for base %s: %s" msgstr "Numero non valido per base %s: %s" #: plugin.py:69 #, docstring msgid "Convert a decimal number to another base; returns a string." msgstr "Converte un numero decimale in un'altra base, restituisce una stringa." #: plugin.py:90 #, docstring msgid "" "Convert a number from any base, 2 through 36, to any other\n" " base, 2 through 36. Returns a string." msgstr "" "Converte un numero da qualsiasi base tra 2 e 36 ad un'altra tra 2 e 36, restituisce una stringa.\n" #: plugin.py:157 #, docstring msgid "" "<math expression>\n" "\n" " Returns the value of the evaluated <math expression>. The syntax is\n" " Python syntax; the type of arithmetic is floating point. Floating\n" " point arithmetic is used in order to prevent a user from being able to\n" " crash to the bot with something like '10**10**10**10'. One consequence\n" " is that large values such as '10**24' might not be exact.\n" " " msgstr "" "<espressione matematica>\n" "\n" " Restituisce il valore dell'<espressione matematica> valutata. La sintassi\n" " è quella di Python; il tipo di aritmetica è in virgola mobile. Quest'ultima\n" " è utilizzata per prevenire che un utente sia in grado di mandare in crash il\n" " bot con qualcosa di simile a \"10**10**10**10\". Una delle conseguenze però è\n" " che valori di grandi dimensioni come \"10**24\" potrebbero non essere precisi.\n" " " #: plugin.py:166 plugin.py:220 msgid "There's really no reason why you should have underscores or brackets in your mathematical expression. Please remove them." msgstr "Non v'è alcuna ragione di usare underscore o parentesi nelle espressioni matematiche; ti invito a rimuoverli." #: plugin.py:172 plugin.py:228 msgid "You can't use lambda in this command." msgstr "Non è possibile usare lambda in questo comando." #: plugin.py:202 plugin.py:236 msgid "The answer exceeded %s or so." msgstr "La risposta ha superato %s." #: plugin.py:204 plugin.py:238 msgid "Something in there wasn't a valid number." msgstr "Qualcosa non equivaleva a un numero valido." #: plugin.py:206 plugin.py:240 msgid "%s is not a defined function." msgstr "%s non è una funzione definita." #: plugin.py:213 #, docstring msgid "" "<math expression>\n" "\n" " This is the same as the calc command except that it allows integer\n" " math, and can thus cause the bot to suck up CPU. Hence it requires\n" " the 'trusted' capability to use.\n" " " msgstr "" "<espressione matematica>\n" "\n" " Questo comando è identico a \"calc\" eccetto che permette numeri interi\n" " e può causare l'uso massiccio di CPU da parte del bot. Per questo motivo\n" " richiede la capacità \"trusted\" per poterlo utilizzare.\n" " " #: plugin.py:250 #, docstring msgid "" "<rpn math expression>\n" "\n" " Returns the value of an RPN expression.\n" " " msgstr "" "<espressione matematica rpn>\n" "\n" " Restituisce il valore di un'espressione RPN (Reverse Polish Notation).\n" " " #: plugin.py:275 msgid "Not enough arguments for %s" msgstr "Argomenti per %s insufficienti" #: plugin.py:288 msgid "%q is not a defined function." msgstr "%q non è una funzione definita." #: plugin.py:295 msgid "Stack: [%s]" msgstr "Stack: [%s]" #: plugin.py:299 #, docstring msgid "" "[<number>] <unit> to <other unit>\n" "\n" " Converts from <unit> to <other unit>. If number isn't given, it\n" " defaults to 1. For unit information, see 'units' command.\n" " " msgstr "" "[<numero>] <unità> ad <altra unità>\n" "\n" " Converte da <unità> ad <altra unità>. Se <numero> non è specificato,\n" " usa 1 come predefinito. Per informazioni sulle unità, utilizza il comando \"units\".\n" " " #: plugin.py:314 #, docstring msgid "" " [<type>]\n" "\n" " With no arguments, returns a list of measurement types, which can be\n" " passed as arguments. When called with a type as an argument, returns\n" " the units of that type.\n" " " msgstr "" " [<tipo>]\n" "\n" " Senza argomenti restituisce un elenco di tipi di misura che possono essere\n" " passati come argomento. Quando viene chiamato con un tipo come argomento,\n" " riporta le unità di quel tipo.\n" " " ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Math/plugin.py����������������������������������������������������������0000644�0001750�0001750�00000024714�13634634532�017551� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2008-2009, James McCoy # 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 __future__ import division import re import math import cmath import types import string import supybot.utils as utils from supybot.commands import * import supybot.utils.minisix as minisix import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Math') from .local import convertcore from supybot.utils.math_evaluator import safe_eval, InvalidNode, SAFE_ENV baseArg = ('int', 'base', lambda i: i <= 36) class Math(callbacks.Plugin): """Provides commands to work with math, such as a calculator and a unit converter.""" @internationalizeDocstring def base(self, irc, msg, args, frm, to, number): """<fromBase> [<toBase>] <number> Converts <number> from base <fromBase> to base <toBase>. If <toBase> is left out, it converts to decimal. """ if not number: number = str(to) to = 10 try: irc.reply(self._convertBaseToBase(number, to, frm)) except ValueError: irc.error(_('Invalid <number> for base %s: %s') % (frm, number)) base = wrap(base, [('int', 'base', lambda i: 2 <= i <= 36), optional(('int', 'base', lambda i: 2 <= i <= 36), 10), additional('something')]) def _convertDecimalToBase(self, number, base): """Convert a decimal number to another base; returns a string.""" if number == 0: return '0' elif number < 0: negative = True number = -number else: negative = False digits = [] while number != 0: digit = number % base if digit >= 10: digit = string.ascii_uppercase[digit - 10] else: digit = str(digit) digits.append(digit) number = number // base digits.reverse() return '-'*negative + ''.join(digits) def _convertBaseToBase(self, number, toBase, fromBase): """Convert a number from any base, 2 through 36, to any other base, 2 through 36. Returns a string.""" number = minisix.long(str(number), fromBase) if toBase == 10: return str(number) return self._convertDecimalToBase(number, toBase) def _floatToString(self, x): if -1e-10 < x < 1e-10: return '0' elif -1e-10 < int(x) - x < 1e-10: return str(int(x)) else: return str(x) def _complexToString(self, x): realS = self._floatToString(x.real) imagS = self._floatToString(x.imag) if imagS == '0': return realS elif imagS == '1': imagS = '+i' elif imagS == '-1': imagS = '-i' elif x.imag < 0: imagS = '%si' % imagS else: imagS = '+%si' % imagS if realS == '0' and imagS == '0': return '0' elif realS == '0': return imagS.lstrip('+') elif imagS == '0': return realS else: return '%s%s' % (realS, imagS) @internationalizeDocstring def calc(self, irc, msg, args, text): """<math expression> Returns the value of the evaluated <math expression>. The syntax is Python syntax; the type of arithmetic is floating point. Floating point arithmetic is used in order to prevent a user from being able to crash to the bot with something like '10**10**10**10'. One consequence is that large values such as '10**24' might not be exact. """ try: self.log.info('evaluating %q from %s', text, msg.prefix) x = complex(safe_eval(text, allow_ints=False)) irc.reply(self._complexToString(x)) except OverflowError: maxFloat = math.ldexp(0.9999999999999999, 1024) irc.error(_('The answer exceeded %s or so.') % maxFloat) except InvalidNode as e: irc.error(_('Invalid syntax: %s') % e.args[0]) except NameError as e: irc.error(_('%s is not a defined function.') % e.args[0]) except MemoryError: irc.error(_('Memory error (too much recursion?)')) except Exception as e: irc.error(str(e)) calc = wrap(calc, ['text']) @internationalizeDocstring def icalc(self, irc, msg, args, text): """<math expression> This is the same as the calc command except that it allows integer math, and can thus cause the bot to suck up CPU. Hence it requires the 'trusted' capability to use. """ try: self.log.info('evaluating %q from %s', text, msg.prefix) x = safe_eval(text, allow_ints=True) irc.reply(str(x)) except OverflowError: maxFloat = math.ldexp(0.9999999999999999, 1024) irc.error(_('The answer exceeded %s or so.') % maxFloat) except InvalidNode as e: irc.error(_('Invalid syntax: %s') % e.args[0]) except NameError as e: irc.error(_('%s is not a defined function.') % str(e).split()[1]) except Exception as e: irc.error(utils.exnToString(e)) icalc = wrap(icalc, [('checkCapability', 'trusted'), 'text']) _rpnEnv = { 'dup': lambda s: s.extend([s.pop()]*2), 'swap': lambda s: s.extend([s.pop(), s.pop()]) } def rpn(self, irc, msg, args): """<rpn math expression> Returns the value of an RPN expression. """ stack = [] for arg in args: try: x = complex(arg) if x == abs(x): x = abs(x) stack.append(x) except ValueError: # Not a float. if arg in SAFE_ENV: f = SAFE_ENV[arg] if callable(f): called = False arguments = [] while not called and stack: arguments.append(stack.pop()) try: stack.append(f(*arguments)) called = True except TypeError: pass if not called: irc.error(_('Not enough arguments for %s') % arg) return else: stack.append(f) elif arg in self._rpnEnv: self._rpnEnv[arg](stack) else: arg2 = stack.pop() arg1 = stack.pop() s = '%s%s%s' % (arg1, arg, arg2) try: stack.append(safe_eval(s, allow_ints=False)) except SyntaxError: irc.error(format(_('%q is not a defined function.'), arg)) return if len(stack) == 1: irc.reply(str(self._complexToString(complex(stack[0])))) else: s = ', '.join(map(self._complexToString, list(map(complex, stack)))) irc.reply(_('Stack: [%s]') % s) @internationalizeDocstring def convert(self, irc, msg, args, number, unit1, unit2): """[<number>] <unit> to <other unit> Converts from <unit> to <other unit>. If number isn't given, it defaults to 1. For unit information, see 'units' command. """ try: digits = len(str(number).split('.')[1]) except IndexError: digits = 0 try: newNum = convertcore.convert(number, unit1, unit2) if isinstance(newNum, float): zeros = 0 for char in str(newNum).split('.')[1]: if char != '0': break zeros += 1 # Let's add one signifiant digit. Physicists would not like # that, but common people usually do not give extra zeros... # (for example, with '32 C to F', an extra digit would be # expected). newNum = round(newNum, digits + 1 + zeros) newNum = self._floatToString(newNum) irc.reply(str(newNum)) except convertcore.UnitDataError as ude: irc.error(str(ude)) convert = wrap(convert, [optional('float', 1.0),'something','to','text']) @internationalizeDocstring def units(self, irc, msg, args, type): """ [<type>] With no arguments, returns a list of measurement types, which can be passed as arguments. When called with a type as an argument, returns the units of that type. """ irc.reply(convertcore.units(type)) units = wrap(units, [additional('text')]) Class = Math # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������limnoria-2020.03.17/plugins/Math/test.py������������������������������������������������������������0000644�0001750�0001750�00000021374�13634634532�017231� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2008, James McCoy # 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 __future__ import print_function from supybot.test import * class MathTestCase(PluginTestCase): plugins = ('Math',) def testBase(self): self.assertNotRegexp('base 56 asdflkj', 'ValueError') self.assertResponse('base 16 2 F', '1111') self.assertResponse('base 2 16 1111', 'F') self.assertResponse('base 20 BBBB', '92631') self.assertResponse('base 10 20 92631', 'BBBB') self.assertResponse('base 2 36 10', '2') self.assertResponse('base 36 2 10', '100100') self.assertResponse('base 2 1010101', '85') self.assertResponse('base 2 2 11', '11') self.assertResponse('base 12 0', '0') self.assertResponse('base 36 2 0', '0') self.assertNotError("base 36 " +\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ") self.assertResponse("base 10 36 [base 36 " +\ "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"\ "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"\ "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"\ "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"\ "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"\ "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"\ "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz]", "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ") self.assertResponse('base 2 10 [base 10 2 12]', '12') self.assertResponse('base 16 2 [base 2 16 110101]', '110101') self.assertResponse('base 10 8 [base 8 76532]', '76532') self.assertResponse('base 10 36 [base 36 csalnwea]', 'CSALNWEA') self.assertResponse('base 5 4 [base 4 5 212231]', '212231') self.assertError('base 37 1') self.assertError('base 1 1') self.assertError('base 12 1 1') self.assertError('base 1 12 1') self.assertError('base 1.0 12 1') self.assertError('base A 1') self.assertError('base 4 4') self.assertError('base 10 12 A') self.assertRegexp('base 2 10 [base 10 2 -12]', '-12') self.assertRegexp('base 16 2 [base 2 16 -110101]', '-110101') def testCalc(self): self.assertResponse('calc 5*0.06', str(5*0.06)) self.assertResponse('calc 2.0-7.0', str(2-7)) self.assertResponse('calc e**(i*pi)+1', '0') if minisix.PY3: # Python 2 has bad handling of exponentiation of negative numbers self.assertResponse('calc (-1)**.5', 'i') self.assertRegexp('calc (-5)**.5', '2.236067977[0-9]+i') self.assertRegexp('calc -((-5)**.5)', '-2.236067977[0-9]+i') self.assertNotRegexp('calc [9, 5] + [9, 10]', 'TypeError') self.assertError('calc [9, 5] + [9, 10]') self.assertNotError('calc degrees(2)') self.assertNotError('calc (2 * 3) - 2*(3*4)') self.assertNotError('calc (3) - 2*(3*4)') self.assertNotError('calc (1600 * 1200) - 2*(1024*1280)') self.assertNotError('calc 3-2*4') self.assertNotError('calc (1600 * 1200)-2*(1024*1280)') self.assertError('calc factorial(20000)') def testCalcNoNameError(self): self.assertRegexp('calc foobar(x)', 'foobar is not a defined function') def testCalcInvalidNode(self): self.assertRegexp('calc {"foo": "bar"}', 'Illegal construct Dict') def testCalcImaginary(self): self.assertResponse('calc 3 + sqrt(-1)', '3+i') def testCalcFloorWorksWithSqrt(self): self.assertNotError('calc floor(sqrt(5))') def testCaseInsensitive(self): self.assertNotError('calc PI**PI') def testCalcMaxMin(self): self.assertResponse('calc max(1,2)', '2') self.assertResponse('calc min(1,2)', '1') def testCalcStrFloat(self): self.assertResponse('calc 3+33333333333333', '33333333333336') def testCalcMemoryError(self): self.assertRegexp('calc ' + '('*10000, '(too much recursion' # cpython '|parenthesis is never closed)' # pypy ) def testICalc(self): self.assertResponse('icalc 1^1', '0') self.assertResponse('icalc 10**24', '1' + '0'*24) self.assertRegexp('icalc 49/6', '8.16') self.assertNotError('icalc factorial(20000)') def testRpn(self): self.assertResponse('rpn 5 2 +', '7') self.assertResponse('rpn 1 2 3 +', 'Stack: [1, 5]') self.assertResponse('rpn 1 dup', 'Stack: [1, 1]') self.assertResponse('rpn 2 3 4 + -', str(2-7)) self.assertNotError('rpn 2 degrees') def testRpnSwap(self): self.assertResponse('rpn 1 2 swap', 'Stack: [2, 1]') def testRpmNoSyntaxError(self): self.assertNotRegexp('rpn 2 3 foobar', 'SyntaxError') def testConvert(self): self.assertResponse('convert 1 m to cm', '100') self.assertResponse('convert m to cm', '100') self.assertResponse('convert 3 metres to km', '0.003') self.assertResponse('convert 32 F to C', '0') self.assertResponse('convert 32 C to F', '89.6') self.assertResponse('convert [calc 2*pi] rad to degree', '360') self.assertResponse('convert amu to atomic mass unit', '1') self.assertResponse('convert [calc 2*pi] rad to circle', '1') self.assertError('convert 1 meatball to bananas') self.assertError('convert 1 gram to meatballs') self.assertError('convert 1 mol to grams') self.assertError('convert 1 m to kpa') def testConvertSingularPlural(self): self.assertResponse('convert [calc 2*pi] rads to degrees', '360') self.assertResponse('convert 1 carat to grams', '0.2') self.assertResponse('convert 10 lbs to oz', '160') self.assertResponse('convert mA to amps', '0.001') def testConvertCaseSensitivity(self): self.assertError('convert MA to amps') self.assertError('convert M to amps') self.assertError('convert Radians to rev') def testUnits(self): self.assertNotError('units') self.assertNotError('units mass') self.assertNotError('units flux density') def testAbs(self): self.assertResponse('calc abs(2)', '2') self.assertResponse('calc abs(-2)', '2') self.assertResponse('calc abs(2.0)', '2') self.assertResponse('calc abs(-2.0)', '2') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/MessageParser/����������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017547� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/MessageParser/__init__.py�����������������������������������������������0000644�0001750�0001750�00000005147�13634634532�021661� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # 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. ### """ MessageParser can be configured to run commands when a message matches a given trigger. """ 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__ = "0.1" __author__ = supybot.Author('Daniel Folkinshteyn', 'nanotube', 'nanotube@users.sourceforge.net') __maintainer__ = supybot.authors.limnoria_core # 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__ = '' from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/MessageParser/config.py�������������������������������������������������0000644�0001750�0001750�00000010504�13634634532�021360� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # 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 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 def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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, 'enableForNotices', registry.Boolean(False, _("""Determines whether the message parser is enabled for NOTICE messages too."""))) 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 regexps when shown by the list command."""))) conf.registerChannelValue(MessageParser, 'maxTriggers', registry.Integer(0, _("""Determines the maximum number of triggers in one message. Set this to 0 to allow an infinite number of triggers."""))) # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/MessageParser/locales/��������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�021171� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/MessageParser/locales/fi.po���������������������������������������������0000644�0001750�0001750�00000025775�13634634532�022141� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: \n" "POT-Creation-Date: 2014-03-22 12:41+EET\n" "PO-Revision-Date: 2014-03-22 14:39+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\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-Generator: Poedit 1.5.4\n" #: config.py:57 msgid "" "Determines whether the\n" " message parser is enabled. If enabled, will trigger on regexps\n" " added to the regexp db." msgstr "" "Määrittää onko viestikursija käytössä.\n" " Jos käytössä aktivoituu säännöllisissä lausekkeissa, jotka on\n" " lisätty säännöllinen lauseke tietokantaan." #: config.py:61 msgid "" "Determines whether the message parser\n" " is enabled for NOTICE messages too." msgstr "" "Määrittää onko viestien parsija käytössä\n" " myös NOTICE viesteille." #: config.py:64 msgid "" "Determines whether we keep updating the usage\n" " count for each regexp, for popularity ranking." msgstr "" "Määrottää pidetäänkö käyttö\n" " count for each regexp, for popularity ranking." #: config.py:67 msgid "" "Determines the number of regexps returned\n" " by the triggerrank command." msgstr "" "Määrittää palautuneiden säännöllisten lausekkeiden, jotka palautuvat\n" " triggerrank komennolla, määrän." #: config.py:70 msgid "" "Determines the capability required (if any) to\n" " vacuum the database." msgstr "" "Määrittää valtuuden (jos mikään), joka on vaadittu\n" " tietokannan tyhjentämiseen." #: config.py:73 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 "" "Määrittää\n" " valtuudet, jotka (jos mitkään) on vaadittu hallitsemaan säännöllinen " "lauseke tietokantaa,\n" " sisältäen lisää, poista, lukitse, avaa. Käytä 'kanava,valtuus':ksia\n" " kanava tason valtuuksille.\n" " Huomaa, että jopa antivaltuuden olemassaolo tarkoittaa, että käyttäjällä " "on \n" " valtuus." #: config.py:80 msgid "" "Determines the separator used between regexps when\n" " shown by the list command." msgstr "" "Määrittää erottajan, jota käytetään list komennon näyttämien\n" " säännöllisten lausekkeiden välissä." #: config.py:83 msgid "" "Determines the maximum number of triggers in\n" " one message. Set this to 0 to allow an infinite number of triggers." msgstr "" "Määrittää liipaisimien enimmäismäärän yhdessä viestissä.\n" " Aseta tämä arvoon 0 salliaksesi rajattoman määrän liipaisimia." #: plugin.py:72 msgid "" "This plugin can set regexp triggers to activate the bot.\n" " Use 'add' command to add regexp trigger, 'remove' to remove." msgstr "" "Tämä lisäosa voi asettaa säännöllisiä lausekeke liipaisimia, jotka " "aktivoivat botin.\n" " Käytä 'add' komentoa lisätäksesi säännöllisen lausekkeen ja 'remove' " "poistaaksesi säännöllisen lausekkeen." #: plugin.py:80 msgid "Create the database and connect to it." msgstr "Luo tietokanta ja yhdistä siihen." #: plugin.py:103 msgid "Use this to get a database for a specific channel." msgstr "Käytä tätä saadaksesi tietokannan tietylle kanavalle." #: plugin.py:127 msgid "Run a command from message, as if command was sent over IRC." msgstr "" "Suorittaa komennon viestistä, kuin jos komento olisi lähetetty IRC:een yli." #: plugin.py:135 msgid "" "Check if the user has any of the required capabilities to manage\n" " the regexp database." msgstr "" "Tarkista onko käyttäjällä vaadittu oikeus säännöllisen lauseke tietokannan\n" " muokkaamiseen." #: plugin.py:195 #, fuzzy msgid "" "[<channel>|global] <regexp> <action>\n" "\n" " Associates <regexp> with <action>. <channel> 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 "" "[<kanava>|global] <säännöllinen lauseke> <toiminto>\n" "\n" " Liittää <säännöllisen lausekkeen> <toimintoon>. <Kanava> vaaditaan\n" " vain, ellei viestiä lähetetä kanavalla itsellään.\n" " toiminto toistetaan säännöllisen lausekkeen täsmätessä muuttujilla $1, $2,\n" " jne. tulevat interpolatoiduiksi säännöllisen lausekkeen täsmäysryhmistä." #: plugin.py:217 msgid "Invalid python regexp: %s" msgstr "Viallinen Python säännöllinen lauseke: %s" #: plugin.py:229 msgid "That trigger is locked." msgstr "Tuo liipaisin on lukittu." #: plugin.py:235 #, fuzzy msgid "" "[<channel>|global] [--id] <regexp>]\n" "\n" " Removes the trigger for <regexp> from the triggers database.\n" " <channel> 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 "" "[<kanava>] [--id] <säännöllinen lauseke>]\n" "\n" " Poistaa lipaisimen <säännölliselle lausekkeelle> liipaisin " "tietokannasta.\n" " <Kanava> on vaadittu vain jos viestiä ei lähetetä kanavalla " "itsellään\n" " jos viestiä ei lähetetä kanavalla itsellään.\n" " Jos asetus --id on annettu, hakee säännöllinen lauseke id:llä, ei " "sisällöllä.\n" " " #: plugin.py:257 plugin.py:287 plugin.py:310 plugin.py:338 plugin.py:368 msgid "There is no such regexp trigger." msgstr "Tuollaista säännöllinen lauseke liipaisinta ei ole." #: plugin.py:261 msgid "This regexp trigger is locked." msgstr "Tämä säännöllinen lauseke liipaisin on lukittu." #: plugin.py:273 #, fuzzy msgid "" "[<channel>|global] <regexp>\n" "\n" " Locks the <regexp> so that it cannot be\n" " removed or overwritten to. <channel> is only necessary if the " "message isn't\n" " sent in the channel itself.\n" " " msgstr "" "[<kanava>] <säännöllinen lauseke>\n" "\n" " Lukitsee <säännöllisen lausekkeen>, jotta sitä ei voida poistaa tai " "ylikirjoittaa.\n" " <Kanava> on vaadittu vain, jos viestiä ei lähetetä kanavalla " "itsellään.\n" " " #: plugin.py:296 #, fuzzy msgid "" "[<channel>|global] <regexp>\n" "\n" " Unlocks the entry associated with <regexp> so that it can be\n" " removed or overwritten. <channel> is only necessary if the message " "isn't\n" " sent in the channel itself.\n" " " msgstr "" "[<kanava>] <säännöllinen lauseke>\n" "\n" " Avaa merkinnän, joka on liitetty <säännölliseen lausekkeeseen> , " "jotta se voidaan\n" " poistaa tai ylikirjoittaa. <Kanava> on vaadittu vain jos viestiä " "ei\n" " lähetetä kanavalla itsellään.\n" " " #: plugin.py:319 #, fuzzy msgid "" "[<channel>|global] [--id] <regexp>\n" "\n" " Looks up the value of <regexp> in the triggers database.\n" " <channel> 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 "" "[<kanava>] [--id] <säännöllinen lauseke>\n" "\n" " Etsii <säännöllisen lausekkeen> arvoa liipaisin tietokannassa.\n" " <Kanava> on vaadittu vain, jos viestiä ei lähetetä kanavalla\n" " itsellään.\n" " Jos asetus --id on määritetty, haetaan säännöllisen lausekkeen id:n, " "ei sisällön perusteella\n" " " #: plugin.py:348 #, fuzzy msgid "" "[<channel>|global] [--id] <regexp>\n" "\n" " Display information about <regexp> in the triggers database.\n" " <channel> 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 "" "[<kanava>] [--id] <säännöllinen lauseke>\n" "\n" " Näyttää tiedot <säännöllisestä lausekkeesta> liipaisin " "tietokannassa.\n" " <kanava> on vaadittu vain jos viestiä ei lähetetä kanavalla " "itsellään.\n" " Jos asetus --id on annettu, hakee säännöllisen lausekkeen id:een, ei " "sisällön perusteella.\n" " " #: plugin.py:371 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 "" "Säännöllisen lausekkeen id on %d, säännöllinen lauseke on \"%s\", ja " "toiminto on \"%s\". Sen lisäsi %s %s:llä, on liipaistu %d kertaa, ja on %s." #: plugin.py:380 msgid "locked" msgstr "lukittu" #: plugin.py:380 msgid "not locked" msgstr "ei lukittu" #: plugin.py:387 #, fuzzy msgid "" "[<channel>|global]\n" "\n" " Lists regexps present in the triggers database.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself. Regexp ID listed in parentheses.\n" " " msgstr "" "[<kanava>]\n" "\n" " Luettelee säännölliset lausekkeet, jotka ovat liipaisin " "tietokannassa kanavalla.\n" " <Kanava> on vaadittu vain jos viestiä ei lähetetä kanavalla\n" " itsellään. Säännöllinen lauseke ID on luetteloitu suluissa.\n" " " #: plugin.py:400 plugin.py:426 msgid "There are no regexp triggers in the database." msgstr "Säännöllinen lauseke tietokannassa ei ole liipaisimia." #: plugin.py:410 #, fuzzy msgid "" "[<channel>|global]\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. <channel> is only necessary if the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>]\n" "\n" " Palauttaa top säännölliset lausekkeet, jotka on lajiteltu " "käyttömäärän\n" " (rank). Palautuneiden säännöllisten lausekkeiden lukumäärän " "määrittää\n" " rankListLength rekisteriarvo. <Kanava> on vaadittu vain jos viestiä " "ei lähetetä\n" " kanavalla itsellään.\n" " " #: plugin.py:434 #, fuzzy msgid "" "[<channel>|global]\n" "\n" " Vacuums the database for <channel>.\n" " See SQLite vacuum doc here: http://www.sqlite.org/lang_vacuum.html\n" " <channel> 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 "" "[<kanava>]\n" "\n" " Tyhjentää <kanavan> tietokannan.\n" " Katso SQLite tyhjennys documentaatio täältä: http://www.sqlite.org/" "lang_vacuum.html\n" " <Kanava> on vaadittu vain jos viestiä ei lähetetä kanavalla " "itsellään.\n" " Tarkista ensin onko käyttäjällä vaadittu valtuus, joka on " "määritetty\n" " asetuksessa requireVacuumCapability.\n" " " ���limnoria-2020.03.17/plugins/MessageParser/locales/fr.po���������������������������������������������0000644�0001750�0001750�00000023610�13634634532�022134� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-01-21 16:39+CET\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: Limnoria <progval@gmail.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" "Language: fr\n" #: config.py:57 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:61 msgid "" "Determines whether the message parser\n" " is enabled for NOTICE messages too." msgstr "Détermine si le parseur de messages est aussi activé pour les NOTICEs." #: config.py:64 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:67 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:70 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:73 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 capacités " "par canal. Notez que l'absence de toute anti-capacité explicite signifit que " "l'utilisateur peut le faire." #: config.py:80 msgid "" "Determines the separator used between regexps 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." #: config.py:83 msgid "" "Determines the maximum number of triggers in\n" " one message. Set this to 0 to allow an infinite number of triggers." msgstr "." #: plugin.py:72 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:80 msgid "Create the database and connect to it." msgstr "." #: plugin.py:103 msgid "Use this to get a database for a specific channel." msgstr "." #: plugin.py:127 msgid "Run a command from message, as if command was sent over IRC." msgstr "." #: plugin.py:135 msgid "" "Check if the user has any of the required capabilities to manage\n" " the regexp database." msgstr "." #: plugin.py:195 msgid "" "[<channel>|global] <regexp> <action>\n" "\n" " Associates <regexp> with <action>. <channel> 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 "" "[<canal|global>] <expression régulière> <action>\n" "\n" "Associe l'<expression régulière> à l'<action>. <action> est affiché après la " "correspondance avec l'<expression régulière>, avec les variables $1, $2, " "etc, récupérés à partir des groupes de correspondance de l'<expression " "régulière>.<canal> n'est nécessaire que si le message n'est pas envoyé sur " "le canal lui-même." #: plugin.py:217 msgid "Invalid python regexp: %s" msgstr "Expression régulière Python invalide : %s" #: plugin.py:229 msgid "That trigger is locked." msgstr "Ce trigger est bloqué." #: plugin.py:235 msgid "" "[<channel>|global] [--id] <regexp>]\n" "\n" " Removes the trigger for <regexp> from the triggers database.\n" " <channel> 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 "" "[<canal|global>] [--id] <expression régulière>\n" "\n" "Supprime le déclencheur pour l'<expression régulière> de la base de données " "des déclencheurs. Si l'option --id est spécifiée, l'id de l'<expression " "régulière> sera récupéré, et non le contenu." #: plugin.py:257 plugin.py:287 plugin.py:310 plugin.py:338 plugin.py:368 msgid "There is no such regexp trigger." msgstr "Cette expression régulière n'existe pas." #: plugin.py:261 msgid "This regexp trigger is locked." msgstr "Cette expression régulière est verrouillée" #: plugin.py:273 msgid "" "[<channel>|global] <regexp>\n" "\n" " Locks the <regexp> so that it cannot be\n" " removed or overwritten to. <channel> is only necessary if the " "message isn't\n" " sent in the channel itself.\n" " " msgstr "" "[<canal|global>] <expression régulière>\n" "\n" "Verrouille l'<expression régulière>, ce qui fait que l'on ne puisse plus la " "supprimer ou la modifier. <canal> n'est nécessaire que si le message n'est " "pas envoyé sur le canal lui-même." #: plugin.py:296 msgid "" "[<channel>|global] <regexp>\n" "\n" " Unlocks the entry associated with <regexp> so that it can be\n" " removed or overwritten. <channel> is only necessary if the message " "isn't\n" " sent in the channel itself.\n" " " msgstr "" "[<canal|global>] <expression régulière>\n" "\n" "Déverrouille l'<expression régulière>, ce qui fait que l'on peut à nouveau " "la supprimer ou la modifier. <canal> n'est nécessaire que si le message " "n'est pas envoyé sur le canal lui-même." #: plugin.py:319 msgid "" "[<channel>|global] [--id] <regexp>\n" "\n" " Looks up the value of <regexp> in the triggers database.\n" " <channel> 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 "" "[<canal|global>] [--id] <expression régulière>\n" "\n" "Recherche la valeur de l'<expression régulière> de la base de données des " "déclencheurs. Si l'option --id est spécifiée, l'id de l'<expression " "régulière> sera récupéré, et non le contenu." #: plugin.py:348 msgid "" "[<channel>|global] [--id] <regexp>\n" "\n" " Display information about <regexp> in the triggers database.\n" " <channel> 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 "" "[<canal|global>] [--id] <expression régulière>\n" "\n" "Affiche des informations à propos de l'<expression régulière> de la base de " "données des déclencheurs. Si l'option --id est spécifiée, l'id de " "l'<expression régulière> sera récupéré, et non le contenu." #: plugin.py:371 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:380 msgid "locked" msgstr "verouillée" #: plugin.py:380 msgid "not locked" msgstr "non verrouillée" #: plugin.py:387 msgid "" "[<channel>|global]\n" "\n" " Lists regexps present in the triggers database.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself. Regexp ID listed in parentheses.\n" " " msgstr "" "[<canal|global>]\n" "\n" "Liste les expressions régulières présentes dans la base de données. <canal> " "n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:400 plugin.py:426 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:410 msgid "" "[<channel>|global]\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. <channel> is only necessary if the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<canal|global>]\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. <canal> n'est nécessaire que si le " "message n'est pas envoyé sur le canal lui-même." #: plugin.py:434 msgid "" "[<channel>|global]\n" "\n" " Vacuums the database for <channel>.\n" " See SQLite vacuum doc here: http://www.sqlite.org/lang_vacuum.html\n" " <channel> 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 "" "[<canal|global>]\n" "\n" "Fait un vacuum de la base de données pour le <canal>.Lisez la documentation " "de SQLite sur cette fonctionnalité : http://www.sqlite.org/lang_vacuum." "htmlVérifie d'abord si l'utilisateur a bien la capacité spécifiée dans la " "variable de configuration supybot.plugins.requireVacuumCapability<canal> " "n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." ������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/MessageParser/locales/it.po���������������������������������������������0000644�0001750�0001750�00000023321�13634634532�022140� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-07-17 01:43+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:57 msgid "" "Determines whether the\n" " message parser is enabled. If enabled, will trigger on regexps\n" " added to the regexp db." msgstr "" "Determina se il parser dei messaggi è abilitato. In caso lo sia, reagirà\n" " alle regexp aggiunte al database delle espressioni regolari." #: config.py:61 msgid "" "Determines whether we keep updating the usage\n" " count for each regexp, for popularity ranking." msgstr "" "Determina se il conteggio dell'uso venga mantenuto aggiornato per ogni regexp, per una classifica di popolarità." #: config.py:64 msgid "" "Determines the number of regexps returned\n" " by the triggerrank command." msgstr "" "Determina il numero di regexp restituite dal comando \"triggerrank\"." #: config.py:67 msgid "" "Determines the capability required (if any) to\n" " vacuum the database." msgstr "" "Determina la capacità richiesta (eventuale) per svuotare il database." #: config.py:70 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 "" "Determina la capacità richiesta (eventuale) per gestire il database delle\n" " regexp che include aggiunta, rimozione, blocco e sblocco. Utilizza\n" " \"canale,capacità\" per le capacità del singolo canale. L'assenza di\n" " un'esplicita anti-capacità significa che l'utente può usare i comandi." #: config.py:77 msgid "" "Determines the separator used between regexps when\n" " shown by the list command." msgstr "" "Determina il separatore utilizzato tra le regexp quando mostrate dal comando \"list\"." #: 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 "" "Questo plugin può definire dei trigger basati su regexp per attivare il bot.\n" " Utilizza il comando \"add\" per aggiungerne e \"remove\" per rimuoverle." #: plugin.py:83 #, docstring msgid "Create the database and connect to it." msgstr "Crea il database e ci si connette." #: plugin.py:106 #, docstring msgid "Use this to get a database for a specific channel." msgstr "Utilizzalo per ottenere un database per un canale specifico." #: plugin.py:129 #, docstring msgid "Run a command from message, as if command was sent over IRC." msgstr "Esegue un comando da un messaggio, come se questo fosse stato inviato su IRC." #: plugin.py:137 #, docstring msgid "" "Check if the user has any of the required capabilities to manage\n" " the regexp database." msgstr "" "Controlla se l'utente ha una delle capacità richieste per gestire il database delle regexp." #: plugin.py:179 #, docstring msgid "" "[<channel>] <regexp> <action>\n" "\n" " Associates <regexp> with <action>. <channel> 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 "" "[<canale>] <regexp> <azione>\n" "\n" " Associa <regexp> ad <azione>. L'azione viene mostrata sulla corrispondenza\n" " della regexp, con le variabili $1, $2, ecc. inserita dai gruppi di corrispondenza\n" " dell'espressione regolare. <canale> è necessario solo se il messaggio\n" " non viene inviato nel canale stesso." #: plugin.py:201 msgid "Invalid python regexp: %s" msgstr "Espressione regolare python non valida: %s" #: plugin.py:213 msgid "That trigger is locked." msgstr "Questo trigger è bloccato." #: plugin.py:219 #, docstring msgid "" "[<channel>] [--id] <regexp>]\n" "\n" " Removes the trigger for <regexp> from the triggers database.\n" " <channel> 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 "" "[<canale>] [--id] <regexp>]\n" "\n" " Rimuove il trigger per <regexp> dal database. <canale> è necessario\n" " solo se il messaggio non viene inviato nel canale stesso. Se l'opzione\n" " --id è specificata, verrà recuperato tramite ID della regexp anziché contenuto.\n" " " #: plugin.py:241 plugin.py:271 plugin.py:294 plugin.py:322 plugin.py:352 msgid "There is no such regexp trigger." msgstr "Questa espressione regolare non esiste." #: plugin.py:245 msgid "This regexp trigger is locked." msgstr "Questa espressione regolare è bloccata." #: plugin.py:257 #, docstring msgid "" "[<channel>] <regexp>\n" "\n" " Locks the <regexp> so that it cannot be\n" " removed or overwritten to. <channel> is only necessary if the message isn't\n" " sent in the channel itself.\n" " " msgstr "" "[<canale>] <regexp>\n" "\n" " Blocca <regexp> in modo che non sia possibile rimuoverla o sovrascriverla.\n" " <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:280 #, docstring msgid "" "[<channel>] <regexp>\n" "\n" " Unlocks the entry associated with <regexp> so that it can be\n" " removed or overwritten. <channel> is only necessary if the message isn't\n" " sent in the channel itself.\n" " " msgstr "" "[<canale>] <regexp>\n" "\n" " Sblocca la voce associata a <regexp> in modo che sia possibile rimuoverla o\n" " sovrascriverla. <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:303 #, docstring msgid "" "[<channel>] [--id] <regexp>\n" "\n" " Looks up the value of <regexp> in the triggers database.\n" " <channel> 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 "" "[<canale>] [--id] <regexp>\n" "\n" " Cerca il valore di <regexp> nel database dei trigger. <canale> è necessario\n" " solo se il messaggio non viene inviato nel canale stesso. Se l'opzione --id\n" " è specificata, verrà recuperato tramite ID della regexp anziché contenuto.\n" " " #: plugin.py:332 #, docstring msgid "" "[<channel>] [--id] <regexp>\n" "\n" " Display information about <regexp> in the triggers database.\n" " <channel> 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 "" "[<canale>] [--id] <regexp>\n" "\n" " Mostra informazioni su <regexp> presente nel database dei trigger. <canale> è\n" " necessario solo se il messaggio non viene inviato nel canale stesso. Se l'opzione\n" " --id è specificata, verranno recuperate tramite ID della regexp anziché contenuto.\n" " " #: 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 della regexp è %d, la regexp è \"%s\" e l'azione associata è \"%s\". È stata aggiunta dall'utente %s il %s, è stata utilizzata %d volte ed è %s." #: plugin.py:364 msgid "locked" msgstr "bloccata" #: plugin.py:364 msgid "not locked" msgstr "non bloccata" #: plugin.py:371 #, docstring msgid "" "[<channel>]\n" "\n" " Lists regexps present in the triggers database.\n" " <channel> is only necessary if the message isn't sent in the channel\n" " itself. Regexp ID listed in parentheses.\n" " " msgstr "" "[<canale>]\n" "\n" " Elenca le regexp presenti nel database dei trigger. <canale> è necessario solo se il\n" " messaggio non viene inviato nel canale stesso. Gli ID delle regexp sono tra parentesi.\n" " " #: plugin.py:384 plugin.py:410 msgid "There are no regexp triggers in the database." msgstr "Non ci sono espressioni regolari nel database." #: plugin.py:394 #, docstring msgid "" "[<channel>]\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. <channel> is only necessary if the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>]\n" "\n" " Riporta un elenco delle regexp più utilizzate. Il numero di espressioni\n" " regolari restituito è definito dalla voce di registro rankListLength. <canale>\n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:418 #, docstring msgid "" "[<channel>]\n" "\n" " Vacuums the database for <channel>.\n" " See SQLite vacuum doc here: http://www.sqlite.org/lang_vacuum.html\n" " <channel> 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 "" "[<canale>]\n" "\n" " Svuota il database di <canale>. Consulta la documentazione di SQLite relativa\n" " all'indirizzo http://www.sqlite.org/lang_vacuum.html e controlla prima se l'utente\n" " ha la capacità richiesta specificata nella variabile requireVacuumCapability.\n" " <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/MessageParser/plugin.py�������������������������������������������������0000644�0001750�0001750�00000044775�13634634532�021432� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # 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.utils.minisix as minisix 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 sys 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: #raise callbacks.Error, 'You need to have PySQLite installed to use this ' \ #'plugin. Download it at ' \ #'<http://code.google.com/p/pysqlite/>' import sqlite3 # 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) if minisix.PY2: db.text_factory = str return db db = sqlite3.connect(filename) if minisix.PY2: 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, network, channel, regexp): subfolder = None if channel == 'global' else channel if self.registryValue('keepRankInfo', subfolder, network): 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, channel=msg.channel, network=irc.network) try: self.Proxy(irc.irc, msg, tokens) except Exception as 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 = capability[8:] if channel != 'global': capability = ircdb.makeChannelCapability(channel, capability) if capability and ircdb.checkCapability(msg.prefix, capability): #print "has capability:", capability return True return False else: return True def do_privmsg_notice(self, irc, msg): channel = msg.channel if not channel: return if self.registryValue('enable', channel, irc.network): actions = [] results = [] for channel in set(map(plugins.getChannel, (channel, 'global'))): db = self.getDb(channel) cursor = db.cursor() cursor.execute("SELECT regexp, action FROM triggers") # Fetch results and prepend channel name or 'global'. This # prevents duplicating the following lines. results.extend([(channel,)+x for x in cursor.fetchall()]) if len(results) == 0: return max_triggers = self.registryValue('maxTriggers', channel, irc.network) for (channel, regexp, action) in results: for match in re.finditer(regexp, msg.args[1]): if match is not None: thisaction = action self._updateRank(irc.network, channel, regexp) for (i, j) in enumerate(match.groups()): if match.group(i+1) is not None: thisaction = re.sub(r'\$' + str(i+1), match.group(i+1), thisaction) actions.append(thisaction) if max_triggers != 0 and max_triggers == len(actions): break if max_triggers != 0 and max_triggers == len(actions): break for action in actions: self._runCommandFunction(irc, msg, action) def doPrivmsg(self, irc, msg): if not callbacks.addressed(irc.nick, msg): #message is not direct command self.do_privmsg_notice(irc, msg) def doNotice(self, irc, msg): if self.registryValue('enableForNotices', msg.channel, irc.network): self.do_privmsg_notice(irc, msg) @internationalizeDocstring def add(self, irc, msg, args, channel, regexp, action): """[<channel>|global] <regexp> <action> Associates <regexp> with <action>. <channel> 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) = list(map(int, results[0])) else: locked = 0 usage_count = 0 if not locked: try: re.compile(regexp) except Exception as 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, ['channelOrGlobal', 'something', 'something']) @internationalizeDocstring def remove(self, irc, msg, args, channel, optlist, regexp): """[<channel>|global] [--id] <regexp>] Removes the trigger for <regexp> from the triggers database. <channel> 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) = list(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, ['channelOrGlobal', getopts({'id': '',}), 'something']) @internationalizeDocstring def lock(self, irc, msg, args, channel, regexp): """[<channel>|global] <regexp> Locks the <regexp> so that it cannot be removed or overwritten to. <channel> 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, ['channelOrGlobal', 'text']) @internationalizeDocstring def unlock(self, irc, msg, args, channel, regexp): """[<channel>|global] <regexp> Unlocks the entry associated with <regexp> so that it can be removed or overwritten. <channel> 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, ['channelOrGlobal', 'text']) @internationalizeDocstring def show(self, irc, msg, args, channel, optlist, regexp): """[<channel>|global] [--id] <regexp> Looks up the value of <regexp> in the triggers database. <channel> 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, ['channelOrGlobal', getopts({'id': '',}), 'something']) @internationalizeDocstring def info(self, irc, msg, args, channel, optlist, regexp): """[<channel>|global] [--id] <regexp> Display information about <regexp> in the triggers database. <channel> 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, ['channelOrGlobal', getopts({'id': '',}), 'something']) @internationalizeDocstring def list(self, irc, msg, args, channel): """[<channel>|global] Lists regexps present in the triggers database. <channel> is only necessary if the message isn't sent in the channel itself. Regexp ID listed in parentheses. """ db = self.getDb(channel) cursor = db.cursor() cursor.execute("SELECT regexp, id FROM triggers ORDER BY id") results = cursor.fetchall() if len(results) != 0: regexps = results else: irc.reply(_('There are no regexp triggers in the database.')) return s = [ "%s: %s" % (ircutils.bold('#'+str(regexp[1])), regexp[0]) for regexp in regexps ] separator = self.registryValue('listSeparator', channel, irc.network) irc.reply(separator.join(s)) list = wrap(list, ['channelOrGlobal']) @internationalizeDocstring def rank(self, irc, msg, args, channel): """[<channel>|global] Returns a list of top-ranked regexps, sorted by usage count (rank). The number of regexps returned is set by the rankListLength registry value. <channel> is only necessary if the message isn't sent in the channel itself. """ numregexps = self.registryValue('rankListLength', channel, irc.network) 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, ['channelOrGlobal']) @internationalizeDocstring def vacuum(self, irc, msg, args, channel): """[<channel>|global] Vacuums the database for <channel>. See SQLite vacuum doc here: http://www.sqlite.org/lang_vacuum.html <channel> 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, ['channelOrGlobal']) MessageParser = internationalizeDocstring(MessageParser) Class = MessageParser # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���limnoria-2020.03.17/plugins/MessageParser/test.py���������������������������������������������������0000644�0001750�0001750�00000024112�13634634532�021072� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # 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 * import sqlite3 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 testGroups(self): self.assertNotError('messageparser add "this (.+) a(.*)" "echo $1 $2"') self.feedMsg('this is a foo') self.assertResponse(' ', 'is foo') self.feedMsg('this is a') self.assertResponse(' ', 'is') self.assertNotError('messageparser remove "this (.+) a(.*)"') self.assertNotError('messageparser add "this (.+) a(.*)" "echo $1"') self.feedMsg('this is a foo') self.assertResponse(' ', 'is') self.feedMsg('this is a') self.assertResponse(' ', 'is') self.assertNotError('messageparser remove "this (.+) a(.*)"') self.assertNotError('messageparser add "this( .+)? a(.*)" "echo $1 $2"') self.feedMsg('this a foo') self.assertResponse(' ', '$1 foo') self.feedMsg('this a') self.assertResponse(' ', '$1') self.assertNotError('messageparser remove "this( .+)? a(.*)"') 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.assertTrue(str(m).startswith('PRIVMSG #test :i saw some stuff')) def testMaxTriggers(self): self.assertNotError('messageparser add "stuff" "echo i saw some stuff"') self.assertNotError('messageparser add "sbd" "echo i saw somebody"') self.feedMsg('this message issued by sbd has some stuff in it') m = self.getMsg(' ') self.assertTrue(str(m).startswith('PRIVMSG #test :i saw some')) m = self.getMsg(' ') self.assertTrue(str(m).startswith('PRIVMSG #test :i saw some')) with conf.supybot.plugins.messageparser.maxtriggers.context(1): self.feedMsg('this message issued by sbd has some stuff in it') m = self.getMsg(' ') self.assertTrue(str(m).startswith('PRIVMSG #test :i saw some')) m = self.getMsg(' ') self.assertFalse(m) 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', r'There are no regexp triggers in the database\.') self.assertNotError('messageparser add "stuff" "echo i saw some stuff"') self.assertRegexp('messageparser rank', r'#1 "stuff" \(0\)') self.assertNotError('messageparser add "aoeu" "echo vowels are nice!"') self.assertRegexp('messageparser rank', r'#1 "stuff" \(0\), #2 "aoeu" \(0\)') self.feedMsg('instead of asdf, dvorak has aoeu') self.getMsg(' ') self.assertRegexp('messageparser rank', r'#1 "aoeu" \(1\), #2 "stuff" \(0\)') def testList(self): self.assertRegexp('messageparser list', r'There are no regexp triggers in the database\.') self.assertNotError('messageparser add "stuff" "echo i saw some stuff"') self.assertRegexp('messageparser list', '\x02#1\x02: stuff') self.assertNotError('messageparser add "aoeu" "echo vowels are nice!"') self.assertRegexp('messageparser list', '\x02#1\x02: stuff, \x02#2\x02: aoeu') 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: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Misc/�������������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�015701� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Misc/__init__.py��������������������������������������������������������0000644�0001750�0001750�00000004317�13634634532�020011� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Miscellaneous commands. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload reload(plugin) # In case we're being reloaded. if world.testing: from . import test Class = plugin.Class configure = config.configure �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Misc/config.py����������������������������������������������������������0000644�0001750�0001750�00000007767�13634634532�017533� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Misc') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Misc', True) Misc = conf.registerPlugin('Misc') conf.registerChannelValue(Misc, 'mores', registry.PositiveInteger(1, _("""Determines how many messages the bot will issue when using the 'more' command."""))) conf.registerGlobalValue(Misc, 'listPrivatePlugins', registry.Boolean(False, _("""Determines whether the bot will list private plugins with the list command if given the --private switch. If this is disabled, non-owner users should be unable to see what private plugins are loaded."""))) conf.registerGlobalValue(Misc, 'customHelpString', registry.String('', _("""Sets a custom help string, displayed when the 'help' command is called without arguments."""))) conf.registerGlobalValue(Misc, 'listUnloadedPlugins', registry.Boolean(False, _("""Determines whether the bot will list unloaded plugins with the list command if given the --unloaded switch. If this is disabled, non-owner users should be unable to see what unloaded plugins are available."""))) conf.registerGlobalValue(Misc, 'timestampFormat', registry.String('[%H:%M:%S]', _("""Determines the format string for timestamps in the Misc.last command. Refer to the Python documentation for the time module to see what formats are accepted. If you set this variable to the empty string, the timestamp will not be shown."""))) conf.registerGroup(Misc, 'last') conf.registerGroup(Misc.last, 'nested') conf.registerChannelValue(Misc.last.nested, 'includeTimestamp', registry.Boolean(False, _("""Determines whether or not the timestamp will be included in the output of last when it is part of a nested command"""))) conf.registerChannelValue(Misc.last.nested, 'includeNick', registry.Boolean(False, _("""Determines whether or not the nick will be included in the output of last when it is part of a nested command"""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������limnoria-2020.03.17/plugins/Misc/locales/�����������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017323� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Misc/locales/de.po������������������������������������������������������0000644�0001750�0001750�00000025763�13634634532�020262� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2012-03-11 20:58+UTC\n" "PO-Revision-Date: 2012-04-27 15:40+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: German <fbesser@gmail.com>\n" "Language: de\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: de\n" #: config.py:45 msgid "" "Determines whether the bot will list private\n" " plugins with the list command if given the --private switch. If this is\n" " disabled, non-owner users should be unable to see what private plugins\n" " are loaded." msgstr "Legt fest ob der Bot private Plugins mit dem list Befehl auflisten soll, wenn man den --private Schalter angibt. Falls das deaktiviert ist, ist es nicht Inhabern unmöglich zu sehen ob private Plugins geladen sind." #: config.py:50 #, fuzzy msgid "" "Determines whether the bot will list unloaded\n" " plugins with the list command if given the --unloaded switch. If this is\n" " disabled, non-owner users should be unable to see what unloaded plugins\n" " are available." msgstr "Legt fest ob der Bot private Plugins mit dem list Befehl auflisten soll, wenn man den --private Schalter angibt. Falls das deaktiviert ist, ist es nicht Inhabern unmöglich zu sehen ob private Plugins geladen sind." #: config.py:55 msgid "" "Determines the format string for\n" " timestamps in the Misc.last command. Refer to the Python documentation\n" " for the time module to see what formats are accepted. If you set this\n" " variable to the empty string, the timestamp will not be shown." msgstr "Legt das Format der Zeichenkette für Zeitstempel im Misc.last Befehl fest. Schau in die Python Dokumentation für das time Modul um zu sehen welche Formate unterstützt sind. Falls du diese Variable auf eine leere Zeichenkette setzt wird kein Zeitstempel angezeigt." #: config.py:62 msgid "" "Determines whether or not\n" " the timestamp will be included in the output of last when it is part of a\n" " nested command" msgstr "Legt fest ob der Zeitstempel mit in der Ausgabe stehen soll, wenn der letzte Befehl teil eines verschachtelten Befehls war." #: config.py:66 msgid "" "Determines whether or not the\n" " nick will be included in the output of last when it is part of a nested\n" " command" msgstr "Legt fest ob der Nick mit in der Ausgabe stehen soll, wenn der letzte Befehl teil eines verschachtelten Befehls war." #: plugin.py:104 msgid "You've given me %s invalid commands within the last minute; I'm now ignoring you for %s." msgstr "Du hast mir %s nicht zulässige Befehle innerhalb der letzten Minute gegeben. Ich werde dich für %s ignorieren." #: plugin.py:116 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 "Das %q Plugin ist geladen, aber es besitzt keinen Befehl mit dem Namen %q. Versuche \"list %s\" um die Befehle des %q Plugins zu sehen." #: plugin.py:142 #, fuzzy msgid "" "[--private] [--unloaded] [<plugin>]\n" "\n" " Lists the commands available in the given plugin. If no plugin is\n" " given, lists the public plugins available. If --private is given,\n" " lists the private plugins. If --unloaded is given, it will list\n" " available plugins that are not loaded.\n" " " msgstr "" "[--private] [<Plugin>]\n" "\n" "Listet die Befehl auf die das gegebene Plugin anbietet. Falls kein Plugin angegeben wird, werden alle verfügbaren öffentliche Befehle aufgelistet. Falls --private angegeben wird, werden alle privaten Plugins aufgelistet." #: plugin.py:163 msgid "--private and --unloaded are incompatible options." msgstr "" #: plugin.py:194 msgid "There are no private plugins." msgstr "Es gibt keine privaten Plugins." #: plugin.py:196 msgid "There are no public plugins." msgstr "Es gibt keine öffentlichen Plugins." #: plugin.py:203 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 "Dieses Plugin existiert, hat aber keine Befehle. Das heißt womöglich das es einige Konfigurationsvariablen hat, die verändert werden können um das Verhalten zu beeinflussen. Probiere \"config list supybot.plugins.%s\" um zu sehen welche Konfigurationsvariablen es hat." #: plugin.py:215 msgid "" "<string>\n" "\n" " Searches for <string> in the commands currently offered by the bot,\n" " returning a list of the commands containing that string.\n" " " msgstr "" "<Zeichenkette>\n" "\n" "Sucht nach <Zeichenkette> in den Befehlen die der Bot momentan anbietet, gibt eine Liste der Befehle zurück in denen die Zeichenkette vorkommt." #: plugin.py:234 msgid "No appropriate commands were found." msgstr "Kein passender Befehl gefunden." #: plugin.py:239 msgid "" "[<plugin>] [<command>]\n" "\n" " This command gives a useful description of what <command> does.\n" " <plugin> is only necessary if the command is in more than one plugin.\n" " " msgstr "" "[<Plugin>] [<Befehl>]\n" "\n" "Gibt eine nützliche Beschreibung was der <Befehl> tut. <Plugin> wird nur benötigt, falls es den Befehl in mehr wie einem Plugin gibt." #: plugin.py:249 msgid "That command exists in the %L plugins. Please specify exactly which plugin command you want help with." msgstr "Den Befehl gibt es in diesen %L Plugins. Bitte gibt genau an mit welchen Plugin Befehl du Hilfe benötigst." #: plugin.py:256 msgid "There is no command %q." msgstr "Es gibt keinen Befehl %q." #: plugin.py:262 msgid "" "takes no arguments\n" "\n" " Returns the version of the current bot.\n" " " msgstr "" "hat keine Argumente\n" "\n" "Gibt die momentane Bot Version aus." #: plugin.py:276 msgid "The newest versions available online are %s." msgstr "Die neuste online Version ist %s." #: plugin.py:277 msgid "%s (in %s)" msgstr "%s (in %s)" #: plugin.py:281 msgid "I couldn't fetch the newest version from the Limnoria repository." msgstr "Ich konnte mir die neuste Version nicht aus dem Limnoria Verzeichnis holen." #: plugin.py:283 msgid "The current (running) version of this Supybot is %s. %s" msgstr "Die momentane (laufende) Version von Supybot ist %s. %s" #: plugin.py:290 msgid "" "takes no arguments\n" "\n" " Returns a URL saying where to get Limnoria.\n" " " msgstr "" "hat keine Argumente\n" "\n" "Gibt eine URL zurück die dir sagt wo man Limnoria bekommt." #: plugin.py:294 msgid "My source is at https://github.com/ProgVal/Limnoria" msgstr "Mein Quellcode ist auf https://github.com/ProgVal/Limnoria" #: plugin.py:299 msgid "" "[<nick>]\n" "\n" " If the last command was truncated due to IRC message length\n" " limitations, returns the next chunk of the result of the last command.\n" " If <nick> is given, it takes the continuation of the last command from\n" " <nick> instead of the person sending this message.\n" " " msgstr "" "[<nick>]\n" "\n" "Falls die Ausgabe des letzten Befehls abgeschnitten wurde, wegen der IRC Nachrichtenlänge beschränkung, wird der nexte Teil der Ausgabe des letzten Befehls ausgegben. Falls <nick> angegeben wird, wird die AUsgabe des letzten Befehls von <nick> weitergeführt, anstatt der Person die den Befehl sendete." #: plugin.py:313 msgid "%s has no public mores." msgstr "%s hat keine öffentlichen Nachrichten mehr." #: plugin.py:316 msgid "Sorry, I can't find any mores for %s" msgstr "Sorry, Ich kann nicht mehr für %s finden" #: plugin.py:323 #, fuzzy msgid "1 more message" msgstr "1 mehr Nachricht" #: plugin.py:325 #, fuzzy msgid "%i more messages" msgstr "%i mehr Nachrichten" #: plugin.py:329 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 "Du hast mir keinen Befehl geben; eventuell willst du mehr Nachrichten einer anderen Person sehen. Um das zu ereichen, rufe diesen Befehl auf mit dem Nicknamen der Person auf." #: plugin.py:333 msgid "That's all, there is no more." msgstr "Das ist alles, mehr ist nicht da." #: plugin.py:343 msgid "" "[--{from,in,on,with,without,regexp} <value>] [--nolimit]\n" "\n" " Returns the last message matching the given criteria. --from requires\n" " a nick from whom the message came; --in requires a channel the message\n" " was sent to; --on requires a network the message was sent on; --with\n" " requires some string that had to be in the message; --regexp requires\n" " a regular expression the message must match; --nolimit returns all\n" " the messages that can be found. By default, the channel this command is\n" " given in is searched.\n" " " msgstr "" "[--{from,in,on,with,without,regexp} <value>] [--nolimit]\n" "\n" "Gibt die letzte Nachricht aus, die auf die gegeben Kriterien passt. --from benötigt den Nick von dem die Nachricht kam; --in benötigt den Kanal in den die Nachricht gesendet wurde; --on benötigt das Netzwerk in dem die Nachricht gesendet wurde; --with benötigt eine Zeichenkette die in der Nachricht vorkommt; --regexp benötigt einen regulären Ausdruck auf der auf die Nachricht zutrifft; --nolimit zeigt alle Nachrichten die gefunden wurden. Voreinstellung: Der Kanal in dem der Befehl gegeben wird, wird durchsucht." #: plugin.py:445 msgid "The regular expression timed out." msgstr "" #: plugin.py:458 msgid "I couldn't find a message matching that criteria in my history of %s messages." msgstr "I konnte keine Nachricht in meiner Geschichte von %s Nachrichten finden die auf die Kriterien passt." #: plugin.py:473 msgid "" "<nick> <text>\n" "\n" " Tells the <nick> whatever <text> is. Use nested commands to your\n" " benefit here.\n" " " msgstr "" "<nick> <text>\n" "\n" "Sagt <nick> den Inhalt von <text>. Benutze verschachtelte Befehle um nutzen daraus zu ziehen." #: plugin.py:483 msgid "Hey, just give the command. No need for the tell." msgstr "Junge, gib mir einfach den Befehl." #: plugin.py:488 msgid "You just told me, why should I tell myself?" msgstr "Das hast du mir gerade gesagt, wieso sollte ich es mir selbst sagen?" #: plugin.py:493 msgid "I haven't seen %s, I'll let you do the telling." msgstr "Ich habe %s nicht gesehen, sag es ihm selbst." #: plugin.py:498 msgid "%s wants me to tell you: %s" msgstr "%s will dir folgendes sagen: %s" #: plugin.py:505 msgid "" "takes no arguments\n" "\n" " Checks to see if the bot is alive.\n" " " msgstr "" "hat keine Argumente\n" "\n" "Überprüft ob der Bot noch am Leben ist." #: plugin.py:509 msgid "pong" msgstr "Pong" #: plugin.py:513 msgid "" "[<channel>] <beginning> [--match-case]\n" "\n" " Returns the nick of someone on the channel whose nick begins with the\n" " given <beginning>.\n" " <channel> defaults to the current channel." msgstr "" #: plugin.py:519 msgid "I'm not even in %s." msgstr "" #: plugin.py:531 msgid "No such nick." msgstr "" �������������limnoria-2020.03.17/plugins/Misc/locales/fi.po������������������������������������������������������0000644�0001750�0001750�00000032745�13634634532�020266� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Misc plugin in Limnoria. # Copyright (C) 2011 Limnoria # Mikaela Suomalainen <mkaysi@outlook.com>, 2011, 2012. # msgid "" msgstr "" "Project-Id-Version: Misc plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 14:51+EET\n" "PO-Revision-Date: 2014-12-20 14:51+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.10\n" #: config.py:45 msgid "" "Determines how many messages the bot\n" " will issue when using the 'more' command." msgstr "" "Määrittää 'more'-komentoa käytettäessä annettavien\n" " viestien määrän." #: config.py:48 msgid "" "Determines whether the bot will list private\n" " plugins with the list command if given the --private switch. If this " "is\n" " disabled, non-owner users should be unable to see what private plugins\n" " are loaded." msgstr "" "Määrittää luetteleeko botti yksityiset lisäosat\n" " \"list\" komennolla jos --private valitsin on annettu. Jos tämä on " "poistettu\n" " käytöstä, ei-omistaja käyttäjien pitäisi olla kykenemättömiä näkemään " "mitkä\n" " yksityiset lisäosat ovat ladattuina." #: config.py:53 msgid "" "Sets a custom help string, displayed when the 'help'\n" " command is called without arguments." msgstr "" "Asettaa mukautetun ohjeen, joka näytetään pyydettäessä 'help'-komentoa ilman " "parametrejä." #: config.py:56 msgid "" "Determines whether the bot will list unloaded\n" " plugins with the list command if given the --unloaded switch. If this " "is\n" " disabled, non-owner users should be unable to see what unloaded plugins\n" " are available." msgstr "" "Määrittää luetteleeko botti lataamattomat lisäosat\n" " \"list\" komennolla jos --unloaded valitsin on annettu. Jos tämä on " "poistettu käytöstä,\n" " ei-omistaja käyttäjien pitäisi olla kykenemättömiä näkemään mitkä " "lisäosat eivät ole\n" " ladattuja." #: config.py:61 msgid "" "Determines the format string for\n" " timestamps in the Misc.last command. Refer to the Python documentation\n" " for the time module to see what formats are accepted. If you set this\n" " variable to the empty string, the timestamp will not be shown." msgstr "" "Määrittää muotoketjun\n" " aikaleimoille Misc.last komennossa. Katso Python dokumentaatioon\n" " aikamoduulin kohdalle nähdäksesi mitkä aikamuodot hyväksytään. Jos " "asetat tämän asetuksen\n" " tyhjäksi, niin aikaleimaa ei näytetä." #: config.py:68 msgid "" "Determines whether or not\n" " the timestamp will be included in the output of last when it is part of " "a\n" " nested command" msgstr "" "Määrittää sisällytetäänkö\n" " aikeleima \"last\" komennon ulostuloon, kun se on osa\n" " sisäkkäisiä komentoja." #: config.py:72 msgid "" "Determines whether or not the\n" " nick will be included in the output of last when it is part of a nested\n" " command" msgstr "" "Määrittää sisällytetäänkö\n" " sisällytetäänkö nimimerkki \"last\" komennon ulostuloon, kun se on osa " "sisäkkäisiä\n" " komentoja." #: plugin.py:80 msgid "" "Miscellaneous commands to access Supybot core. This is a core\n" " Supybot plugin that should not be removed!" msgstr "" "Sekalaisia komentoja Supybotin ytimeen pääsemiseksi. Tänä on ydin Supybot-" "plugini, jota ei pitäisi poistaa." #: plugin.py:118 msgid "" "You've given me %s invalid commands within the last %i seconds; I'm now " "ignoring you for %s." msgstr "" "Olet antanut minulle %s epäkelvollista komentoa minuutin sisällä; Minä jätän " "sinut nyt huomioitta ajaksi %s." #: plugin.py:159 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 "" "Lisäosa %q on ladattu, mutta siinä ei ole %q nimistä komentoa. Käytä " "komentoa \"list %s\" mähdäksesi kaikki komennot lisäosassa %q." #: plugin.py:165 plugin.py:168 msgid "command" msgstr "komento" #: plugin.py:175 msgid "private" msgstr "yksityinen" #: plugin.py:191 msgid "" "[--private] [--unloaded] [<plugin>]\n" "\n" " Lists the commands available in the given plugin. If no plugin is\n" " given, lists the public plugins available. If --private is given,\n" " lists the private plugins. If --unloaded is given, it will list\n" " available plugins that are not loaded.\n" " " msgstr "" "[--private] [<lisäosa>]\n" "\n" " Luettelee kaikki komennot, jotka ovat saatavilla annetussa " "lisäosassa. Jos lisäosaa ei ole annettu,\n" " luettelee kaikki julkiset lisäosat. Jos --private on annettu,\n" " luettelee kaikki yksityiset lisäosat.\n" " " #: plugin.py:212 msgid "--private and --unloaded are incompatible options." msgstr "--private ja --unloaded ovat epäyhteensopivia asetuksia." #: plugin.py:240 msgid "There are no private plugins." msgstr "Yksityisiä lisäosia ei ole." #: plugin.py:242 msgid "There are no public plugins." msgstr "Julkisia lisäosia ei ole." #: plugin.py:249 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 "" "Tuo lisäosa on olemassa, mutta siinä ei ole komentoja. Tämä tarkoittaa " "luultavasti sitä, että sillä on joitain asetusarvoja, joita voidaan muuttaa " "sen käyttäytymisen muokkaamiseksi. Käytä komentoa \"config list supybot." "plugins.%s\" nähdäksesi mitä asetusarvoja sillä on." #: plugin.py:261 msgid "" "<string>\n" "\n" " Searches for <string> in the commands currently offered by the bot,\n" " returning a list of the commands containing that string.\n" " " msgstr "" "<merkkiketju>\n" "\n" " Etsii <merkkiketjua> komennoista joita botti tarjoaa tällä " "hetkellä,\n" " palauttaen listan komennoista, jotka sisältävät sen merkkiketjun.\n" " " #: plugin.py:280 msgid "No appropriate commands were found." msgstr "Sopivia komentoja ei löytynyt." #: plugin.py:285 msgid "" "[<plugin>] [<command>]\n" "\n" " This command gives a useful description of what <command> does.\n" " <plugin> is only necessary if the command is in more than one " "plugin.\n" "\n" " You may also want to use the 'list' command to list all available\n" " plugins and commands.\n" " " msgstr "" "[<lisäosa>] [<komento>]\n" "\n" " Tämä komento antaa hyödyllisen kuvauksen mitä <komento> tekee.\n" " <Lisäosa> on vaadittu vain jos komento on useammassa kuin yhdessä " "lisäosassa.\n" " 'list'-komentoa voidaan myös käyttää kaikkien saatavilla olevien lisä-osien " "ja komentojen luettelointiin.\n" " " #: plugin.py:305 msgid "" "That command exists in the %L plugins. Please specify exactly which plugin " "command you want help with." msgstr "" "Tuo komento on %L lisäosassa. Ole hyvä ja määritä minkä komennon kanssa " "haluat apua." #: plugin.py:314 msgid "There is no command %q." msgstr "Komentoa %q ei ole." #: plugin.py:325 msgid "" "takes no arguments\n" "\n" " Returns the version of the current bot.\n" " " msgstr "" "Ei ota parametrejä\n" "\n" " Palauttaa nykyisen botin version.\n" " " #: plugin.py:342 msgid "The newest versions available online are %s." msgstr "Uusimmat verkossa olevat versiot ovat %s." #: plugin.py:343 msgid "%s (in %s)" msgstr "%s (%s:ssa)" #: plugin.py:347 msgid "I couldn't fetch the newest version from the Limnoria repository." msgstr "Minä en voinut tarkistaa uusinta versiota Limnorian pakettivarastosta." #: plugin.py:349 msgid "" "The current (running) version of this Supybot is %s, running on Python %s. " "%s" msgstr "" "Tällä hetkellä (käynnissä) oleva versio tästä Supybotista on %s, Python, " "jota suoritetaan Pythonin versiolla %s. %s" #: plugin.py:357 msgid "" "takes no arguments\n" "\n" " Returns a URL saying where to get Limnoria.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Palauttaa URL:n, joka kertoo mistä Limnorian saa.\n" " " #: plugin.py:361 msgid "My source is at https://github.com/ProgVal/Limnoria" msgstr "Minun lähdekoodini on osoitteessa: https://github.com/ProgVal/Limnoria" #: plugin.py:366 msgid "" "[<nick>]\n" "\n" " If the last command was truncated due to IRC message length\n" " limitations, returns the next chunk of the result of the last " "command.\n" " If <nick> is given, it takes the continuation of the last command " "from\n" " <nick> instead of the person sending this message.\n" " " msgstr "" "[<nimimerkki>]\n" "\n" " Jos viestiä lyhennetään IRC viestin pituusrajoitusten takia,\n" " palauttaa seuraavan pätkän edellistä komentoa.\n" " Jos <nimimerkki> on annettu, se ottaa jatkon\n" " <nimimerkiltä> komennon antaneen henkilön sijasta.\n" " " #: plugin.py:380 msgid "%s has no public mores." msgstr "%s:llä ei ole julkista jatkoa." #: plugin.py:383 msgid "Sorry, I can't find any mores for %s" msgstr "Anteeksi, en voi löytää jatkoa %s:lle." #: plugin.py:391 msgid "1 more message" msgstr "1 viesti jäljellä" #: plugin.py:393 msgid "%i more messages" msgstr "%i viestiä jäljellä" #: plugin.py:397 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 "" "Et ole pyytänyt minulta komentoa; ehkäpä tahdoit nähdä jonkun muun jatkon. " "Tehdäksesi niin, käytä tätä komentoa tuon henkilön nimimerkillä." #: plugin.py:401 msgid "That's all, there is no more." msgstr "Siinä kaikki, enempää ei ole." #: plugin.py:411 msgid "" "[--{from,in,on,with,without,regexp} <value>] [--nolimit]\n" "\n" " Returns the last message matching the given criteria. --from " "requires\n" " a nick from whom the message came; --in requires a channel the " "message\n" " was sent to; --on requires a network the message was sent on; --" "with\n" " requires some string that had to be in the message; --regexp " "requires\n" " a regular expression the message must match; --nolimit returns all\n" " the messages that can be found. By default, the channel this " "command is\n" " given in is searched.\n" " " msgstr "" "[--{from,in,on,with,without,regexp} <arvo>] [--nolimit]\n" "\n" " Palauttaa viimeisimmän vietin, joka täsmää annettuihin " "kriteereihin. --from vaatii\n" " keneltä nimimerkin, jolta viesti tuli; --in vaatii kanavan, jolta " "viesti\n" " lähetettiin; --on vaatii verkon, jossa viesti lähetettiin; --with\n" " vaatii jonkun merkkiketjun, joka täytyy olla viestissä; --regexp " "vaatii\n" " säännöllisen lausekkeen, johon viestin täytyy täsmätä; --nolimit " "palauttaa kaikki\n" " viestit, jotka löydetään. Oletuksena etsitään kanavalta, jolla\n" " komento on annettu.\n" " " #: plugin.py:506 msgid "The regular expression timed out." msgstr "Säännöllinen lauseke aiheutti aikakatkaisun." #: plugin.py:519 msgid "" "I couldn't find a message matching that criteria in my history of %s " "messages." msgstr "" "En voinut löytää viestiä, joka täsmää noihin kriteereihin %s:än viestin " "historiassa." #: plugin.py:538 msgid "Hey, just give the command. No need for the tell." msgstr "Keikari, anna vain komento. Ei tarvitse kertoa." #: plugin.py:543 msgid "You just told me, why should I tell myself?" msgstr "Sinä kerroit juuri minulle, miksi minun pitäisi kertoa itselleni?" #: plugin.py:548 msgid "I haven't seen %s, I'll let you do the telling." msgstr "En ole nähnyt %s:ää, annan sinun hoitaa kertomisen." #: plugin.py:553 msgid "%s wants me to tell you: %s" msgstr "%s haluaa minun kertovan sinulle: %s" #: plugin.py:559 msgid "" "<nick> <text>\n" "\n" " Tells the <nick> whatever <text> is. Use nested commands to your\n" " benefit here.\n" " " msgstr "" "<nimimerkki> <teksti>\n" "\n" " Kertoo <nimimerkille> ihansama mikä <teksti> on. Käytä sisäkkäisiä " "komentoja\n" " eduksesi tässä.\n" " " #: plugin.py:569 msgid "" "<nick> <text>\n" "\n" " Tells the <nick> whatever <text> is, in a notice. Use nested\n" " commands to your benefit here.\n" " " msgstr "" "<nimimerkki> <teksti>\n" "\n" " Kertoo <nimimerkille> mitä tahansa mitä <teksti> on NOTICE-viestinä. " "Käytä\n" " sisäkkäisiä komentoja hyväksesi tässä.\n" " " #: plugin.py:579 msgid "" "takes no arguments\n" "\n" " Checks to see if the bot is alive.\n" " " msgstr "" "Ei ota parametrejä\n" "\n" " Tarkistaa onko botti elossa.\n" " " #: plugin.py:583 msgid "pong" msgstr "pong" #: plugin.py:587 msgid "" "[<channel>] <beginning> [--match-case]\n" "\n" " Returns the nick of someone on the channel whose nick begins with " "the\n" " given <beginning>.\n" " <channel> defaults to the current channel." msgstr "" "[<kanava>] <alku> [--match-case]\n" "\n" " Palauttaa jonkun kanavalla olevan nimimerkin, joka alkaa\n" " annetulla <alulla>.\n" " <Kanava> on oletuksena nykyinen kanava." #: plugin.py:593 msgid "I'm not even in %s." msgstr "En edes ole kanavalla %s." #: plugin.py:605 msgid "No such nick." msgstr "Tuollaista nimimerkkiä ei ole." ���������������������������limnoria-2020.03.17/plugins/Misc/locales/fr.po������������������������������������������������������0000644�0001750�0001750�00000030556�13634634532�020275� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-01-21 16:39+CET\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: Limnoria <progval@gmail.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" #: config.py:45 msgid "" "Determines how many messages the bot\n" " will issue when using the 'more' command." msgstr "" "Détermine combien de message le bot enverra lorsque l’on utilise la commande " "« more »." #: config.py:48 msgid "" "Determines whether the bot will list private\n" " plugins with the list command if given the --private switch. If this " "is\n" " disabled, non-owner users should be unable to see what private plugins\n" " are loaded." msgstr "" "Détermine si le bot listera les plugins privés dans la commande 'list', si " "l'option --private est donné. Si cette variable est désactivée, les " "utilisateurs non-owner ne pourront pas voir quels plugins privés sont " "chargés." #: config.py:53 msgid "" "Determines whether the bot will list unloaded\n" " plugins with the list command if given the --unloaded switch. If this " "is\n" " disabled, non-owner users should be unable to see what unloaded plugins\n" " are available." msgstr "" "Détermine si le bot listera les plugins déchargés dans la commande 'list', " "si l'option --unloaded est donné. Si cette variable est désactivée, les " "utilisateurs non-owner ne pourront pas voir quels plugins déchargés sont " "présents." #: config.py:58 msgid "" "Determines the format string for\n" " timestamps in the Misc.last command. Refer to the Python documentation\n" " for the time module to see what formats are accepted. If you set this\n" " variable to the empty string, the timestamp will not be shown." msgstr "" "Détermine la chaîne de formattage pour les timestamps de la commande Misc." "last. Référez-vous à la documentation de Python sur le module 'time' pour " "voir quels formats sont acceptés. Si vous définissez cette variable pour " "être une chaîne vide, le timestamp ne sera pas affiché." #: config.py:65 msgid "" "Determines whether or not\n" " the timestamp will be included in the output of last when it is part of " "a\n" " nested command" msgstr "" "Détermine si le timestamp sera inclu dans la sortie de 'last' lorsqu'il est " "dans une commande imbriquée." #: config.py:69 msgid "" "Determines whether or not the\n" " nick will be included in the output of last when it is part of a nested\n" " command" msgstr "" "Détermine si le nick est inclu dans la sortie de 'last' lorsqu'il est dans " "une commande imbriquée." #: plugin.py:112 msgid "" "You've given me %s invalid commands within the last %i seconds; I'm now " "ignoring you for %s." msgstr "" "Vous m'avez donné %s commandes invalides durant les %i dernières secondes, " "je vous ignore maintenant pendant %s." #: plugin.py:154 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 "" "Le plugin %q est chargé, mais il n'a pas de commande appelée %q. Essayez " "\"list %s\" pour voir les commandes dans le plugin %q." #: plugin.py:160 plugin.py:163 msgid "command" msgstr "commande" #: plugin.py:180 msgid "" "[--private] [--unloaded] [<plugin>]\n" "\n" " Lists the commands available in the given plugin. If no plugin is\n" " given, lists the public plugins available. If --private is given,\n" " lists the private plugins. If --unloaded is given, it will list\n" " available plugins that are not loaded.\n" " " msgstr "" "[--private] [--unloaded] [<plugin>]\n" "\n" "Liste les commandes disponibles dans le plugin donné. Si aucun plugin n'est " "donné, liste les plugins publics. Si --private est donné, il liste les " "plugins privés. Si --unloaded est donné, il liste les plugins disponibles " "qui ne sont pas chargés." #: plugin.py:201 msgid "--private and --unloaded are incompatible options." msgstr "--private et --unloaded ne sont pas des options compatibles." #: plugin.py:232 msgid "There are no private plugins." msgstr "Il n'y a pas de plugin privé." #: plugin.py:234 msgid "There are no public plugins." msgstr "Il n'y a pas de plugin privé." #: plugin.py:241 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 "" "Ce plugin existe, mais n'a pas de commande. Cela signifie probablement qu'il " "a des variables de configuration qui peuvent être changés pour modifier son " "comportement. Essayez \"config list supybot.plugins.%s\" pour voir quelles " "variables de configuration il a." #: plugin.py:253 msgid "" "<string>\n" "\n" " Searches for <string> in the commands currently offered by the bot,\n" " returning a list of the commands containing that string.\n" " " msgstr "" "<chaîne>\n" "\n" "Recherche la <chaîne> dans les commandes actuellement fournies par le bot et " "retourne une list des commandes contenant cette chaîne." #: plugin.py:272 msgid "No appropriate commands were found." msgstr "Aucune commande appropriée n'a été trouvée." #: plugin.py:277 msgid "" "[<plugin>] [<command>]\n" "\n" " This command gives a useful description of what <command> does.\n" " <plugin> is only necessary if the command is in more than one " "plugin.\n" "\n" " You may also want to use the 'list' command to list all available\n" " plugins and commands.\n" " " msgstr "" "[<plugin>] [<commande>]\n" "\n" "Cette commande donne une description utile de ce que fait la <commande>. " "<plugin> n'est nécessaire que si la commande est présente dans plus d'un " "plugin. Il se peut que vous vouliez utiliser la commande « list » pour lister " "tous les plugins et commandes disponibles." #: plugin.py:290 msgid "" "That command exists in the %L plugins. Please specify exactly which plugin " "command you want help with." msgstr "" "Cette commande existe dans les plugins %L. Veuillez spécifier dans quel " "plugin se trouve la commande pour laquelle vous cherchez de l'aide." #: plugin.py:297 msgid "There is no command %q." msgstr "Il n'y a pas de commande %q." #: plugin.py:303 msgid "" "takes no arguments\n" "\n" " Returns the version of the current bot.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne la version actuelle du bot" #: plugin.py:320 msgid "The newest versions available online are %s." msgstr "Les dernières versions disponibles en ligne sont %s." #: plugin.py:321 msgid "%s (in %s)" msgstr "%s (dans %s)" #: plugin.py:325 msgid "I couldn't fetch the newest version from the Limnoria repository." msgstr "Je ne peux récupérer la dernière version sur le dépôt de Limnoria." #: plugin.py:327 msgid "" "The current (running) version of this Supybot is %s, running on Python %s. " "%s" msgstr "" "La version actuelle de ce Supybot est %s, fonctionnant sur Python %s. %s" #: plugin.py:335 msgid "" "takes no arguments\n" "\n" " Returns a URL saying where to get Limnoria.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne une URL disant où trouver Limnoria." #: plugin.py:339 msgid "My source is at https://github.com/ProgVal/Limnoria" msgstr "Ma source est disponible sur https://github.com/ProgVal/Limnoria" #: plugin.py:344 msgid "" "[<nick>]\n" "\n" " If the last command was truncated due to IRC message length\n" " limitations, returns the next chunk of the result of the last " "command.\n" " If <nick> is given, it takes the continuation of the last command " "from\n" " <nick> instead of the person sending this message.\n" " " msgstr "" "[<nick>]\n" "\n" "Si la dernière commande était tronquée par les limitations de taille des " "messages sur IRC, retourne le morceau suivant résultant de la dernière " "commande. Si le <nick> est donné, continue la dernière commande du <nick> " "plutôt que de la personne envoyant ce message." #: plugin.py:358 msgid "%s has no public mores." msgstr "%s n'a pas de 'more' public." #: plugin.py:361 msgid "Sorry, I can't find any mores for %s" msgstr "Désolé, je ne peux trouver de 'more' pour %s" #: plugin.py:369 msgid "1 more message" msgstr "1 autre message" #: plugin.py:371 msgid "%i more messages" msgstr "%i autres messages" #: plugin.py:375 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 "" "Vous ne m'avez donné aucune commande. Peut-être que vous voulez voir celle " "de quelqu'un d'autre. Pour cela, appelez cette commande en ajoutant le nick " "de cette personne." #: plugin.py:379 msgid "That's all, there is no more." msgstr "C'est tout, il n'y a plus de 'more'" #: plugin.py:389 msgid "" "[--{from,in,on,with,without,regexp} <value>] [--nolimit]\n" "\n" " Returns the last message matching the given criteria. --from " "requires\n" " a nick from whom the message came; --in requires a channel the " "message\n" " was sent to; --on requires a network the message was sent on; --" "with\n" " requires some string that had to be in the message; --regexp " "requires\n" " a regular expression the message must match; --nolimit returns all\n" " the messages that can be found. By default, the channel this " "command is\n" " given in is searched.\n" " " msgstr "" "[--{from,in,on,with,without,regexp} <valeur>] [--nolimit]\n" "\n" "Retourne le dernier message correspondant aux critères donnés. --from " "requiert le nick de la personne qui a envoyé le message ; --in requiert le " "canal sur lequel a été envoyé le message ; --with requiert une chaîne qui " "doit être dans le message --regexp requiert une expression régulière à " "laquelle le message doit correspondre ; --nolimit retourne tous les messages " "qui peuvent être trouvés. Par défaut, recherche dans les logs du canal sur " "lequel est envoyée cette commande." #: plugin.py:484 msgid "The regular expression timed out." msgstr "L'expression régulière a pris trop de temps à être évaluée." #: plugin.py:497 msgid "" "I couldn't find a message matching that criteria in my history of %s " "messages." msgstr "" "Je ne peux trouver de message correspondant à ce critère dans mon historique " "de %s messages." #: plugin.py:516 msgid "Hey, just give the command. No need for the tell." msgstr "" "Oh, contentes-toi de me donner la commande. Pas besoin d'utiliser 'tell'." #: plugin.py:521 msgid "You just told me, why should I tell myself?" msgstr "Vous venez de me le dire, pourquoi devrais-je me le dire moi-même ?" #: plugin.py:526 msgid "I haven't seen %s, I'll let you do the telling." msgstr "Je n'ai pas vu %s, je vous laisse lui dire." #: plugin.py:531 msgid "%s wants me to tell you: %s" msgstr "%s veut que je vous dise : %s" #: plugin.py:537 msgid "" "<nick> <text>\n" "\n" " Tells the <nick> whatever <text> is. Use nested commands to your\n" " benefit here.\n" " " msgstr "" "<nick> <texte>\n" "\n" "Dit le <texte> au <nick>. Utile si vous utilisez des commandes imbriquées." #: plugin.py:547 msgid "" "<nick> <text>\n" "\n" " Tells the <nick> whatever <text> is, in a notice. Use nested\n" " commands to your benefit here.\n" " " msgstr "" "<nick> <texte>\n" "\n" "Dit le <texte> à <nick>, dans une notice. Utile si vous utilisez des " "commandes imbriquées." #: plugin.py:557 msgid "" "takes no arguments\n" "\n" " Checks to see if the bot is alive.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Vérifie si le bot est encore en vie." #: plugin.py:561 msgid "pong" msgstr "pong" #: plugin.py:565 msgid "" "[<channel>] <beginning> [--match-case]\n" "\n" " Returns the nick of someone on the channel whose nick begins with " "the\n" " given <beginning>.\n" " <channel> defaults to the current channel." msgstr "" "[<canal>] <lettres> [--match-case]\n" "Retourne le nick de quelqu'un dans le salon dont le nick commence par les " "<lettres> indiquées. <channel> correspond par défaut au salon actuel." #: plugin.py:571 msgid "I'm not even in %s." msgstr "Je ne suis pas dans %s." #: plugin.py:583 msgid "No such nick." msgstr "Ce nick n'existe pas." ��������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Misc/locales/hu.po������������������������������������������������������0000644�0001750�0001750�00000026015�13634634532�020275� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # msgid "" msgstr "" "Project-Id-Version: Limnoria Misc\n" "POT-Creation-Date: 2012-03-11 20:58+UTC\n" "PO-Revision-Date: 2012-04-27 15:15+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: \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" #: config.py:45 msgid "" "Determines whether the bot will list private\n" " plugins with the list command if given the --private switch. If this is\n" " disabled, non-owner users should be unable to see what private plugins\n" " are loaded." msgstr "Meghatározza, hogy a bot listázza-e a privát bővítményeket a list paranccsal, ha a --private kapcsoló meg van adva. Ha ez le van tiltva, a tulajdonoson kívül más felhasználók nem láthatják, hogy milyen privát bővítmények vannak betöltve." #: config.py:50 #, fuzzy msgid "" "Determines whether the bot will list unloaded\n" " plugins with the list command if given the --unloaded switch. If this is\n" " disabled, non-owner users should be unable to see what unloaded plugins\n" " are available." msgstr "Meghatározza, hogy a bot listázza-e a privát bővítményeket a list paranccsal, ha a --private kapcsoló meg van adva. Ha ez le van tiltva, a tulajdonoson kívül más felhasználók nem láthatják, hogy milyen privát bővítmények vannak betöltve." #: config.py:55 msgid "" "Determines the format string for\n" " timestamps in the Misc.last command. Refer to the Python documentation\n" " for the time module to see what formats are accepted. If you set this\n" " variable to the empty string, the timestamp will not be shown." msgstr "Meghatározza az időbélyegek formátumát a Misc.last parancsban. Hivatkozz a Python dokumentációra a time modulhoz, hogy lásd, milyen formátumok fogadhatók el. Ha üres karakterláncra állítód ezt a változót, az időbélyegző nem lesz megjelenítve." #: config.py:62 msgid "" "Determines whether or not\n" " the timestamp will be included in the output of last when it is part of a\n" " nested command" msgstr "Meghatározza, hogy a last kimenete tartalmazza-e az időbélyegzőt, ha az egy beágyazott parancs része." #: config.py:66 msgid "" "Determines whether or not the\n" " nick will be included in the output of last when it is part of a nested\n" " command" msgstr "Meghatározza, hogy a last kimenete tartalmazza-e a nevet, ha az egy beágyazott parancs része." #: plugin.py:104 msgid "You've given me %s invalid commands within the last minute; I'm now ignoring you for %s." msgstr "%s érvénytelen parancsot adtál nekem az utolsó percben; most mellőzlek %s-ig." #: plugin.py:116 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 "A %q bővítmény be van töltve, de nincs benne parancs %q névvel. Próbáld meg a \"list %s\"-t, hogy lásd a parancsokat a %q bővítményben." #: plugin.py:142 #, fuzzy msgid "" "[--private] [--unloaded] [<plugin>]\n" "\n" " Lists the commands available in the given plugin. If no plugin is\n" " given, lists the public plugins available. If --private is given,\n" " lists the private plugins. If --unloaded is given, it will list\n" " available plugins that are not loaded.\n" " " msgstr "" "[--private] [<bővítmény>]\n" "\n" "Kiírja az elérhető parancsokat a megadott bővítményben. Ha nincs megadva bővítmény, kiírja az elérhető publikus bővítményeket. Ha --private meg van adva, kiírja a privát bővítményeket." #: plugin.py:163 msgid "--private and --unloaded are incompatible options." msgstr "" #: plugin.py:194 msgid "There are no private plugins." msgstr "Nincsenek privát bővítmények." #: plugin.py:196 msgid "There are no public plugins." msgstr "Nincsenek publikus bővítmények." #: plugin.py:203 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 "Ez a bővítmény létezik, de nincsenek parancsai. Ez valószínűleg azt jelenti, hogy van néhány konfigurációs változója, ami megváltoztatható, hogy módosítsd a viselkedését. Próbáld meg a \"config list supybot.plugins.%s\"-t, hogy lásd, milyen konfigurációs változói vannak." #: plugin.py:215 msgid "" "<string>\n" "\n" " Searches for <string> in the commands currently offered by the bot,\n" " returning a list of the commands containing that string.\n" " " msgstr "" "<karakterlánc>\n" "\n" "<karakterlánc>-ra keres a parancsokban, amelyeket a bot kínál, és kiírja a parancsokat, amelyek tartalmazzák a karakterláncot." #: plugin.py:234 msgid "No appropriate commands were found." msgstr "Nem található megfelelő parancs." #: plugin.py:239 msgid "" "[<plugin>] [<command>]\n" "\n" " This command gives a useful description of what <command> does.\n" " <plugin> is only necessary if the command is in more than one plugin.\n" " " msgstr "" "[<bővítmény>] [<parancs>]\n" "\n" "Ez a parancs egy hasznos leírást ad arról, hogy mit csinál <parancs>. <bővítmény> csak akkor szükséges, ha a parancs egynél több bővítményben van." #: plugin.py:249 msgid "That command exists in the %L plugins. Please specify exactly which plugin command you want help with." msgstr "Ez a parancs a(z) %L bővítményekben létezik. Kérlek pontosan határozd meg, hogy melyik bővítmény parancsának akarod látni a segítségét." #: plugin.py:256 msgid "There is no command %q." msgstr "Nincs %q parancs." #: plugin.py:262 msgid "" "takes no arguments\n" "\n" " Returns the version of the current bot.\n" " " msgstr "" "paraméter nélküli\n" "\n" "Kiírja a bot verzióját." #: plugin.py:276 msgid "The newest versions available online are %s." msgstr "A legújabb elérhető verziók az interneten: %s." #: plugin.py:277 msgid "%s (in %s)" msgstr "%s (%s-ban)" #: plugin.py:281 msgid "I couldn't fetch the newest version from the Limnoria repository." msgstr "Nem tudtam lekérdezni a legújabb verziót a Limnoria gyűjteményből." #: plugin.py:283 msgid "The current (running) version of this Supybot is %s. %s" msgstr "Az aktuális (futó) verziója ennek a Supybot-nak %s. %s" #: plugin.py:290 msgid "" "takes no arguments\n" "\n" " Returns a URL saying where to get Limnoria.\n" " " msgstr "" "paraméter nélküli\n" "Kiír egy linket, ami megmondja, honnan lehet a Limnoria-t megszerezni." #: plugin.py:294 msgid "My source is at https://github.com/ProgVal/Limnoria" msgstr "A forrásom https://github.com/ProgVal/Limnoria -ban van." #: plugin.py:299 msgid "" "[<nick>]\n" "\n" " If the last command was truncated due to IRC message length\n" " limitations, returns the next chunk of the result of the last command.\n" " If <nick> is given, it takes the continuation of the last command from\n" " <nick> instead of the person sending this message.\n" " " msgstr "" "[<név>]\n" "\n" "Ha a legutóbbi parancs le volt rövidítve az IRC üzenethosszúság-korlátai miatt, kiírja a következő részét az utolsó parancs eredményének. Ha <név> meg van adva, <név> utolsó parancsának a folytatását írja ki." #: plugin.py:313 msgid "%s has no public mores." msgstr "%s-nek nincsenek publikus folytatásai." #: plugin.py:316 msgid "Sorry, I can't find any mores for %s" msgstr "Sajnálom, nem találok folytatásokat %s-hoz." #: plugin.py:323 #, fuzzy msgid "1 more message" msgstr "eggyel több üzenet" #: plugin.py:325 #, fuzzy msgid "%i more messages" msgstr "%i több üzenet" #: plugin.py:329 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 "Nem adtál nekem parancsot; talán valaki más folyatását szeretnéd látni. Ha ezt szeretnéd tenni, hívd meg ezt a parancsot az adott ember nevével." #: plugin.py:333 msgid "That's all, there is no more." msgstr "Ez minden, nincs több." #: plugin.py:343 msgid "" "[--{from,in,on,with,without,regexp} <value>] [--nolimit]\n" "\n" " Returns the last message matching the given criteria. --from requires\n" " a nick from whom the message came; --in requires a channel the message\n" " was sent to; --on requires a network the message was sent on; --with\n" " requires some string that had to be in the message; --regexp requires\n" " a regular expression the message must match; --nolimit returns all\n" " the messages that can be found. By default, the channel this command is\n" " given in is searched.\n" " " msgstr "" "[--{from,in,on,with,without,regexp} <érték>] [--nolimit]\n" "Kiírja a legutolsó üzenetet, amire illeszkedik a megadott kritérium. --from kér egy nevet, akitől az üzenet jött; --in kér egy csatornát, ahová az üzenet lett küldve; --on kér egy hálózatpt, amelyen az üzenet lett küldve; --with kér egy karakterlánc aminek az üzenetben kellett lennie; --regexp kér egy szabályos kifejezést, amelyre az üzenetnek illeszkednie kell; --nolimit kiírja az összes talált üzenetet. Alapértelmezésben abban a csatornában keres, ahol ez a parancs végre lett hajtva." #: plugin.py:445 msgid "The regular expression timed out." msgstr "" #: plugin.py:458 msgid "I couldn't find a message matching that criteria in my history of %s messages." msgstr "Nem találtam a kritériumra illeszkedő üzenetet a(z) %s üzenetes előzményeimben." #: plugin.py:473 msgid "" "<nick> <text>\n" "\n" " Tells the <nick> whatever <text> is. Use nested commands to your\n" " benefit here.\n" " " msgstr "<név> <szöveg>Megmondja <név>-nek szöveget. Itt az előnyödre használhatod a beágyazott parancsokat." #: plugin.py:483 msgid "Hey, just give the command. No need for the tell." msgstr "Haver, csak add meg a parancsot. Nem kell mondani." #: plugin.py:488 msgid "You just told me, why should I tell myself?" msgstr "Most mondtad el nekem, miért mondjam el magamnak?" #: plugin.py:493 msgid "I haven't seen %s, I'll let you do the telling." msgstr "Nem láttam %s-t, rád hagyom a mondást." #: plugin.py:498 msgid "%s wants me to tell you: %s" msgstr "%s szeretné, hogy megmondjam neked: %s" #: plugin.py:505 msgid "" "takes no arguments\n" "\n" " Checks to see if the bot is alive.\n" " " msgstr "" "paraméter nélküli\n" "\n" "Ellenőrzi, hogy a bot él-e." #: plugin.py:509 msgid "pong" msgstr "pong" #: plugin.py:513 msgid "" "[<channel>] <beginning> [--match-case]\n" "\n" " Returns the nick of someone on the channel whose nick begins with the\n" " given <beginning>.\n" " <channel> defaults to the current channel." msgstr "" #: plugin.py:519 msgid "I'm not even in %s." msgstr "" #: plugin.py:531 msgid "No such nick." msgstr "" �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Misc/locales/it.po������������������������������������������������������0000644�0001750�0001750�00000026711�13634634532�020300� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2012-03-15 21:01+0100\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:45 msgid "" "Determines whether the bot will list private\n" " plugins with the list command if given the --private switch. If this is\n" " disabled, non-owner users should be unable to see what private plugins\n" " are loaded." msgstr "" "Determina se il bot elencherà i plugin privati con il comando \"list\" se usato\n" " con l'opzione --private. Se questa variabile è disattivata, gli utenti non\n" " proprietari (owner) potranno vedere quali plugin privati sono caricati." #: config.py:50 msgid "" "Determines whether the bot will list unloaded\n" " plugins with the list command if given the --unloaded switch. If this is\n" " disabled, non-owner users should be unable to see what unloaded plugins\n" " are available." msgstr "" "Determina se il bot elencherà i plugin non caricati con il comando \"list\" se usato\n" " con l'opzione --unloaded. Se questa variabile è disattivata, gli utenti non\n" " proprietari (owner) non saranno in grado di vedere quali altri plugin sono\n" " disponibili." #: config.py:55 msgid "" "Determines the format string for\n" " timestamps in the Misc.last command. Refer to the Python documentation\n" " for the time module to see what formats are accepted. If you set this\n" " variable to the empty string, the timestamp will not be shown." msgstr "" "Determina il formato per i timestamp del comando Misc.last. Per sapere quali sono\n" " i formati validi fai riferimento alla documentazione di Python per il modulo time.\n" " Se si assegna una stringa vuota a questa variabile, il timestamp non verrà mostrato." #: config.py:62 msgid "" "Determines whether or not\n" " the timestamp will be included in the output of last when it is part of a\n" " nested command" msgstr "" "Determina se il timestamp verrà incluso o meno nell'output di \"last\" quando fa parte di un comando nidificato." #: config.py:66 msgid "" "Determines whether or not the\n" " nick will be included in the output of last when it is part of a nested\n" " command" msgstr "" "Determines se il nick verrà incluso o meno nell'output di \"last\" quando fa parte di un comando nidificato." #: plugin.py:104 msgid "You've given me %s invalid commands within the last minute; I'm now ignoring you for %s." msgstr "Mi hai fornito %s comandi non validi entro l'ultimo minuto; ti ignoro per %s." #: plugin.py:116 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 "Il plugin %q è caricato ma non ha nessun comando chiamato %q. Prova \"list %s\" per vedere i comandi disponibili nel plugin %q." #: plugin.py:142 #, docstring msgid "" "[--private] [--unloaded] [<plugin>]\n" "\n" " Lists the commands available in the given plugin. If no plugin is\n" " given, lists the public plugins available. If --private is given,\n" " lists the private plugins. If --unloaded is given, it will list\n" " available plugins that are not loaded.\n" " " msgstr "" "[--private] [--unloaded] [<plugin>]\n" "\n" " Elenca i comandi disponibili nel dato plugin. Se nessun plugin è specificato,\n" " riporta l'elenco di quelli pubblici. Specificando --private vengono mostrati quelli\n" " privati, mentre con --unloaded quelli non caricati.\n" " " #: plugin.py:163 msgid "--private and --unloaded are incompatible options." msgstr "Le opzioni --private e --unloaded non possono essere usate insieme." #: plugin.py:194 msgid "There are no private plugins." msgstr "Non ci sono plugin privati." #: plugin.py:196 msgid "There are no public plugins." msgstr "Non ci sono plugin pubblici." #: plugin.py:203 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 "Il plugin esiste ma non ha comandi. Probabilmente significa che ha delle variabili di configurazione modificabili che cambiano il suo comportamento. Prova \"config list supybot.plugins.%s\" per vedere quali sono disponibili." #: plugin.py:215 #, docstring msgid "" "<string>\n" "\n" " Searches for <string> in the commands currently offered by the bot,\n" " returning a list of the commands containing that string.\n" " " msgstr "" "<stringa>\n" "\n" " Cerca <stringa> nei comandi attualmente forniti dal bot,\n" " riportando un elenco di quelli che contengono la stringa.\n" " " #: plugin.py:234 msgid "No appropriate commands were found." msgstr "Non è stato trovato alcun comando appropriato." #: plugin.py:239 #, docstring msgid "" "[<plugin>] [<command>]\n" "\n" " This command gives a useful description of what <command> does.\n" " <plugin> is only necessary if the command is in more than one plugin.\n" " " msgstr "" "[<plugin>] [<comando>]\n" "\n" " Fornisce un'utile descrizione di cosa fa <comando>. <plugin> è\n" " necessario solo se il comando è presente in più di un plugin.\n" " " #: plugin.py:249 msgid "That command exists in the %L plugins. Please specify exactly which plugin command you want help with." msgstr "Questo comando esiste nei plugin %L. Specifica per quale vuoi aiuto." #: plugin.py:256 msgid "There is no command %q." msgstr "Non c'è nessun comando %q." #: plugin.py:262 #, docstring msgid "" "takes no arguments\n" "\n" " Returns the version of the current bot.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Riporta la versione attuale del bot.\n" " " #: plugin.py:276 msgid "The newest versions available online are %s." msgstr "Le versioni online più recenti sono %s." #: plugin.py:277 msgid "%s (in %s)" msgstr "%s (in %s)" #: plugin.py:281 msgid "I couldn't fetch the newest version from the Limnoria repository." msgstr "Non riesco a recuperare la versione più recente dal repository di Limnoria." #: plugin.py:283 msgid "The current (running) version of this Supybot is %s. %s" msgstr "La versione di questo Supybot è %s. %s" #: plugin.py:290 #, docstring msgid "" "takes no arguments\n" "\n" " Returns a URL saying where to get Limnoria.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Riporta un URL che dice dove ottenere Limnoria.\n" " " #: plugin.py:294 msgid "My source is at https://github.com/ProgVal/Limnoria" msgstr "I miei sorgenti sono disponibili all'indirizzo https://github.com/ProgVal/Limnoria" #: plugin.py:299 #, docstring msgid "" "[<nick>]\n" "\n" " If the last command was truncated due to IRC message length\n" " limitations, returns the next chunk of the result of the last command.\n" " If <nick> is given, it takes the continuation of the last command from\n" " <nick> instead of the person sending this message.\n" " " msgstr "" "[<nick>]\n" "\n" " Se l'ultimo comando è stato troncato a causa di limitazioni della lunghezza\n" " dei messaggi IRC, riporta il pezzo successivo dell'output del comando.\n" " Se <nick> è specificato, continua l'ultimo messaggio di <nick> anziché\n" " dell'utente che usa questo comando.\n" " " #: plugin.py:313 msgid "%s has no public mores." msgstr "%s non ha \"more\" pubblici." #: plugin.py:316 msgid "Sorry, I can't find any mores for %s" msgstr "Spiacente, non trovo alcun \"more\" per %s" #: plugin.py:323 #, fuzzy msgid "1 more message" msgstr "altro messaggio" #: plugin.py:325 #, fuzzy msgid "%i more messages" msgstr "%i altri messaggi" #: plugin.py:329 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 "Non mi hai richiesto un comando, forse vuoi vedere quelli di qualcun altro. Per farlo usa questo comando con il nick di quell'utente." #: plugin.py:333 msgid "That's all, there is no more." msgstr "È tutto, non c'è nessun \"more\"." #: plugin.py:343 #, docstring msgid "" "[--{from,in,on,with,without,regexp} <value>] [--nolimit]\n" "\n" " Returns the last message matching the given criteria. --from requires\n" " a nick from whom the message came; --in requires a channel the message\n" " was sent to; --on requires a network the message was sent on; --with\n" " requires some string that had to be in the message; --regexp requires\n" " a regular expression the message must match; --nolimit returns all\n" " the messages that can be found. By default, the channel this command is\n" " given in is searched.\n" " " msgstr "" "[--{from,in,on,with,without,regexp} <valore>] [--nolimit]\n" "\n" " Riporta l'ultimo messaggio corrispondente al dato criterio. --from richiede un\n" " nick da cui è partito il messaggio; --in un canale dove il messaggio è stato\n" " inviato; --on una rete sulla quale il messaggio è stato inviato; --with necessita\n" " una stringa che deve essere nel messaggio; --regexp un'espressione regolare\n" " alla quale deve corrispondere il messaggio; --nolimit riporta tutti i messaggi\n" " trovati. Per impostazione predefinita ricerca nel canale in cui è stato dato il comando.\n" " " #: plugin.py:445 msgid "The regular expression timed out." msgstr "L'espressione regolare è scaduta." #: plugin.py:458 msgid "I couldn't find a message matching that criteria in my history of %s messages." msgstr "Non trovo un messaggio corrispondente a questo criterio nella cronologia di %s messaggi." #: plugin.py:473 #, docstring msgid "" "<nick> <text>\n" "\n" " Tells the <nick> whatever <text> is. Use nested commands to your\n" " benefit here.\n" " " msgstr "" "<nick> <testo>\n" "\n" " Dice <testo> a <nick>. Utilizza i comandi nidificati a tuo vantaggio.\n" " " #: plugin.py:483 msgid "Hey, just give the command. No need for the tell." msgstr "Dammi il comando, non c'è bisogno di usare \"tell\"." #: plugin.py:488 msgid "You just told me, why should I tell myself?" msgstr "Me l'hai appena detto, perché dovrei ripetermelo?" #: plugin.py:493 msgid "I haven't seen %s, I'll let you do the telling." msgstr "Non ho mai visto %s, lascio a te l'invio del messaggio." #: plugin.py:498 msgid "%s wants me to tell you: %s" msgstr "%s vuole che ti dica: %s" #: plugin.py:505 #, docstring msgid "" "takes no arguments\n" "\n" " Checks to see if the bot is alive.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Controlla che il bot sia ancora vivo.\n" " " #: plugin.py:509 msgid "pong" msgstr "pong" #: plugin.py:513 #, docstring msgid "" "[<channel>] <beginning> [--match-case]\n" "\n" " Returns the nick of someone on the channel whose nick begins with the\n" " given <beginning>.\n" " <channel> defaults to the current channel." msgstr "" "[<canale>] <beginning> [--match-case]\n" "\n" " Restituisce i nick presenti in canale che cominciano con il dato pattern\n" " specificato da <inizio>.\n" " <canale> è quello corrente." #: plugin.py:519 msgid "I'm not even in %s." msgstr "Non sono in %s." #: plugin.py:531 msgid "No such nick." msgstr "Nessun nick trovato." �������������������������������������������������������limnoria-2020.03.17/plugins/Misc/plugin.py����������������������������������������������������������0000644�0001750�0001750�00000065625�13634634532�017561� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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 re import os import sys import json import time import functools import supybot import supybot.conf as conf from supybot import commands import supybot.utils as utils from supybot.commands import * import supybot.ircdb as ircdb import supybot.irclib as irclib import supybot.utils.minisix as minisix import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks import supybot.registry as registry from supybot import commands from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Misc') if minisix.PY2: from itertools import ifilter as filter def getPluginsInDirectory(directory): # get modules in a given directory plugins = [] for filename in os.listdir(directory): pluginPath = os.path.join(directory, filename) if os.path.isdir(pluginPath): if all(os.path.isfile(os.path.join(pluginPath, x)) for x in ['__init__.py', 'config.py', 'plugin.py']): plugins.append(filename) return plugins class RegexpTimeout(Exception): pass class Misc(callbacks.Plugin): """Miscellaneous commands to access Supybot core. This is a core Supybot plugin that should not be removed!""" def __init__(self, irc): self.__parent = super(Misc, self) self.__parent.__init__(irc) self.invalidCommands = \ ircutils.FloodQueue(conf.supybot.abuse.flood.interval()) conf.supybot.abuse.flood.interval.addCallback(self.setFloodQueueTimeout) def setFloodQueueTimeout(self, *args, **kwargs): self.invalidCommands.timeout = conf.supybot.abuse.flood.interval() def callPrecedence(self, irc): return ([cb for cb in irc.callbacks if cb is not self], []) def invalidCommand(self, irc, msg, tokens): assert not msg.repliedTo, 'repliedTo msg in Misc.invalidCommand.' assert self is irc.callbacks[-1], 'Misc isn\'t last callback.' assert msg.command in ('PRIVMSG', 'NOTICE') self.log.debug('Misc.invalidCommand called (tokens %s)', tokens) # First, we check for invalidCommand floods. This is rightfully done # here since this will be the last invalidCommand called, and thus it # will only be called if this is *truly* an invalid command. maximum = conf.supybot.abuse.flood.command.invalid.maximum() self.invalidCommands.enqueue(msg) if self.invalidCommands.len(msg) > maximum and \ conf.supybot.abuse.flood.command.invalid() and \ not ircdb.checkCapability(msg.prefix, 'trusted'): punishment = conf.supybot.abuse.flood.command.invalid.punishment() banmask = '*!%s@%s' % (msg.user, msg.host) self.log.info('Ignoring %s for %s seconds due to an apparent ' 'invalid command flood.', banmask, punishment) if tokens and tokens[0] == 'Error:': self.log.warning('Apparent error loop with another Supybot ' 'observed. Consider ignoring this bot ' 'permanently.') ircdb.ignores.add(banmask, time.time() + punishment) if conf.supybot.abuse.flood.command.invalid.notify(): irc.reply(_('You\'ve given me %s invalid commands within the last ' '%i seconds; I\'m now ignoring you for %s.') % (maximum, conf.supybot.abuse.flood.interval(), utils.timeElapsed(punishment, seconds=False))) return # Now, for normal handling. channel = msg.channel # Only bother with the invaildCommand flood handling if it's actually # enabled if conf.supybot.abuse.flood.command.invalid(): # First, we check for invalidCommand floods. This is rightfully done # here since this will be the last invalidCommand called, and thus it # will only be called if this is *truly* an invalid command. maximum = conf.supybot.abuse.flood.command.invalid.maximum() banmasker = conf.supybot.protocols.irc.banmask.makeBanmask if self.invalidCommands.len(msg) > maximum and \ not ircdb.checkCapability(msg.prefix, 'trusted') and \ msg.prefix != irc.prefix and \ ircutils.isUserHostmask(msg.prefix): penalty = conf.supybot.abuse.flood.command.invalid.punishment() banmask = banmasker(msg.prefix, channel=channel, network=irc.network) self.log.info('Ignoring %s for %s seconds due to an apparent ' 'invalid command flood.', banmask, penalty) if tokens and tokens[0] == 'Error:': self.log.warning('Apparent error loop with another Supybot ' 'observed. Consider ignoring this bot ' 'permanently.') ircdb.ignores.add(banmask, time.time() + penalty) if conf.supybot.abuse.flood.command.invalid.notify(): irc.reply('You\'ve given me %s invalid commands within ' 'the last minute; I\'m now ignoring you for %s.' % (maximum, utils.timeElapsed(penalty, seconds=False))) return # Now, for normal handling. if conf.supybot.reply.whenNotCommand.getSpecific( irc.network, channel)(): if len(tokens) >= 2: cb = irc.getCallback(tokens[0]) if cb: plugin = cb.name() irc.error(format(_('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.'), plugin, tokens[1], plugin, plugin)) else: irc.errorInvalid(_('command'), tokens[0], repr=False) else: command = tokens and tokens[0] or '' irc.errorInvalid(_('command'), command, repr=False) else: if tokens: # echo [] will get us an empty token set, but there's no need # to log this in that case anyway, it being a nested command. self.log.info('Not replying to %s in %s, not a command.', tokens[0], channel if channel != irc.nick else _('private')) if irc.nested: bracketConfig = conf.supybot.commands.nested.brackets brackets = bracketConfig.getSpecific(irc.network, channel)() if brackets: (left, right) = brackets irc.reply(left + ' '.join(tokens) + right) else: pass # Let's just do nothing, I can't think of better. def isPublic(self, cb): name = cb.name() return conf.supybot.plugins.get(name).public() @internationalizeDocstring def list(self, irc, msg, args, optlist, cb): """[--private] [--unloaded] [<plugin>] Lists the commands available in the given plugin. If no plugin is given, lists the public plugins available. If --private is given, lists the private plugins. If --unloaded is given, it will list available plugins that are not loaded. """ private = False unloaded = False for (option, argument) in optlist: if option == 'private': private = True if not self.registryValue('listPrivatePlugins') and \ not ircdb.checkCapability(msg.prefix, 'owner'): irc.errorNoCapability('owner') elif option == 'unloaded': unloaded = True if not self.registryValue('listUnloadedPlugins') and \ not ircdb.checkCapability(msg.prefix, 'owner'): irc.errorNoCapability('owner') if unloaded and private: irc.error(_('--private and --unloaded are incompatible options.')) return if not cb: if unloaded: # We were using the path of Misc + .. to detect the install # directory. However, it fails if Misc is not in the # installation directory for some reason, so we use a # supybot module. installedPluginsDirectory = os.path.join( os.path.dirname(conf.__file__), 'plugins') plugins = getPluginsInDirectory(installedPluginsDirectory) for directory in conf.supybot.directories.plugins()[:]: plugins.extend(getPluginsInDirectory(directory)) # Remove loaded plugins: loadedPlugins = [x.name() for x in irc.callbacks] plugins = [x for x in plugins if x not in loadedPlugins] plugins.sort() irc.reply(format('%L', plugins)) else: names = [cb.name() for cb in irc.callbacks if (private and not self.isPublic(cb)) or (not private and self.isPublic(cb))] names.sort() if names: irc.reply(format('%L', names)) else: if private: irc.reply(_('There are no private plugins.')) else: irc.reply(_('There are no public plugins.')) else: commands = cb.listCommands() if commands: commands.sort() irc.reply(format('%L', commands)) else: irc.reply(format(_('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.'), cb.name())) list = wrap(list, [getopts({'private':'', 'unloaded':''}), additional('plugin')]) @internationalizeDocstring def apropos(self, irc, msg, args, s): """<string> Searches for <string> in the commands currently offered by the bot, returning a list of the commands containing that string. """ commands = {} L = [] for cb in irc.callbacks: if isinstance(cb, callbacks.Plugin): for command in cb.listCommands(): if s in command: commands.setdefault(command, []).append(cb.name()) for (key, names) in commands.items(): for name in names: L.append('%s %s' % (name, key)) if L: L.sort() irc.reply(format('%L', L)) else: irc.reply(_('No appropriate commands were found.')) apropos = wrap(apropos, ['lowered']) @internationalizeDocstring def help(self, irc, msg, args, command): """[<plugin>] [<command>] This command gives a useful description of what <command> does. <plugin> is only necessary if the command is in more than one plugin. You may also want to use the 'list' command to list all available plugins and commands. """ if not command: cHelp = self.registryValue("customHelpString") if cHelp: irc.reply(cHelp) else: irc.error() return command = list(map(callbacks.canonicalName, command)) (maxL, cbs) = irc.findCallbacksForArgs(command) if maxL == command: if len(cbs) > 1: names = sorted([cb.name() for cb in cbs]) irc.error(format(_('That command exists in the %L plugins. ' 'Please specify exactly which plugin command ' 'you want help with.'), names)) else: assert cbs, 'Odd, maxL == command, but no cbs.' irc.reply(_.__call__(cbs[0].getCommandHelp(command, False))) else: plugins = [cb.name() for cb in irc.callbacks if self.isPublic(cb)] s = format(_('There is no command %q.'), callbacks.formatCommand(command)) if command[0].lower() in map(str.lower, plugins): s += (' However, "{0}" is the name of a loaded plugin, and ' 'you may be able to find its provided commands ' 'using \'list {0}\'.'.format(command[0].title())) irc.error(s) help = wrap(help, [any('something')]) @internationalizeDocstring def version(self, irc, msg, args): """takes no arguments Returns the version of the current bot. """ try: newestUrl = 'https://api.github.com/repos/ProgVal/Limnoria/' + \ 'commits/%s' versions = {} for branch in ('master', 'testing'): data = json.loads(utils.web.getUrl(newestUrl % branch) .decode('utf8')) version = data['commit']['committer']['date'] # Strip the last 'Z': version = version.rsplit('T', 1)[0].replace('-', '.') if minisix.PY2 and isinstance(version, unicode): version = version.encode('utf8') versions[branch] = version newest = _('The newest versions available online are %s.') % \ ', '.join([_('%s (in %s)') % (y,x) for x,y in versions.items()]) except utils.web.Error as e: self.log.info('Couldn\'t get website version: %s', e) newest = _('I couldn\'t fetch the newest version ' 'from the Limnoria repository.') s = _('The current (running) version of this Limnoria is %s, ' 'running on Python %s. %s') % \ (conf.version, sys.version.replace('\n', ' '), newest) irc.reply(s) version = wrap(thread(version)) @internationalizeDocstring def source(self, irc, msg, args): """takes no arguments Returns a URL saying where to get Limnoria. """ irc.reply(_('My source is at https://github.com/ProgVal/Limnoria')) source = wrap(source) @internationalizeDocstring def more(self, irc, msg, args, nick): """[<nick>] If the last command was truncated due to IRC message length limitations, returns the next chunk of the result of the last command. If <nick> is given, it takes the continuation of the last command from <nick> instead of the person sending this message. """ userHostmask = msg.prefix.split('!', 1)[1] if nick: try: (private, L) = irc._mores[nick] if not private: irc._mores[userHostmask] = L[:] else: irc.error(_('%s has no public mores.') % nick) return except KeyError: irc.error(_('Sorry, I can\'t find any mores for %s') % nick) return try: L = irc._mores[userHostmask] number = self.registryValue('mores', msg.channel, irc.network) chunks = [L.pop() for x in range(0, number)] if L: if len(L) < 2: more = _('1 more message') else: more = _('%i more messages') % len(L) chunks[-1] += format(' \x02(%s)\x0F', more) irc.replies(chunks, noLengthCheck=True, oneToOne=False) except KeyError: irc.error(_('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.')) except IndexError: irc.error(_('That\'s all, there is no more.')) more = wrap(more, [additional('seenNick')]) def _validLastMsg(self, irc, msg): return msg.prefix and \ msg.command == 'PRIVMSG' and \ msg.channel @internationalizeDocstring def last(self, irc, msg, args, optlist): """[--{from,in,on,with,without,regexp} <value>] [--nolimit] Returns the last message matching the given criteria. --from requires a nick from whom the message came; --in requires a channel the message was sent to; --on requires a network the message was sent on; --with requires some string that had to be in the message; --regexp requires a regular expression the message must match; --nolimit returns all the messages that can be found. By default, the channel this command is given in is searched. """ predicates = {} nolimit = False skipfirst = True if msg.channel: predicates['in'] = lambda m: ircutils.strEqual(m.args[0], msg.channel) else: skipfirst = False for (option, arg) in optlist: if option == 'from': def f(m, arg=arg): return ircutils.hostmaskPatternEqual(arg, m.nick) predicates['from'] = f elif option == 'in': def f(m, arg=arg): return ircutils.strEqual(m.args[0], arg) predicates['in'] = f if arg != msg.channel: skipfirst = False elif option == 'on': def f(m, arg=arg): return m.receivedOn == arg predicates['on'] = f elif option == 'with': def f(m, arg=arg): return arg.lower() in m.args[1].lower() predicates.setdefault('with', []).append(f) elif option == 'without': def f(m, arg=arg): return arg.lower() not in m.args[1].lower() predicates.setdefault('without', []).append(f) elif option == 'regexp': def f(m, arg=arg): def f1(s, arg): """Since we can't enqueue match objects into the multiprocessing queue, we'll just wrap the function to return bools.""" if process(arg.search, s, timeout=0.1) is not None: return True else: return False if ircmsgs.isAction(m): m1 = ircmsgs.unAction(m) else: m1 = m.args[1] return regexp_wrapper(m1, reobj=arg, timeout=0.1, plugin_name=self.name(), fcn_name='last') predicates.setdefault('regexp', []).append(f) elif option == 'nolimit': nolimit = True iterable = filter(functools.partial(self._validLastMsg, irc), reversed(irc.state.history)) if skipfirst: # Drop the first message only if our current channel is the same as # the channel we've been instructed to look at. next(iterable) predicates = list(utils.iter.flatten(predicates.values())) # Make sure the user can't get messages from channels they aren't in def userInChannel(m): return m.args[0] in irc.state.channels \ and msg.nick in irc.state.channels[m.args[0]].users predicates.append(userInChannel) # Make sure the user can't get messages from a +s channel unless # they're calling the command from that channel or from a query # TODO: support statusmsg, but be careful about leaking scopes. def notSecretMsg(m): return not irc.isChannel(msg.args[0]) \ or msg.args[0] == m.args[0] \ or (m.args[0] in irc.state.channels \ and 's' not in irc.state.channels[m.args[0]].modes) predicates.append(notSecretMsg) resp = [] if irc.nested and not \ self.registryValue('last.nested.includeTimestamp'): tsf = None else: tsf = self.registryValue('timestampFormat') if irc.nested and not self.registryValue('last.nested.includeNick'): showNick = False else: showNick = True for m in iterable: for predicate in predicates: try: if not predicate(m): break except RegexpTimeout: irc.error(_('The regular expression timed out.')) return else: if nolimit: resp.append(ircmsgs.prettyPrint(m, timestampFormat=tsf, showNick=showNick)) else: irc.reply(ircmsgs.prettyPrint(m, timestampFormat=tsf, showNick=showNick)) return if not resp: irc.error(_('I couldn\'t find a message matching that criteria in ' 'my history of %s messages.') % len(irc.state.history)) else: irc.reply(format('%L', resp)) last = wrap(last, [getopts({'nolimit': '', 'on': 'something', 'with': 'something', 'from': 'something', 'without': 'something', 'in': 'callerInGivenChannel', 'regexp': 'regexpMatcher',})]) def _tell(self, irc, msg, args, target, text, notice): if irc.nested: irc.error('This command cannot be nested.', Raise=True) if target.lower() == 'me': target = msg.nick if irc.isChannel(target): irc.error(_('Hey, just give the command. No need for the tell.')) return if not ircutils.isNick(target): irc.errorInvalid('nick', target) if ircutils.nickEqual(target, irc.nick): irc.error(_('You just told me, why should I tell myself?'), Raise=True) if target not in irc.state.nicksToHostmasks and \ not ircdb.checkCapability(msg.prefix, 'owner'): # We'll let owners do this. s = _('I haven\'t seen %s, I\'ll let you do the telling.') % target irc.error(s, Raise=True) if irc.action: irc.action = False text = '* %s %s' % (irc.nick, text) s = _('%s wants me to tell you: %s') % (msg.nick, text) irc.replySuccess() irc.reply(s, to=target, private=True, notice=notice) @internationalizeDocstring def tell(self, *args): """<nick> <text> Tells the <nick> whatever <text> is. Use nested commands to your benefit here. """ self._tell(*args, notice=False) tell = wrap(tell, ['something', 'text']) @internationalizeDocstring def noticetell(self, *args): """<nick> <text> Tells the <nick> whatever <text> is, in a notice. Use nested commands to your benefit here. """ self._tell(*args, notice=True) noticetell = wrap(noticetell, ['something', 'text']) @internationalizeDocstring def ping(self, irc, msg, args): """takes no arguments Checks to see if the bot is alive. """ irc.reply(_('pong'), prefixNick=False) @internationalizeDocstring def completenick(self, irc, msg, args, channel, beginning, optlist): """[<channel>] <beginning> [--match-case] Returns the nick of someone on the channel whose nick begins with the given <beginning>. <channel> defaults to the current channel.""" if channel not in irc.state.channels: irc.error(_('I\'m not even in %s.') % channel, Raise=True) if ('match-case', True) in optlist: def match(nick): return nick.startswith(beginning) else: beginning = beginning.lower() def match(nick): return nick.lower().startswith(beginning) for nick in irc.state.channels[channel].users: if match(nick): irc.reply(nick) return irc.error(_('No such nick.')) completenick = wrap(completenick, ['channel', 'something', getopts({'match-case':''})]) @internationalizeDocstring def clearmores(self, irc, msg, args): """takes no arguments Clears all mores for the current network.""" irc._mores.clear() irc.replySuccess() clearmores = wrap(clearmores, ['admin']) Class = Misc # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Misc/test.py������������������������������������������������������������0000644�0001750�0001750�00000026101�13634634532�017224� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2008, James McCoy # 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 re from supybot.test import * class MiscTestCase(ChannelPluginTestCase): plugins = ('Misc', 'Utilities', 'Anonymous', 'Plugin', 'Channel', 'Dict', 'User', 'String') def testReplyWhenNotCommand(self): try: original = conf.supybot.reply.whenNotCommand() conf.supybot.reply.whenNotCommand.setValue(True) self.prefix = 'somethingElse!user@host.domain.tld' self.assertRegexp('foo', 'not.*command') self.assertRegexp('foo bar baz', 'not.*command') finally: conf.supybot.reply.whenNotCommand.setValue(original) def testReplyWhenNotCommandButFirstCommandIsPluginName(self): try: original = conf.supybot.reply.whenNotCommand() conf.supybot.reply.whenNotCommand.setValue(True) self.assertRegexp('misc foo', '"list Misc"') finally: conf.supybot.reply.whenNotCommand.setValue(original) # if network: # def testNotReplyWhenRegexpsMatch(self): # try: # orig = conf.supybot.reply.whenNotCommand() # gk = conf.supybot.plugins.Gameknot.gameSnarfer() # conf.supybot.reply.whenNotCommand.setValue(True) # conf.supybot.plugins.Gameknot.gameSnarfer.setValue(True) # self.prefix = 'somethingElse!user@host.domain.tld' # self.assertSnarfNotError( # 'http://gameknot.com/chess.pl?bd=1019508') # finally: # conf.supybot.reply.whenNotCommand.setValue(orig) # conf.supybot.plugins.Gameknot.gameSnarfer.setValue(gk) def testNotReplyWhenNotCanonicalName(self): try: original = str(conf.supybot.reply.whenNotCommand) conf.supybot.reply.whenNotCommand.set('True') self.prefix = 'somethingElse!user@host.domain.tld' self.assertNotRegexp('LeN foobar', 'command') self.assertResponse('lEn foobar', '6') finally: conf.supybot.reply.whenNotCommand.set(original) def testHelp(self): self.assertHelp('help list') self.assertRegexp('help help', r'^\(\x02help') #self.assertRegexp('help misc help', r'^\(\x02misc help') self.assertError('help nonExistentCommand') def testHelpIncludeFullCommandName(self): self.assertHelp('help channel capability add') m = self.getMsg('help channel capability add') self.assertTrue('channel capability add' in m.args[1]) def testHelpDoesAmbiguityWithDefaultPlugins(self): m = self.getMsg('help list') # Misc.list and User.list. self.assertFalse(m.args[1].startswith('Error')) def testHelpIsCaseInsensitive(self): self.assertHelp('help LIST') def testList(self): self.assertNotError('list') self.assertNotError('list Misc') self.assertRegexp('list --unloaded', 'Ctcp') def testListIsCaseInsensitive(self): self.assertNotError('list misc') def testListPrivate(self): # If Anonymous changes to public, these tests will break. So if # the next assert fails, change the plugin we test for public/private # to some other non-public plugin. name = 'Anonymous' conf.supybot.plugins.Anonymous.public.setValue(False) self.assertNotRegexp('list', name) self.assertRegexp('list --private', name) conf.supybot.plugins.Anonymous.public.setValue(True) self.assertRegexp('list', name) self.assertNotRegexp('list --private', name) def testListUnloaded(self): unloadedPlugin = 'Alias' loadedPlugin = 'Anonymous' self.assertRegexp('list --unloaded', 'Alias') self.assertNotRegexp('list --unloaded', 'Anonymous') def testListDoesNotIncludeNonCanonicalName(self): self.assertNotRegexp('list Owner', '_exec') def testListNoIncludeDispatcher(self): self.assertNotRegexp('list Misc', 'misc') def testListIncludesDispatcherIfThereIsAnOriginalCommand(self): self.assertRegexp('list Dict', r'\bdict\b') if network: def testVersion(self): print('*** This test should start passing when we have our '\ 'threaded issues resolved.') self.assertNotError('version') def testSource(self): self.assertNotError('source') def testTell(self): # This test fails because the test is seeing us as owner and Misc.tell # allows the owner to send messages to people the bot hasn't seen. oldprefix, self.prefix = self.prefix, 'tester!foo@bar__no_testcap__baz' self.nick = 'tester' m = self.getMsg('tell aljsdkfh [plugin tell]') self.assertTrue('let you do' in m.args[1]) m = self.getMsg('tell #foo [plugin tell]') self.assertTrue('No need for' in m.args[1]) m = self.getMsg('tell me you love me') m = self.irc.takeMsg() self.assertTrue(m.args[0] == self.nick) def testNoNestedTell(self): self.assertRegexp('echo [tell %s foo]' % self.nick, 'nested') def testTellDoesNotPropogateAction(self): m = self.getMsg('tell foo [action bar]') self.assertFalse(ircmsgs.isAction(m)) def testLast(self): orig = conf.supybot.plugins.Misc.timestampFormat() try: conf.supybot.plugins.Misc.timestampFormat.setValue('') self.feedMsg('foo bar baz') self.assertResponse('last', '<%s> foo bar baz' % self.nick) self.assertRegexp('last', r'<%s> @last' % self.nick) self.assertResponse('last --with foo', '<%s> foo bar baz' % \ self.nick) self.assertResponse('last --without foo', '<%s> @last' % self.nick) self.assertRegexp(r'last --regexp m/\s+/', r'last --without foo') self.assertResponse('last --regexp m/bar/', '<%s> foo bar baz' % self.nick) self.assertResponse('last --from %s' % self.nick.upper(), '<%s> @last --regexp m/bar/' % self.nick) self.assertResponse('last --from %s*' % self.nick[0], '<%s> @last --from %s' % (self.nick, self.nick.upper())) conf.supybot.plugins.Misc.timestampFormat.setValue('foo') self.assertSnarfNoResponse('foo bar baz', 1) self.assertResponse('last', 'foo <%s> foo bar baz' % self.nick) finally: conf.supybot.plugins.Misc.timestampFormat.setValue(orig) def testNestedLastTimestampConfig(self): tsConfig = conf.supybot.plugins.Misc.last.nested.includeTimestamp orig = tsConfig() try: tsConfig.setValue(True) self.getMsg('foo bar baz') chars = conf.supybot.reply.whenAddressedBy.chars() chars = re.escape(chars) self.assertRegexp('echo [last]', r'[%s]foo bar baz' % chars) finally: tsConfig.setValue(orig) def testNestedLastNickConfig(self): nickConfig = conf.supybot.plugins.Misc.last.nested.includeNick orig = nickConfig() try: nickConfig.setValue(True) self.getMsg('foo bar baz') chars = conf.supybot.reply.whenAddressedBy.chars() chars = re.escape(chars) self.assertRegexp('echo [last]', '<%s> [%s]foo bar baz' % (self.nick, chars)) finally: nickConfig.setValue(orig) def testMore(self): self.assertRegexp('echo %s' % ('abc'*400), 'more') self.assertRegexp('more', 'more') self.assertNotRegexp('more', 'more') with conf.supybot.plugins.Misc.mores.context(2): self.assertRegexp('echo %s' % ('abc'*700), 'more') self.assertNotRegexp('more', 'more') m = self.irc.takeMsg() self.assertIsNot(m, None) self.assertIn('more', m.args[1]) self.assertNotRegexp('more', 'more') m = self.irc.takeMsg() self.assertIsNot(m, None) self.assertNotIn('more', m.args[1]) def testClearMores(self): self.assertRegexp('echo %s' % ('abc'*700), 'more') self.assertRegexp('more', 'more') self.assertNotError('clearmores') self.assertError('more') def testInvalidCommand(self): self.assertError('echo []') def testInvalidCommands(self): with conf.supybot.abuse.flood.command.invalid.maximum.context(3): self.assertNotRegexp('foo', 'given me', frm='f!f@__no_testcap__') self.assertNotRegexp('bar', 'given me', frm='f!f@__no_testcap__') self.assertNotRegexp('baz', 'given me', frm='f!f@__no_testcap__') self.assertRegexp('qux', 'given me', frm='f!f@__no_testcap__') def testMoreIsCaseInsensitive(self): self.assertNotError('echo %s' % ('abc'*2000)) self.assertNotError('more') nick = ircutils.nickFromHostmask(self.prefix) self.assertNotError('more %s' % nick) self.assertNotError('more %s' % nick.upper()) self.assertNotError('more %s' % nick.lower()) def testApropos(self): self.assertNotError('apropos f') self.assertRegexp('apropos asldkfjasdlkfja', 'No appropriate commands') def testAproposIsNotCaseSensitive(self): self.assertNotRegexp('apropos LIST', 'No appropriate commands') def testAproposDoesntReturnNonCanonicalNames(self): self.assertNotRegexp('apropos exec', '_exec') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/MoobotFactoids/���������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017722� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/MoobotFactoids/__init__.py����������������������������������������������0000644�0001750�0001750�00000005054�13634634532�022031� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Daniel DiPaolo # 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. ### """ Moobot factoid compatibility module. Moobot's factoids were originally designed to emulate Blootbot's factoids, so in either case, you should find this plugin comfortable. """ 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__ = "0.1" __author__ = supybot.authors.strike __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=8 expandtab textwidth=78: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/MoobotFactoids/config.py������������������������������������������������0000644�0001750�0001750�00000005210�13634634532�021531� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Daniel DiPaolo # 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 from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('MoobotFactoids') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('MoobotFactoids', True) MoobotFactoids = conf.registerPlugin('MoobotFactoids') conf.registerChannelValue(MoobotFactoids, 'showFactoidIfOnlyOneMatch', registry.Boolean(True, _("""Determines whether or not the factoid value will be shown when a listkeys search returns only one factoid key."""))) conf.registerChannelValue(MoobotFactoids, 'mostCount', registry.Integer(10, _("""Determines how many items are shown when the 'most' command is called."""))) # vim:set shiftwidth=4 softtabstop=8 expandtab textwidth=78 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/MoobotFactoids/locales/�������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�021344� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/MoobotFactoids/locales/fi.po��������������������������������������������0000644�0001750�0001750�00000024471�13634634532�022304� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: MoobotFactoids plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 11:59+EET\n" "PO-Revision-Date: 2014-12-20 13:02+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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-Generator: Poedit 1.6.10\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: config.py:46 msgid "" "Determines whether\n" " or not the factoid value will be shown when a listkeys search returns " "only\n" " one factoid key." msgstr "" "Määrittää näytetöönkö\n" " vaiko eikö factoidin arvo kun \"listkeys\" haku\n" " palauttaa vain yhden factoidin avaimen." #: config.py:50 msgid "" "Determines how many items are shown\n" " when the 'most' command is called." msgstr "" "Määrittää montako asiaa näytetään\n" " kun 'most' komentoa kutsutaan." #: plugin.py:288 msgid "" "An alternative to the Factoids plugin, this plugin keeps factoids in\n" " your bot." msgstr "" "Vaihtoehto Factoids-pluginille, tämä plugin säilyttää factoideja botissasi." #: plugin.py:345 msgid "%s is %s" msgstr "%s on %s" #: plugin.py:364 msgid "Factoid %q is locked." msgstr "Factoidi %q on lukittu." #: plugin.py:371 msgid "Factoid %q not found." msgstr "Factoidia %q ei löytynyt." #: plugin.py:381 msgid "Missing an 'is' or '_is_'." msgstr "Puuttuva 'is' tai '_is_'." #: plugin.py:397 msgid "Factoid %q already exists." msgstr "Factoidi %q on jo olemassa." #: plugin.py:431 msgid "%s, or %s" msgstr "%s, tai %s" #: plugin.py:452 msgid "" "[<channel>] <factoid key>\n" "\n" " Returns the literal factoid for the given factoid key. No parsing " "of\n" " the factoid value is done as it is with normal retrieval. " "<channel>\n" " is only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] <factoidi avain>\n" "\n" " Palauttaa kirjaimellisen factoidin annetulle factoidi avaimelle. " "Factoidin jäsentämistä\n" " ei tehdä, koska se on normaalilla palautuksella. <Kanava> on " "vaadittu vain, jos viestiä ei lähetetä\n" " kanavalla itsellään.\n" " " #: plugin.py:465 msgid "" "[<channel>] <factoid key>\n" "\n" " Returns the various bits of info on the factoid for the given key.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<kanava>] <factoidi avain>\n" "\n" " Palauttaa muutamia tiedonmurusia factoidista annetulle avaimelle.\n" " <Kanava> on vaadittu vain jos viestiä ei lähetetä kanavalla\n" " itsellään.\n" " " #: plugin.py:476 plugin.py:516 msgid "No such factoid: %q" msgstr "Ei sellaista factoidia: %q" #: plugin.py:485 msgid "Created by %s on %s." msgstr "luonut %s kello %s." #: plugin.py:491 msgid " Last modified by %s on %s." msgstr "Viimeeksi muokattu %s kello %s." #: plugin.py:499 msgid " Last requested by %s on %s, requested %n." msgstr "Viimeeksi pyytänyt %s kello %s, pyytänyt %n." #: plugin.py:506 msgid " Locked by %s on %s." msgstr "%s:än lukitsema %s:llä." #: plugin.py:521 msgid "Factoid %q is already locked." msgstr "Factoidi %q on jo lukittu." #: plugin.py:524 msgid "Factoid %q is not locked." msgstr "Factoidi %q is not locked." #: plugin.py:534 msgid "Cannot %s someone else's factoid unless you are an admin." msgstr "Et voi %s jonkun muun factoidia paitsi, jos olet admin." #: plugin.py:546 msgid "" "[<channel>] <factoid key>\n" "\n" " Locks the factoid with the given factoid key. Requires that the " "user\n" " be registered and have created the factoid originally. <channel> " "is\n" " only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] <factoidi avain>\n" "\n" " Lukitsee factoidin annetulla factoidi avaimella. Vaatii käyttäjän " "olevan rekisteröitynyt\n" " ja että hän on alunperin luonut sen. <Kanava> on\n" " vaadittu vain jos viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:557 msgid "" "[<channel>] <factoid key>\n" "\n" " Unlocks the factoid with the given factoid key. Requires that the\n" " user be registered and have locked the factoid. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] <factoidi avain>\n" "\n" " Avaa factoidin annetulla factoidi avaimella. Vaatii, että käyttäjä\n" " on rekisteröitynyt ja lukinnut factoidin. <Kanava> on vaadittu\n" " vain jos viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:568 msgid "" "[<channel>] {popular|authored|recent}\n" "\n" " Lists the most {popular|authored|recent} factoids. \"popular\" " "lists the\n" " most frequently requested factoids. \"authored\" lists the author " "with\n" " the most factoids. \"recent\" lists the most recently created " "factoids.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<kanava>] {popular|authored|recent}\n" "\n" " Luettelee {suosituimmat (\"popular\")|kirjoitetuimmat (\"authored\")|" "viimeisimmät (\"recent\")} factoidit. \"popular\" luettelee\n" " useimmiten pyydetyt factoidit. \"authored\" luettelee kirjoittajat, " "joilla on eniten\n" " factoideja. \"recent\" luettelee uusimmat kirjoitetut factoidit.\n" " <Kanava> on vaadittu vain, jos viestiä ei lähetetä kanavalla\n" " itsellään.\n" " " #: plugin.py:590 msgid "author" msgstr "kirjoittaja" #: plugin.py:592 msgid "authors" msgstr "kirjoittajat" #: plugin.py:593 msgid "Most prolific %s: %L" msgstr "Tuottoisimmat %s: %L" #: plugin.py:595 plugin.py:607 msgid "There are no factoids in my database." msgstr "Tietokannassani ei ole factoideja." #: plugin.py:602 msgid "latest factoid" msgstr "viimeisin factoidi" #: plugin.py:604 msgid "latest factoids" msgstr "viimeisimmät factoidit" #: plugin.py:605 msgid "%i %s: %L" msgstr "%i %s: %L" #: plugin.py:614 msgid "requested factoid" msgstr "pyydetty factoidi" #: plugin.py:616 msgid "requested factoids" msgstr "pyydetyt factoidit" #: plugin.py:617 msgid "Top %i %s: %L" msgstr "Huippu %i %s: %L" #: plugin.py:619 msgid "No factoids have been requested from my database." msgstr "Factoideja ei ole pyydetty tietokannastani." #: plugin.py:623 msgid "" "[<channel>] <author name>\n" "\n" " Lists the keys of the factoids with the given author. Note that if " "an\n" " author has an integer name, you'll have to use that author's id to " "use\n" " this function (so don't use integer usernames!). <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] <kirjoittajan nimi>\n" "\n" " Luettelee kaikki annetun kirjailijan kirjoittamat factoidit. " "Huomaa, että\n" " jos kirjoittajalla on kokonaisluku nimi, sinun täytyy käyttää " "kirjoittajan id:tä käyttääksesi\n" " tätä toimintoa (joten älä käytä kokonaisluku käyttäjätunnuksia!). " "<Kanava> on vaadittu vain jos\n" " viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:636 msgid "No factoids by %q found." msgstr "%q:n kirjoittamia factoideja ei löytynyt." #: plugin.py:639 msgid "Author search for %q (%i found): %L" msgstr "Kirjoittaja haku %q:lle (%i löytynyt): %L" #: plugin.py:646 msgid "" "[<channel>] <text>\n" "\n" " Lists the keys of the factoids whose key contains the provided " "text.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<kanava>] <teksti>\n" "\n" " Luettelee kaikki factoidit, joiden avain sisältää annetun tekstin.\n" " <Kanava> on vaadittu vain, jos viestiä ei lähetetä kanavalla\n" " itsellään.\n" " " #: plugin.py:654 msgid "No keys matching %q found." msgstr "Avaimia, jotka täsmäävät %q:un ei löytynyt." #: plugin.py:661 msgid "Key search for %q (%i found): %L" msgstr "Avain haku %q:lle (%i löytynyt): %L" #: plugin.py:668 msgid "" "[<channel>] <text>\n" "\n" " Lists the keys of the factoids whose value contains the provided " "text.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<kanava>] <teksti>\n" "\n" " Luettelee factoidien avaimet, joiden arvo sisältää annetun tekstin.\n" " <Kanava> on vaadittu vain, jos viestiä ei lähetetä\n" " kanavalla itsellään.\n" " " #: plugin.py:676 msgid "No values matching %q found." msgstr "%q:un täsmääviä avaimia ei löytynyt." #: plugin.py:679 msgid "Value search for %q (%i found): %L" msgstr "Arvo haku %q:lle (%i löytynyt): %L" #: plugin.py:686 msgid "" "[<channel>] <factoid key>\n" "\n" " Deletes the factoid with the given key. <channel> is only " "necessary\n" " if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] <factoidi avain>\n" "\n" " Poistaa factoidin annetulla avaimella. <Kanava> on vaadittu vain, " "jos\n" " viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:699 msgid "" "[<channel>]\n" "\n" " Displays a random factoid (along with its key) from the database.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<kanava>]\n" "\n" " Näyttää satunnaisen factoidin (avaimensa kanssa) tietokannasta.\n" " <Kanava> on vaadittu vain jos viestiä ei lähetetä kanavalla\n" " itsellään.\n" " " #: plugin.py:707 msgid "No factoids in the database." msgstr "Tietokannassa ei ole factoideja." #~ 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 "" #~ "Lisää ohje \"help MoobotFactoids\":lle tähän (olettaen ettet luo " #~ "MoobotFactoids\n" #~ " komentoa). Tämän pitäisi kertoa *kuinka* tätä laajennusta käytetään." �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/MoobotFactoids/locales/fr.po��������������������������������������������0000644�0001750�0001750�00000022467�13634634532�022320� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2013-03-02 19:04+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" #: config.py:46 msgid "" "Determines whether\n" " or not the factoid value will be shown when a listkeys search returns " "only\n" " one factoid key." msgstr "" "Détermine si la valeur de la factoid sera affichée lorsqu'une recherche de " "clef ne retournera qu'une seule clef." #: config.py:50 msgid "" "Determines how many items are shown\n" " when the 'most' command is called." msgstr "" "Détermine combien d'éléments sont affichés lorsque la commande 'most' est " "appelée." #: plugin.py:288 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:345 msgid "%s is %s" msgstr "%s est %s" #: plugin.py:364 msgid "Factoid %q is locked." msgstr "La factoid %q est verrouillée" #: plugin.py:371 msgid "Factoid %q not found." msgstr "Factoid %q non trouvée." #: plugin.py:381 msgid "Missing an 'is' or '_is_'." msgstr "Il manque un 'is' ou un '_is_'" #: plugin.py:397 msgid "Factoid %q already exists." msgstr "La factoid %q existe déjà." #: plugin.py:431 msgid "%s, or %s" msgstr "%s, ou %s" #: plugin.py:452 msgid "" "[<channel>] <factoid key>\n" "\n" " Returns the literal factoid for the given factoid key. No parsing " "of\n" " the factoid value is done as it is with normal retrieval. " "<channel>\n" " is only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] <clef>\n" "\n" "Retourne la factoid littérale pour la clef donnée. Aucun parsage n'est " "effecté sur la valeur de la factoid. <canal> n'est nécesaire que si le " "message n'est pas envoyé sur le canal lui-même." #: plugin.py:465 msgid "" "[<channel>] <factoid key>\n" "\n" " Returns the various bits of info on the factoid for the given key.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<channel>] <clef>\n" "\n" "Retourne différentes informations sur la factoid ayant la clef donnée. " "<canal> n'est nécesaire que si le message n'est pas envoyé sur le canal lui-" "même." #: plugin.py:476 plugin.py:516 msgid "No such factoid: %q" msgstr "Cette factoid n'existe pas : %q" #: plugin.py:485 msgid "Created by %s on %s." msgstr "Créé par %s le %s" #: plugin.py:491 msgid " Last modified by %s on %s." msgstr "Dernière modification par %s le %s" #: plugin.py:499 msgid " Last requested by %s on %s, requested %n." msgstr "Dernière requete par %s le %s ; a demandé %n." #: plugin.py:506 msgid " Locked by %s on %s." msgstr "Verrouillé par %s le %s" #: plugin.py:521 msgid "Factoid %q is already locked." msgstr "La factoid %q est déjà bloquée." #: plugin.py:524 msgid "Factoid %q is not locked." msgstr "La factoid %q n'est pas bloquée." #: plugin.py:534 msgid "Cannot %s someone else's factoid unless you are an admin." msgstr "" "Impossible de %s la factoid de quelqu'un d'autre à moins d'être un admin." #: plugin.py:546 msgid "" "[<channel>] <factoid key>\n" "\n" " Locks the factoid with the given factoid key. Requires that the " "user\n" " be registered and have created the factoid originally. <channel> " "is\n" " only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<channel>] <clef>\n" "\n" "Verrouille la factoid ayant la clef donnée. Requiert que l'utilisateur soit " "enregistré et ait créé la factoid. <canal> n'est nécesaire que si le message " "n'est pas envoyé sur le canal lui-même." #: plugin.py:557 msgid "" "[<channel>] <factoid key>\n" "\n" " Unlocks the factoid with the given factoid key. Requires that the\n" " user be registered and have locked the factoid. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<channel>] <factoid key>\n" "\n" "Verrouille la factoid ayant la clef donnée. Requiert que l'utilisateur soit " "enregistré et ait verrouillé la factoid. <canal> n'est nécesaire que si le " "message n'est pas envoyé sur le canal lui-même." #: plugin.py:568 msgid "" "[<channel>] {popular|authored|recent}\n" "\n" " Lists the most {popular|authored|recent} factoids. \"popular\" " "lists the\n" " most frequently requested factoids. \"authored\" lists the author " "with\n" " the most factoids. \"recent\" lists the most recently created " "factoids.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<canal>] {popular|authored|recent}\n" "\n" "Liste les factoids selon un classement. \"popular\" correspond aux plus " "affichées ; \"authored\" liste les auteurs qui envoient le plus de factoids " "\"recent\" liste les factoids les plus récentes. <canal> n'est nécesaire que " "si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:590 msgid "author" msgstr "auteur" #: plugin.py:592 msgid "authors" msgstr "auteurs" #: plugin.py:593 msgid "Most prolific %s: %L" msgstr "%s ayant posté le plus de factoids : %L" #: plugin.py:595 plugin.py:607 msgid "There are no factoids in my database." msgstr "Il n'y a pas de factoid dans ma base de données." #: plugin.py:602 msgid "latest factoid" msgstr "dernière factoid" #: plugin.py:604 msgid "latest factoids" msgstr "dernières factoids" #: plugin.py:605 msgid "%i %s: %L" msgstr "%i %s : %L" #: plugin.py:614 msgid "requested factoid" msgstr "factoid la plus demandée" #: plugin.py:616 msgid "requested factoids" msgstr "factoids les plus demandées" #: plugin.py:617 msgid "Top %i %s: %L" msgstr "Top des %i %s : %L" #: plugin.py:619 msgid "No factoids have been requested from my database." msgstr "Aucune factoid n'a été demandée dans ma base de données." #: plugin.py:623 msgid "" "[<channel>] <author name>\n" "\n" " Lists the keys of the factoids with the given author. Note that if " "an\n" " author has an integer name, you'll have to use that author's id to " "use\n" " this function (so don't use integer usernames!). <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] <auteur>\n" "\n" "Liste les clefs de l'auteur donné. Notez que si l'auteur a un nom qui est un " "nombre entier, vous devrez donner l'ID de l'auteur pour utiliser cette " "fonction (donc, n'utilisez pas de nombres entiers comme noms " "d'utilisateur !) <canal> n'est nécesaire que si le message n'est pas " "envoyé sur le canal lui-même." #: plugin.py:636 msgid "No factoids by %q found." msgstr "Aucune factoid par %q ne peut être trouvée." #: plugin.py:639 msgid "Author search for %q (%i found): %L" msgstr "Recherche d'auteur pour %q (%i trouvé(s)) : %L" #: plugin.py:646 msgid "" "[<channel>] <text>\n" "\n" " Lists the keys of the factoids whose key contains the provided " "text.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<canal>] <texte>\n" "\n" "Liste les clefs des factoids dont la clef contient le texte fourni. <canal> " "n'est nécessaire que si la commande n'est pas envoyée sur le canal lui-même." #: plugin.py:654 msgid "No keys matching %q found." msgstr "Aucune factoid correspondant à %q trouvée." #: plugin.py:661 msgid "Key search for %q (%i found): %L" msgstr "Recherche de clef pour %q (%i trouvée(s)) : %L" #: plugin.py:668 msgid "" "[<channel>] <text>\n" "\n" " Lists the keys of the factoids whose value contains the provided " "text.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<canal>] <texte>\n" "\n" "Liste les clefs dont la valeur contient le texte recherché. <canal> n'est " "nécesaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:676 msgid "No values matching %q found." msgstr "Aucune valeur correspondant à %q trouvée." #: plugin.py:679 msgid "Value search for %q (%i found): %L" msgstr "Recherche de valeurs pour %q (%i trouvée(s)) : %L" #: plugin.py:686 msgid "" "[<channel>] <factoid key>\n" "\n" " Deletes the factoid with the given key. <channel> is only " "necessary\n" " if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] <clef>\n" "\n" "Supprime la factoid avec la clef donnée. <canal> n'est nécesaire que si le " "message n'est pas envoyé sur le canal lui-même." #: plugin.py:699 msgid "" "[<channel>]\n" "\n" " Displays a random factoid (along with its key) from the database.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<channel>]\n" "\n" "Affiche une factoid aléatoire (avec sa clef) de la base de données. <canal> " "n'est nécesaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:707 msgid "No factoids in the database." msgstr "Aucune factoid dans la base de données." ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/MoobotFactoids/locales/it.po��������������������������������������������0000644�0001750�0001750�00000022560�13634634532�022317� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-07-17 16:39+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:46 msgid "" "Determines whether\n" " or not the factoid value will be shown when a listkeys search returns only\n" " one factoid key." msgstr "" "Determina se il valore del factoid verrà mostrato o meno quando una ricerca restituisce una sola chiave." #: config.py:50 msgid "" "Determines how many items are shown\n" " when the 'most' command is called." msgstr "Determina quanti elementi mostrare con il comando \"most\"." #: 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:346 msgid "%s is %s" msgstr "%s è %s" #: plugin.py:365 msgid "Factoid %q is locked." msgstr "Il factoid %q è bloccato." #: plugin.py:372 msgid "Factoid %q not found." msgstr "Factoid %q non trovato." #: plugin.py:382 msgid "Missing an 'is' or '_is_'." msgstr "Manca un 'is' o un '_is_'." #: plugin.py:398 msgid "Factoid %q already exists." msgstr "Il factoid %q esiste già." #: plugin.py:432 msgid "%s, or %s" msgstr "%s, o %s" #: plugin.py:453 #, docstring msgid "" "[<channel>] <factoid key>\n" "\n" " Returns the literal factoid for the given factoid key. No parsing of\n" " the factoid value is done as it is with normal retrieval. <channel>\n" " is only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] <chiave>\n" "\n" " Restituisce l'esatto factoid per la chiave specificata; non viene effettuata nessuna analisi del\n" " suo valore. <canale> è necessario solo se se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:466 #, docstring msgid "" "[<channel>] <factoid key>\n" "\n" " Returns the various bits of info on the factoid for the given key.\n" " <channel> is only necessary if the message isn't sent in the channel\n" " itself.\n" " " msgstr "" "[<canale>] <chiave>\n" "\n" " Riporta varie informazioni sul factoid per la chiave specificata. <canale>\n" " è necessario solo se se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:477 plugin.py:517 msgid "No such factoid: %q" msgstr "Nessun factoid: %q" #: plugin.py:486 msgid "Created by %s on %s." msgstr "Creato da %s il %s." #: plugin.py:492 msgid " Last modified by %s on %s." msgstr " Ultima modifica da %s il %s." #: plugin.py:500 msgid " Last requested by %s on %s, requested %n." msgstr " Ultima richiesta da %s il %s, richiesto %n." #: plugin.py:507 msgid " Locked by %s on %s." msgstr " Bloccato da %s il %s." #: plugin.py:522 msgid "Factoid %q is already locked." msgstr "Il factoid %q è già bloccato." #: plugin.py:525 msgid "Factoid %q is not locked." msgstr "Il factoid %q non è bloccato." #: plugin.py:535 msgid "Cannot %s someone else's factoid unless you are an admin." msgstr "Impossibile %s il factoid di qualcun altro a meno che non sei un amministratore." #: plugin.py:547 #, docstring msgid "" "[<channel>] <factoid key>\n" "\n" " Locks the factoid with the given factoid key. Requires that the user\n" " be registered and have created the factoid originally. <channel> is\n" " only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] <chiave>\n" "\n" " Blocca il factoid con la chiave specificata. Necessita che l'utente sia\n" " registrato e abbia creato il factoid. <canale> è necessario solo se se il\n" " messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:558 #, docstring msgid "" "[<channel>] <factoid key>\n" "\n" " Unlocks the factoid with the given factoid key. Requires that the\n" " user be registered and have locked the factoid. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] <chiave>\n" "\n" " Sblocca il factoid con la chiave specificata. Necessita che l'utente sia\n" " registrato e abbia bloccato il factoid. <canale> è necessario solo se se\n" " il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:569 #, docstring msgid "" "[<channel>] {popular|authored|recent}\n" "\n" " Lists the most {popular|authored|recent} factoids. \"popular\" lists the\n" " most frequently requested factoids. \"authored\" lists the author with\n" " the most factoids. \"recent\" lists the most recently created factoids.\n" " <channel> is only necessary if the message isn't sent in the channel\n" " itself.\n" " " msgstr "" "[<canale>] {popular|authored|recent}\n" "\n" " Elenca i factoid secondo un certo criterio. \"popular\" corrisponde ai più richiesti;\n" " \"authored\" mostra l'autore che ha creato più factoid; \"recent\" elenca quelli creati\n" " più di recente. <canale> è necessario solo se se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:591 msgid "author" msgstr "autore" #: plugin.py:593 msgid "authors" msgstr "autori" #: plugin.py:594 msgid "Most prolific %s: %L" msgstr "%s più prolifico: %L" #: plugin.py:596 plugin.py:608 msgid "There are no factoids in my database." msgstr "Non ci sono factoid nel mio database." #: plugin.py:603 msgid "latest factoid" msgstr "ultimo factoid" #: plugin.py:605 msgid "latest factoids" msgstr "ultimi factoid" #: plugin.py:606 msgid "%s: %L" msgstr "%s: %L" #: plugin.py:615 msgid "requested factoid" msgstr "factoid più richiesto" #: plugin.py:617 msgid "requested factoids" msgstr "factoid più richiesti" #: plugin.py:618 msgid "Top %s: %L" msgstr "%s: %L" #: plugin.py:620 msgid "No factoids have been requested from my database." msgstr "Non è stato richiesto alcun factoid dal mio database." #: plugin.py:624 #, docstring msgid "" "[<channel>] <author name>\n" "\n" " Lists the keys of the factoids with the given author. Note that if an\n" " author has an integer name, you'll have to use that author's id to use\n" " this function (so don't use integer usernames!). <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] <autore>\n" "\n" " Elenca le chiavi dell'autore specificato. Se un autore ha un nome intero, per\n" " usare questa funzione sarà necessario utilizzare il suo ID (per cui non usare nomi\n" " interi!). <canale> è necessario solo se se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:637 msgid "No factoids by %q found." msgstr "Nessun factoid di %q trovato." #: plugin.py:640 msgid "Author search for %q (%i found): %L" msgstr "Ricerca di autori per %q (trovati %i): %L" #: plugin.py:647 #, docstring msgid "" "[<channel>] <text>\n" "\n" " Lists the keys of the factoids whose key contains the provided text.\n" " <channel> is only necessary if the message isn't sent in the channel\n" " itself.\n" " " msgstr "" "[<canale>] <testo>\n" "\n" " Elenca le chiavi dei factoid le quali contengono il testo specificato.\n" " <canale> è necessario solo se se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:655 msgid "No keys matching %q found." msgstr "Nessun factoid corrispondente a %q trovato." #: plugin.py:662 msgid "Key search for %q (%i found): %L" msgstr "Ricerca di chiavi per %q (trovate %i): %L" #: plugin.py:669 #, docstring msgid "" "[<channel>] <text>\n" "\n" " Lists the keys of the factoids whose value contains the provided text.\n" " <channel> is only necessary if the message isn't sent in the channel\n" " itself.\n" " " msgstr "" "[<canale>] <testo>\n" "\n" " Elenca le chiavi dei factoid le quali contengono il testo specificato.\n" " <canale> è necessario solo se se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:677 msgid "No values matching %q found." msgstr "Nessun valore corrispondente a %q trovato." #: plugin.py:680 msgid "Value search for %q (%i found): %L" msgstr "Ricerca di valori per %q (trovati %i): %L" #: plugin.py:687 #, docstring msgid "" "[<channel>] <factoid key>\n" "\n" " Deletes the factoid with the given key. <channel> is only necessary\n" " if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] <chiave>\n" "\n" " Cancella il factoid con la chiave specificata. <canale> è necessario\n" " solo se se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:700 #, docstring msgid "" "[<channel>]\n" "\n" " Displays a random factoid (along with its key) from the database.\n" " <channel> is only necessary if the message isn't sent in the channel\n" " itself.\n" " " msgstr "" "[<canale>]\n" "\n" " Mostra un factoid casuale (con la sua chiave) dal database. <canale> è\n" " necessario solo se se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:708 msgid "No factoids in the database." msgstr "Nessun factoid nel database." ������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/MoobotFactoids/plugin.py������������������������������������������������0000755�0001750�0001750�00000067306�13634634532�021603� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Daniel DiPaolo # 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 os import sys import time import string import supybot.conf as conf import supybot.ircdb as ircdb import supybot.utils as utils import supybot.shlex as shlex from supybot.commands import * import supybot.utils.minisix as minisix import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('MoobotFactoids') class OptionList(object): separators = '|()' def _insideParens(self, lexer): ret = [] while True: token = lexer.get_token() if not token: return '(%s' % ''.join(ret) #) elif token == ')': if '|' in ret: L = list(map(''.join, utils.iter.split('|'.__eq__, ret, yieldEmpty=True))) return utils.iter.choice(L) else: return '(%s)' % ''.join(ret) elif token == '(': ret.append(self._insideParens(lexer)) elif token == '|': ret.append(token) else: ret.append(token) def tokenize(self, s): lexer = shlex.shlex(minisix.io.StringIO(s)) lexer.commenters = '' lexer.quotes = '' lexer.whitespace = '' lexer.separators += self.separators ret = [] while True: token = lexer.get_token() if not token: break elif token == '(': ret.append(self._insideParens(lexer)) else: ret.append(token) return ''.join(ret) def pickOptions(s): return OptionList().tokenize(s) class SqliteMoobotDB(object): def __init__(self, filename): self.filename = filename self.dbs = ircutils.IrcDict() def close(self): for db in self.dbs.values(): db.close() self.dbs.clear() def _getDb(self, channel): import sqlite3 if channel in self.dbs: return self.dbs[channel] filename = plugins.makeChannelFilename(self.filename, channel) if os.path.exists(filename): db = sqlite3.connect(filename, check_same_thread=False) if minisix.PY2: db.text_factory = str self.dbs[channel] = db return db db = sqlite3.connect(filename, check_same_thread=False) if minisix.PY2: db.text_factory = str self.dbs[channel] = db cursor = db.cursor() cursor.execute("""CREATE TABLE factoids ( key TEXT PRIMARY KEY, created_by INTEGER, created_at TIMESTAMP, modified_by INTEGER, modified_at TIMESTAMP, locked_at TIMESTAMP, locked_by INTEGER, last_requested_by TEXT, last_requested_at TIMESTAMP, fact TEXT, requested_count INTEGER )""") db.commit() return db def getFactoid(self, channel, key): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT fact FROM factoids WHERE key LIKE ?""", (key,)) results = cursor.fetchall() if len(results) == 0: return None else: return results[0] def getFactinfo(self, channel, key): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT created_by, created_at, modified_by, modified_at, last_requested_by, last_requested_at, requested_count, locked_by, locked_at FROM factoids WHERE key LIKE ?""", (key,)) results = cursor.fetchall() if len(results) == 0: return None else: 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""") results = cursor.fetchall() if len(results) == 0: return None else: return results[0] def addFactoid(self, channel, key, value, creator_id): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""INSERT INTO factoids VALUES (?, ?, ?, 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=?, 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 = ?, last_requested_at = ?, requested_count = requested_count + 1 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 ?""", (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 ?""", (key,)) if cursor.fetchone()[0] is None: return False else: return True def lock(self, channel, key, locker_id): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""UPDATE factoids 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=?, locked_at=? WHERE key LIKE ?""", (None, None, key)) db.commit() def mostAuthored(self, channel, limit): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT created_by, count(key) FROM factoids GROUP BY created_by 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 ?""", (limit,)) return cursor.fetchall() def mostPopular(self, channel, limit): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT key, requested_count FROM factoids WHERE requested_count > 0 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=? 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 ? 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 ? ORDER BY key""", (glob,)) results = cursor.fetchall() return results MoobotDB = plugins.DB('MoobotFactoids', {'sqlite3': SqliteMoobotDB}) class MoobotFactoids(callbacks.Plugin): """An alternative to the Factoids plugin, this plugin keeps factoids in your bot.""" callBefore = ['Dunno'] def __init__(self, irc): self.db = MoobotDB() self.__parent = super(MoobotFactoids, self) self.__parent.__init__(irc) def die(self): self.__parent.die() self.db.close() def reset(self): self.db.close() _replyTag = '<reply>' _actionTag = '<action>' def _parseFactoid(self, irc, msg, fact): type = 'define' # Default is to just spit the factoid back as a # definition of what the key is (i.e., "foo is bar") newfact = pickOptions(fact) if newfact.startswith(self._replyTag): newfact = newfact[len(self._replyTag):] type = 'reply' elif newfact.startswith(self._actionTag): newfact = newfact[len(self._actionTag):] type = 'action' newfact = newfact.strip() newfact = ircutils.standardSubstitute(irc, msg, newfact) return (type, newfact) def invalidCommand(self, irc, msg, tokens): if '=~' in tokens: self.changeFactoid(irc, msg, tokens) elif tokens and tokens[0] in ('no', 'no,'): self.replaceFactoid(irc, msg, tokens) elif ['is', 'also'] in utils.seq.window(tokens, 2): self.augmentFactoid(irc, msg, tokens) else: key = ' '.join(tokens) key = self._sanitizeKey(key) channel = plugins.getChannel(msg.channel or msg.args[0]) fact = self.db.getFactoid(channel, key) if fact: self.db.updateRequest(channel, key, msg.prefix) # getFactoid returns "all results", so we need to extract the # first one. fact = fact[0] # Update the requested count/requested by for this key hostmask = msg.prefix # Now actually get the factoid and respond accordingly (type, text) = self._parseFactoid(irc, msg, fact) if type == 'action': irc.reply(text, action=True) elif type == 'reply': irc.reply(text, prefixNick=False) elif type == 'define': irc.reply(format(_('%s is %s'), key, text), prefixNick=False) else: assert False, 'Spurious type from _parseFactoid' else: if 'is' in tokens or '_is_' in tokens: self.addFactoid(irc, msg, tokens) def _getUserId(self, irc, prefix): try: return ircdb.users.getUserId(prefix) except KeyError: irc.errorNotRegistered(Raise=True) def _sanitizeKey(self, key): return key.rstrip('!? ') def _checkNotLocked(self, irc, channel, key): if self.db.locked(channel, key): irc.error(format(_('Factoid %q is locked.'), key), Raise=True) def _getFactoid(self, irc, channel, key): fact = self.db.getFactoid(channel, key) if fact is not None: return fact else: irc.error(format(_('Factoid %q not found.'), key), Raise=True) def _getKeyAndFactoid(self, tokens): if '_is_' in tokens: p = '_is_'.__eq__ elif 'is' in tokens: p = 'is'.__eq__ else: self.log.debug('Invalid tokens for {add,replace}Factoid: %s.', tokens) s = _('Missing an \'is\' or \'_is_\'.') raise ValueError(s) (key, newfact) = list(map(' '.join, utils.iter.split(p, tokens, maxsplit=1))) key = self._sanitizeKey(key) return (key, newfact) def addFactoid(self, irc, msg, tokens): # First, check and see if the entire message matches a factoid key channel = plugins.getChannel(msg.channel or msg.args[0]) id = self._getUserId(irc, msg.prefix) try: (key, fact) = self._getKeyAndFactoid(tokens) except ValueError as e: irc.error(str(e), Raise=True) # Check and make sure it's not in the DB already if self.db.getFactoid(channel, key): irc.error(format(_('Factoid %q already exists.'), key), Raise=True) self.db.addFactoid(channel, key, fact, id) irc.replySuccess() def changeFactoid(self, irc, msg, tokens): id = self._getUserId(irc, msg.prefix) (key, regexp) = list(map(' '.join, utils.iter.split('=~'.__eq__, tokens, maxsplit=1))) channel = plugins.getChannel(msg.channel or msg.args[0]) # Check and make sure it's in the DB fact = self._getFactoid(irc, channel, key) self._checkNotLocked(irc, channel, key) # It's fair game if we get to here try: r = utils.str.perlReToReplacer(regexp) except ValueError as e: irc.errorInvalid('regexp', regexp, Raise=True) fact = fact[0] new_fact = r(fact) self.db.updateFactoid(channel, key, new_fact, id) irc.replySuccess() def augmentFactoid(self, irc, msg, tokens): # Must be registered! id = self._getUserId(irc, msg.prefix) pairs = list(utils.seq.window(tokens, 2)) isAlso = pairs.index(['is', 'also']) key = ' '.join(tokens[:isAlso]) new_text = ' '.join(tokens[isAlso+2:]) channel = plugins.getChannel(msg.channel or msg.args[0]) fact = self._getFactoid(irc, channel, key) self._checkNotLocked(irc, channel, key) # It's fair game if we get to here fact = fact[0] new_fact = format(_('%s, or %s'), fact, new_text) self.db.updateFactoid(channel, key, new_fact, id) irc.replySuccess() def replaceFactoid(self, irc, msg, tokens): # Must be registered! channel = plugins.getChannel(msg.channel or msg.args[0]) id = self._getUserId(irc, msg.prefix) del tokens[0] # remove the "no," try: (key, fact) = self._getKeyAndFactoid(tokens) except ValueError as e: irc.error(str(e), Raise=True) _ = self._getFactoid(irc, channel, key) self._checkNotLocked(irc, channel, key) self.db.removeFactoid(channel, key) self.db.addFactoid(channel, key, fact, id) irc.replySuccess() @internationalizeDocstring def literal(self, irc, msg, args, channel, key): """[<channel>] <factoid key> Returns the literal factoid for the given factoid key. No parsing of the factoid value is done as it is with normal retrieval. <channel> is only necessary if the message isn't sent in the channel itself. """ fact = self._getFactoid(irc, channel, key) fact = fact[0] irc.reply(fact) literal = wrap(literal, ['channeldb', 'text']) @internationalizeDocstring def factinfo(self, irc, msg, args, channel, key): """[<channel>] <factoid key> Returns the various bits of info on the factoid for the given key. <channel> is only necessary if the message isn't sent in the channel itself. """ # Start building the response string s = key + ': ' # Next, get all the info and build the response piece by piece info = self.db.getFactinfo(channel, key) if not info: irc.error(format(_('No such factoid: %q'), key)) return (created_by, created_at, modified_by, modified_at, last_requested_by, last_requested_at, requested_count, locked_by, locked_at) = info # First, creation info. # Map the integer created_by to the username created_by = plugins.getUserName(created_by) created_at = time.strftime(conf.supybot.reply.format.time(), time.localtime(int(created_at))) s += format(_('Created by %s on %s.'), created_by, created_at) # Next, modification info, if any. if modified_by is not None: modified_by = plugins.getUserName(modified_by) modified_at = time.strftime(conf.supybot.reply.format.time(), time.localtime(int(modified_at))) s += format(_(' Last modified by %s on %s.'), modified_by, modified_at) # Next, last requested info, if any if last_requested_by is not None: last_by = last_requested_by # not an int user id last_at = time.strftime(conf.supybot.reply.format.time(), time.localtime(int(last_requested_at))) req_count = requested_count s += format(_(' Last requested by %s on %s, requested %n.'), last_by, last_at, (requested_count, 'time')) # Last, locked info if locked_at is not None: lock_at = time.strftime(conf.supybot.reply.format.time(), time.localtime(int(locked_at))) lock_by = plugins.getUserName(locked_by) s += format(_(' Locked by %s on %s.'), lock_by, lock_at) irc.reply(s) factinfo = wrap(factinfo, ['channeldb', 'text']) def _lock(self, irc, msg, channel, user, key, locking=True): #self.log.debug('in _lock') #self.log.debug('id: %s', id) id = user.id info = self.db.getFactinfo(channel, key) if not info: irc.error(format(_('No such factoid: %q'), key)) return (created_by, a, a, a, a, a, a, locked_by, a) = info # Don't perform redundant operations if locking and locked_by is not None: irc.error(format(_('Factoid %q is already locked.'), key)) return if not locking and locked_by is None: irc.error(format(_('Factoid %q is not locked.'), key)) return # Can only lock/unlock own factoids unless you're an admin #self.log.debug('admin?: %s', ircdb.checkCapability(id, 'admin')) #self.log.debug('created_by: %s', created_by) if not (ircdb.checkCapability(id, 'admin') or created_by == id): if locking: s = 'lock' else: s = 'unlock' irc.error(format(_('Cannot %s someone else\'s factoid unless you ' 'are an admin.'), s)) return # Okay, we're done, ready to lock/unlock if locking: self.db.lock(channel, key, id) else: self.db.unlock(channel, key) irc.replySuccess() @internationalizeDocstring def lock(self, irc, msg, args, channel, user, key): """[<channel>] <factoid key> Locks the factoid with the given factoid key. Requires that the user be registered and have created the factoid originally. <channel> is only necessary if the message isn't sent in the channel itself. """ self._lock(irc, msg, channel, user, key, True) lock = wrap(lock, ['channeldb', 'user', 'text']) @internationalizeDocstring def unlock(self, irc, msg, args, channel, user, key): """[<channel>] <factoid key> Unlocks the factoid with the given factoid key. Requires that the user be registered and have locked the factoid. <channel> is only necessary if the message isn't sent in the channel itself. """ self._lock(irc, msg, channel, user, key, False) unlock = wrap(unlock, ['channeldb', 'user', 'text']) @internationalizeDocstring def most(self, irc, msg, args, channel, method): """[<channel>] {popular|authored|recent} Lists the most {popular|authored|recent} factoids. "popular" lists the most frequently requested factoids. "authored" lists the author with the most factoids. "recent" lists the most recently created factoids. <channel> is only necessary if the message isn't sent in the channel itself. """ method = method.capitalize() method = getattr(self, '_most%s' % method, None) if method is None: raise callbacks.ArgumentError limit = self.registryValue('mostCount', channel, irc.network) method(irc, channel, limit) most = wrap(most, ['channeldb', ('literal', ('popular', 'authored', 'recent'))]) def _mostAuthored(self, irc, channel, limit): results = self.db.mostAuthored(channel, limit) L = ['%s (%s)' % (plugins.getUserName(t[0]), int(t[1])) for t in results] if L: author = _('author') if len(L) != 1: author = _('authors') irc.reply(format(_('Most prolific %s: %L'), author, L)) else: irc.error(_('There are no factoids in my database.')) def _mostRecent(self, irc, channel, limit): results = self.db.mostRecent(channel, limit) L = [format('%q', t[0]) for t in results] if L: if len(L) < 2: latest = _('latest factoid') else: latest = _('latest factoids') irc.reply(format(_('%i %s: %L'), len(L), latest, L)) else: irc.error(_('There are no factoids in my database.')) def _mostPopular(self, irc, channel, limit): results = self.db.mostPopular(channel, limit) L = [format('%q (%s)', t[0], t[1]) for t in results] if L: if len(L) < 2: requested = _('requested factoid') else: requested = _('requested factoids') irc.reply(format(_('Top %i %s: %L'), len(L), requested, L)) else: irc.error(_('No factoids have been requested from my database.')) @internationalizeDocstring def listauth(self, irc, msg, args, channel, author): """[<channel>] <author name> Lists the keys of the factoids with the given author. Note that if an author has an integer name, you'll have to use that author's id to use this function (so don't use integer usernames!). <channel> is only necessary if the message isn't sent in the channel itself. """ try: id = ircdb.users.getUserId(author) except KeyError: irc.errorNoUser(name=author, Raise=True) results = self.db.getKeysByAuthor(channel, id) if not results: irc.reply(format(_('No factoids by %q found.'), author)) return keys = [format('%q', t[0]) for t in results] s = format(_('Author search for %q (%i found): %L'), author, len(keys), keys) irc.reply(s) listauth = wrap(listauth, ['channeldb', 'something']) @internationalizeDocstring def listkeys(self, irc, msg, args, channel, search): """[<channel>] <text> Lists the keys of the factoids whose key contains the provided text. <channel> is only necessary if the message isn't sent in the channel itself. """ results = self.db.getKeysByGlob(channel, search) if not results: irc.reply(format(_('No keys matching %q found.'), search)) elif len(results) == 1 and \ self.registryValue('showFactoidIfOnlyOneMatch', channel, irc.network): key = results[0][0] self.invalidCommand(irc, msg, [key]) else: keys = [format('%q', tup[0]) for tup in results] s = format(_('Key search for %q (%i found): %L'), search, len(keys), keys) irc.reply(s) listkeys = wrap(listkeys, ['channeldb', 'text']) @internationalizeDocstring def listvalues(self, irc, msg, args, channel, search): """[<channel>] <text> Lists the keys of the factoids whose value contains the provided text. <channel> is only necessary if the message isn't sent in the channel itself. """ results = self.db.getKeysByValueGlob(channel, search) if not results: irc.reply(format(_('No values matching %q found.'), search)) return keys = [format('%q', tup[0]) for tup in results] s = format(_('Value search for %q (%i found): %L'), search, len(keys), keys) irc.reply(s) listvalues = wrap(listvalues, ['channeldb', 'text']) @internationalizeDocstring def remove(self, irc, msg, args, channel, _, key): """[<channel>] <factoid key> Deletes the factoid with the given key. <channel> is only necessary if the message isn't sent in the channel itself. """ _ = self._getFactoid(irc, channel, key) self._checkNotLocked(irc, channel, key) self.db.removeFactoid(channel, key) irc.replySuccess() remove = wrap(remove, ['channeldb', 'user', 'text']) @internationalizeDocstring def random(self, irc, msg, args, channel): """[<channel>] Displays a random factoid (along with its key) from the database. <channel> is only necessary if the message isn't sent in the channel itself. """ results = self.db.randomFactoid(channel) if not results: irc.error(_('No factoids in the database.')) return (fact, key) = results irc.reply(format('Random factoid: %q is %q', key, fact)) random = wrap(random, ['channeldb']) MoobotFactoids = internationalizeDocstring(MoobotFactoids) Class = MoobotFactoids # vim:set shiftwidth=4 softtabstop=8 expandtab textwidth=78: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/MoobotFactoids/test.py��������������������������������������������������0000644�0001750�0001750�00000041001�13634634532�021241� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- encoding: utf-8 -*- ### # Copyright (c) 2003-2005, Daniel DiPaolo # 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 time from supybot.test import * #import supybot.plugin as plugin import supybot.ircutils as ircutils from supybot.utils.minisix import u # import sqlite3, so it's in sys.modules and conf.supybot.databases includes # sqlite3. try: import sqlite3 except ImportError: sqlite3 = None from . import plugin MFconf = conf.supybot.plugins.MoobotFactoids class OptionListTestCase(SupyTestCase): maxIterations = 267 def _testOptions(self, s, L): max = self.maxIterations original = L[:] while max and L: max -= 1 option = plugin.pickOptions(s) self.assertTrue(option in original, 'Option %s not in %s' % (option, original)) if option in L: L.remove(option) self.assertFalse(L, 'Some options never seen: %s' % L) def testPickOptions(self): self._testOptions('(a|b)', ['a', 'b']) self._testOptions('a', ['a']) self._testOptions('(a|b (c|d))', ['a', 'b c', 'b d']) self._testOptions('(a|(b|)c)', ['a', 'bc', 'c']) self._testOptions('(a(b|)|(c|)d)', ['a', 'ab', 'cd', 'd']) self._testOptions('(a|)', ['a', '']) self._testOptions('(|a)', ['a', '']) self._testOptions('((a)|(b))', ['(a)', '(b)']) self._testOptions('^\\%(\\%(foo\\)\\@<!.\\)*$', ['^\\%(\\%(foo\\)\\@<!.\\)*$']) class NonChannelFactoidsTestCase(ChannelPluginTestCase): plugins = ('MoobotFactoids', 'User') config = {'reply.whenNotCommand': False} def setUp(self): ChannelPluginTestCase.setUp(self) # Create a valid user to use self.prefix = 'mf!bar@baz' self.irc.feedMsg(ircmsgs.privmsg(self.nick, 'register tester moo', prefix=self.prefix)) m = self.irc.takeMsg() # Response to register. def testAddFactoid(self): self.assertNotError('moo is foo') # Check stripping punctuation self.assertError('moo!? is foo') # 'moo' already exists self.assertNotError('foo!? is foo') self.assertResponse('foo', 'foo is foo') self.assertNotError('bar is <reply>moo is moo') self.assertResponse('bar', 'moo is moo') # Check substitution self.assertNotError('who is <reply>$who') self.assertResponse('who', ircutils.nickFromHostmask(self.prefix)) # Check that actions ("\x01ACTION...") don't match m = ircmsgs.action(self.channel, 'is doing something') self.irc.feedMsg(m) self.assertNoResponse(' ', 1) class FactoidsTestCase(ChannelPluginTestCase): plugins = ('MoobotFactoids', 'User', 'String', 'Utilities', 'Web') config = {'reply.whenNotCommand': False} def setUp(self): ChannelPluginTestCase.setUp(self) # Create a valid user to use self.prefix = 'mf!bar@baz' self.irc.feedMsg(ircmsgs.privmsg(self.nick, 'register tester moo', prefix=self.prefix)) m = self.irc.takeMsg() # Response to register. def testAddFactoid(self): self.assertNotError('moo is foo') # Check stripping punctuation self.assertError('moo!? is foo') # 'moo' already exists self.assertNotError('foo!? is foo') self.assertResponse('foo', 'foo is foo') self.assertNotError('bar is <reply>moo is moo') self.assertResponse('bar', 'moo is moo') # Check substitution self.assertNotError('who is <reply>$who') self.assertResponse('who', ircutils.nickFromHostmask(self.prefix)) # Check that actions ("\x01ACTION...") don't match m = ircmsgs.action(self.channel, 'is doing something') self.irc.feedMsg(m) self.assertNoResponse(' ', 1) def testLiteral(self): self.assertError('literal moo') # no factoids yet self.assertNotError('moo is <reply>foo') self.assertResponse('literal moo', '<reply>foo') self.assertNotError('moo2 is moo!') self.assertResponse('literal moo2', 'moo!') self.assertNotError('moo3 is <action>foo') self.assertResponse('literal moo3', '<action>foo') def testGetFactoid(self): self.assertNotError('moo is <reply>foo') self.assertResponse('moo', 'foo') self.assertNotError('moo2 is moo!') self.assertResponse('moo2', 'moo2 is moo!') self.assertNotError('moo3 is <action>foo') self.assertAction('moo3', 'foo') # Test and make sure it's parsing self.assertNotError('moo4 is <reply>(1|2|3)') self.assertRegexp('moo4', r'^(1|2|3)$') # Check case-insensitivity self.assertResponse('MOO', 'foo') self.assertResponse('mOo', 'foo') self.assertResponse('MoO', 'foo') # Check the "_is_" ability self.assertNotError('remove moo') self.assertNotError('moo _is_ <reply>foo') self.assertResponse('moo', 'foo') self.assertNotError('foo is bar _is_ baz') self.assertResponse('foo is bar', 'foo is bar is baz') def testFactinfo(self): self.assertNotError('moo is <reply>foo') self.assertRegexp('factinfo moo', r'^moo: Created by tester on.*$') self.assertNotError('moo') self.assertRegexp('factinfo moo', self.prefix + r'.*1 time') self.assertNotError('moo') self.assertRegexp('factinfo moo', self.prefix + r'.*2 times') self.assertNotError('moo =~ s/foo/bar/') self.assertRegexp('factinfo moo', r'^moo: Created by tester on' r'.*?\. Last modified by tester on .*?\. ' r'Last requested by %s on .*?, ' r'requested 2 times.$' % self.prefix) self.assertNotError('lock moo') self.assertRegexp('factinfo moo', r'^moo: Created by tester on' r'.*?\. Last modified by tester on .*?\. ' r'Last requested by %s on .*?, ' r'requested 2 times. ' r'Locked by tester on .*\.$' % self.prefix) self.assertNotError('unlock moo') self.assertRegexp('factinfo moo', r'^moo: Created by tester on' r'.*?\. Last modified by tester on .*?\. ' r'Last requested by %s on .*?, ' r'requested 2 times.$' % self.prefix) # Make sure I solved this bug # Check and make sure all the other stuff is reset self.assertNotError('foo is bar') self.assertNotError('foo =~ s/bar/blah/') self.assertNotError('foo') self.assertNotError('no foo is baz') self.assertRegexp('factinfo foo', r'^foo: Created by tester on' r'(?!(request|modif)).*?\.$') def testLockUnlock(self): # disable world.testing since we want new users to not # magically be endowed with the admin capability try: world.testing = False self.assertNotError('moo is <reply>moo') self.assertNotError('lock moo') self.assertRegexp('factinfo moo', r'^moo: Created by tester on' r'.*?\. Locked by tester on .*?\.') # switch user original = self.prefix self.prefix = 'moo!moo@moo' self.assertNotError('register nottester moo', private=True) self.assertError('unlock moo') self.assertRegexp('factinfo moo', r'^moo: Created by tester on' r'.*?\. Locked by tester on .*?\.') # switch back self.prefix = original self.assertNotError('identify tester moo', private=True) self.assertNotError('unlock moo') self.assertRegexp('factinfo moo', r'^moo: Created by tester on.*?\.') finally: world.testing = True def testChangeFactoid(self): self.assertNotError('moo is <reply>moo') self.assertNotError('moo =~ s/moo/moos/') self.assertResponse('moo', 'moos') self.assertNotError('moo =~ s/reply/action/') self.assertAction('moo', 'moos') self.assertNotError('moo =~ s/moos/(moos|woofs)/') self.assertActionRegexp('moo', '^(moos|woofs)$') self.assertError('moo =~ s/moo/') def testMost(self): userPrefix1 = 'moo!bar@baz'; userNick1 = 'moo' userPrefix2 = 'boo!bar@baz'; userNick2 = 'boo' self.assertNotError('register %s bar' % userNick1, frm=userPrefix1, private=True) self.assertNotError('register %s bar' % userNick2, frm=userPrefix2, private=True) # Check an empty database self.assertError('most popular') self.assertError('most authored') self.assertError('most recent') # Check singularity response self.prefix = userPrefix1 self.assertNotError('moogle is <reply>moo') self.assertError('most popular') self.assertResponse('most authored', 'Most prolific author: moo (1)') self.assertRegexp('most recent', r"1 latest factoid:.*moogle") self.assertResponse('moogle', 'moo') self.assertRegexp('most popular', r"Top 1 requested factoid:.*moogle.*(1)") # Check plural response time.sleep(1) self.prefix = userPrefix2 self.assertNotError('mogle is <reply>mo') self.assertRegexp('most authored', (r'Most prolific authors: .*' r'(moo.*\(1\).*boo.*\(1\)' r'|boo.*\(1\).*moo.*\(1\))')) self.assertRegexp('most recent', r"2 latest factoids:.*mogle.*moogle.*") self.assertResponse('moogle', 'moo') self.assertRegexp('most popular', r"Top 1 requested factoid:.*moogle.*(2)") self.assertResponse('mogle', 'mo') self.assertRegexp('most popular', r"Top 2 requested factoids:.*" r"moogle.*(2).*mogle.*(1)") # Check most author ordering self.assertNotError('moo is <reply>oom') self.assertRegexp('most authored', r'Most prolific authors:.*boo.*(2).*moo.*(1)') def testListkeys(self): self.assertResponse('listkeys %', 'No keys matching "%" found.') self.assertNotError('moo is <reply>moo') # With this set, if only one key matches, it should respond with # the factoid orig = MFconf.showFactoidIfOnlyOneMatch() try: MFconf.showFactoidIfOnlyOneMatch.setValue(True) self.assertResponse('listkeys moo', 'moo') self.assertResponse('listkeys foo', 'No keys matching "foo" ' 'found.') # Throw in a bunch more for i in range(10): self.assertNotError('moo%s is <reply>moo' % i) self.assertRegexp('listkeys moo', r'^Key search for "moo" ' r'\(11 found\): ("moo\d*", )+and "moo9"$') self.assertNotError('foo is bar') self.assertRegexp('listkeys %', r'^Key search for "\%" ' r'\(12 found\): "foo", ("moo\d*", )+and ' r'"moo9"$') # Check quoting self.assertNotError('foo\' is bar') self.assertResponse('listkeys foo', 'Key search for "foo" ' '(2 found): "foo" and "foo\'"') # Check unicode stuff self.assertResponse(u('listkeys Б'), 'No keys matching "Б" found.') self.assertNotError(u('АБВГДЕЖ is foo')) self.assertNotError(u('АБВГДЕЖЗИ is foo')) self.assertResponse(u('listkeys Б'), 'Key search for "Б" ' '(2 found): "АБВГДЕЖ" and "АБВГДЕЖЗИ"') finally: MFconf.showFactoidIfOnlyOneMatch.setValue(orig) def testListvalues(self): self.assertNotError('moo is moo') self.assertResponse('listvalues moo', 'Value search for "moo" (1 found): "moo"') def testListauth(self): self.assertNotError('moo is <reply>moo') self.assertRegexp('listauth tester', r'tester.*\(1 found\):.*moo') self.assertError('listauth moo') def testRemove(self): self.assertNotError('moo is <reply>moo') self.assertNotError('lock moo') self.assertError('remove moo') self.assertNotError('unlock moo') self.assertNotError('remove moo') def testAugmentFactoid(self): self.assertNotError('moo is foo') self.assertNotError('moo is also bar') self.assertResponse('moo', 'moo is foo, or bar') self.assertNotError('moo is bar _is_ foo') self.assertNotError('moo is bar is also foo') self.assertResponse('moo is bar', 'moo is bar is foo, or foo') def testReplaceFactoid(self): self.assertNotError('moo is foo') self.assertNotError('no moo is bar') self.assertResponse('moo', 'moo is bar') self.assertNotError('no, moo is baz') self.assertResponse('moo', 'moo is baz') self.assertNotError('lock moo') self.assertError('no moo is qux') self.assertNotError('foo is bar _is_ foo') self.assertNotError('no foo is bar _is_ baz') self.assertResponse('foo is bar', 'foo is bar is baz') def testRegexpNotCalledIfAlreadyHandled(self): self.assertResponse('echo foo is bar', 'foo is bar') self.assertNoResponse(' ', 3) def testNoResponseToCtcp(self): self.assertNotError('foo is bar') self.assertResponse('foo', 'foo is bar') self.irc.feedMsg(ircmsgs.privmsg(self.irc.nick, '\x01VERSION\x01')) m = self.irc.takeMsg() self.assertFalse(m) def testAddFactoidNotCalledWithBadNestingSyntax(self): self.assertError('re s/Error:.*/foo/ ]') self.assertNoResponse(' ', 3) def testConfigShowFactoidIfOnlyOneMatch(self): # these are long MFconf = conf.supybot.plugins.MoobotFactoids self.assertNotError('foo is bar') # Default to saying the factoid value self.assertResponse('listkeys foo', 'foo is bar') # Check the False setting MFconf.showFactoidIfOnlyOneMatch.setValue(False) self.assertResponse('listkeys foo', 'Key search for "foo" ' '(1 found): "foo"') def testRandom(self): self.assertNotError('foo is <reply>bar') self.assertNotError('bar is <reply>baz') self.assertRegexp('random', r'bar|baz') # vim:set shiftwidth=4 softtabstop=8 expandtab textwidth=78: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Network/����������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016437� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Network/__init__.py�����������������������������������������������������0000644�0001750�0001750�00000004615�13634634532�020550� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Includes commands for connecting, disconnecting, and reconnecting to multiple networks, as well as several other utility functions related to IRC networks. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload reload(plugin) # In case we're being reloaded. if world.testing: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Network/config.py�������������������������������������������������������0000644�0001750�0001750�00000004664�13634634532�020262� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Network') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Network', True) Network = conf.registerPlugin('Network') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Network, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������limnoria-2020.03.17/plugins/Network/locales/��������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�020061� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Network/locales/de.po���������������������������������������������������0000644�0001750�0001750�00000013503�13634634532�021005� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-10-29 20:20+0100\n" "Last-Translator: Florian Besser <fbesser@gmail.com>\n" "Language-Team: German <fbesser@gmail.com>\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" #: plugin.py:57 msgid "" "[--ssl] <network> [<host[:port]>] [<password>]\n" "\n" " Connects to another network (which will be represented by the name\n" " provided in <network>) at <host:port>. If port is not provided, it\n" " defaults to 6667, the default port for IRC. If password is\n" " provided, it will be sent to the server in a PASS command. If --ssl is\n" " provided, an SSL connection will be attempted.\n" " " msgstr "" "[--ssl] <Netzwerk> [<Host[:Port]>] [<Passwort>]\n" "\n" "Verbindet zu einem anderen Netzwerk, welches durch den Namen in <Netzwerk> auf <Host:Port> festgelegt wird. Falls Port nicht angegeben wird, wird standardgemäß 6667 verwendet, der Standardport für IRC. Falls Passwort angegeben wird, wird es an den Server per PASS Befehl gesendet. Falls --ssl angegeben wird, wird versucht eine SSL Verbindung aufzubauen." #: plugin.py:67 msgid "I'm already connected to %s." msgstr "Ich bin schon verbunden mit %s." #: plugin.py:87 msgid "A server must be provided if the network is not already registered." msgstr "Es muss ein Server angegeben werden, falls das netwerk noch net registriert ist." #: plugin.py:95 msgid "Connection to %s initiated." msgstr "Verbindung zu %s wurde eingeleitet." #: plugin.py:102 msgid "" "[<network>] [<quit message>]\n" "\n" " Disconnects from the network represented by the network <network>.\n" " If <quit message> is given, quits the network with the given quit\n" " message. <network> is only necessary if the network is different\n" " from the network the command is sent on.\n" " " msgstr "[<netzwerk>] [<Beenden Nachricht>]" #: plugin.py:114 msgid "Disconnection to %s initiated." msgstr "Verbindungstrennung zu %s wurde eingeleitet." #: plugin.py:120 msgid "" "[<network>] [<quit message>]\n" "\n" " Disconnects and then reconnects to <network>. If no network is given,\n" " disconnects and then reconnects to the network the command was given\n" " on. If no quit message is given, uses the configured one\n" " (supybot.plugins.Owner.quitMsg) or the nick of the person giving the\n" " command.\n" " " msgstr "" "[<Netzwerk>] [<Beendennachricht>]\n" "\n" "Trennt die Verbindung von <Netzwerk> und baut diese neu auf, falls kein Netzwerk angegeben wird, wird die Verbindung des Netzwerks getrennt und wieder aufgebaut, auf dem der Befehl gegeben wurde. Falls keine Beendennachricht angegeben wird, wird die konfigurierte (supybot.plugins.Owner.quitMsg) oder der Nick des Nutzer, der den Befehl gegeben hat, benutzt." #: plugin.py:137 msgid "" "<network> <command> [<arg> ...]\n" "\n" " Gives the bot <command> (with its associated <arg>s) on <network>.\n" " " msgstr "" "<Netzwerk> <Befehl> [<Argument> ...]\n" "\n" "Gibt dem Bot den <Befehl> (mit den zugehörigen <Argument>en) auf dem <Netzwerk>." #: plugin.py:210 msgid "is an op on %L" msgstr "ist ein Operator in %L" #: plugin.py:212 msgid "is a halfop on %L" msgstr "ist ein Halboperator in %L" #: plugin.py:214 msgid "is voiced on %L" msgstr "hat Sprechrechte in %L" #: plugin.py:217 msgid "is also on %L" msgstr "ist auch in %L" #: plugin.py:219 msgid "is on %L" msgstr "ist in %l" #: plugin.py:221 msgid "isn't on any non-secret channels" msgstr "ist nicht in irgendwelchen Kanälen die nicht als geheim eingestuft sind." #: plugin.py:228 #: plugin.py:229 #: plugin.py:233 msgid "<unknown>" msgstr "<unbekannt>" #: plugin.py:240 msgid " identified" msgstr " identifiziert" #: plugin.py:245 msgid "%s (%s) has been%s on server %s since %s (idle for %s) and %s.%s" msgstr "%s (%s) ist%s auf dem Server % seit %s (im Leerlauf für %s) und %s.%s" #: plugin.py:258 msgid "There is no %s on %s." msgstr "Kein %s auf %s." #: plugin.py:264 msgid "" "[<network>] <nick>\n" "\n" " Returns the WHOIS response <network> gives for <nick>. <network> is\n" " only necessary if the network is different than the network the command\n" " is sent on.\n" " " msgstr "" "[<Netzwerk>] <Nick>\n" "\n" "Gibt die WHOIS Antwort aus, die das <Netzwerk> für <Nick> gibt. <Netzwerk> wird nur benötigt wenn der Befehl nicht im Netzwerk gesendet wird auf dem der Befehl ausgeführt werden soll." #: plugin.py:280 msgid "" "takes no arguments\n" "\n" " Returns the networks to which the bot is currently connected.\n" " " msgstr "" "hat keine Argumente\n" "\n" "Gibt an zu welchen Netzwerken der Bot momentan verbunden ist." #: plugin.py:293 msgid "%.2f seconds." msgstr "%.2f Sekunden." #: plugin.py:297 msgid "" "[<network>]\n" "\n" " Returns the current latency to <network>. <network> is only necessary\n" " if the message isn't sent on the network to which this command is to\n" " apply.\n" " " msgstr "" "[<Netzwerk>]\n" "\n" "Gibt die momenten Latenz zu <Netzwerk> an. <Netzwerk> wird nur benötigt wenn der Befehl nicht im Netzwerk gesendet wird auf dem der Befehl ausgeführt werden soll." #: plugin.py:303 msgid "Latency check (from %s)." msgstr "Latenzprüfung (von %s)." #: plugin.py:311 msgid "" "[<network>]\n" "\n" " Returns the current network driver for <network>. <network> is only\n" " necessary if the message isn't sent on the network to which this\n" " command is to apply.\n" " " msgstr "" "[<Netzwerk>]\n" "\n" "Gibt den momentanen Netzwerktreiber für <Netzwerk> an. <Netzwerk> wird nur benötigt wenn der Befehl nicht im Netzwerk gesendet wird auf dem der Befehl ausgeführt werden soll." ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Network/locales/fi.po���������������������������������������������������0000644�0001750�0001750�00000020504�13634634532�021012� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen <mkaysi@outlook.com>, 2011, 2012. # msgid "" msgstr "" "Project-Id-Version: Network plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 11:29+EET\n" "PO-Revision-Date: 2014-12-20 11:34+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: Finnish <>\n" "Language: fi\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" "Plural-Forms: nplurals=2; plural=(n!=1);\n" "X-Generator: Poedit 1.6.10\n" #: plugin.py:46 msgid "" "Provides network-related commands, such as connecting to multiple networks\n" " and checking latency to the server." msgstr "" "Tarjoaa verkkoon liittyviä komentoja, kuten useaan verkkoon yhdistämisen ja " "botin ja palvelimen\n" " väillä olevan viiveen tarkistksen." #: plugin.py:59 msgid "" "[--ssl] <network> [<host[:port]>] [<password>]\n" "\n" " Connects to another network (which will be represented by the name\n" " provided in <network>) at <host:port>. If port is not provided, it\n" " defaults to 6667, the default port for IRC. If password is\n" " provided, it will be sent to the server in a PASS command. If --ssl " "is\n" " provided, an SSL connection will be attempted.\n" " " msgstr "" "[--ssl] <verkko> [<isäntä[:portti]>] [<salasana>]\n" "\n" " Yhdistää toiseen verkkoon (joka näytetään nimellä, joka on annettu\n" " <verkossa>) <isäntä:portti>. Jos portti ei ole annettu, se\n" " on oletuksena 6667, oletusportti IRC:lle. Jos salasana on\n" " annettu, se lähetetään palvelimelle PASS komennossa. Jos --ssl on\n" " annettu, SSL yhteys muodostetaan.\n" " " #: plugin.py:73 msgid "I'm already connected to %s." msgstr "Olen jo verkkoon %s." #: plugin.py:93 msgid "A server must be provided if the network is not already registered." msgstr "Palvelin täytyy antaa, mikäli verkko ei ole jo rekisteröity." #: plugin.py:101 msgid "Connection to %s initiated." msgstr "Yhdistäminen verkkoon %s aloitettu." #: plugin.py:108 msgid "" "[<network>] [<quit message>]\n" "\n" " Disconnects from the network represented by the network <network>.\n" " If <quit message> is given, quits the network with the given quit\n" " message. <network> is only necessary if the network is different\n" " from the network the command is sent on.\n" " " msgstr "" "[<verkko>] [<lopetus viesti>]\n" "\n" " Katkaisee yhteyden verkkoon, jonka määrittää <verkko>.\n" " Jos <lopetus viesti> on annettu, poistuu verkosta annetulla lopetus\n" " viestillä. <Verkko> on vaadittu vain jos verkko on eri, kuin se, " "verkko josta\n" " viesti on lähetetty.\n" " " #: plugin.py:120 msgid "Disconnection to %s initiated." msgstr "Yhteyden katkaisu verkosta %s aloitettu." #: plugin.py:126 msgid "" "[<network>] [<quit message>]\n" "\n" " Disconnects and then reconnects to <network>. If no network is " "given,\n" " disconnects and then reconnects to the network the command was " "given\n" " on. If no quit message is given, uses the configured one\n" " (supybot.plugins.Owner.quitMsg) or the nick of the person giving " "the\n" " command.\n" " " msgstr "" "[<verkko>] [<lopetus viesti>]\n" "\n" " Katkaisee yhteyden ja yhdistää uudelleen <verkkoon>. Jos verkkoa ei " "ole annettu,\n" " katkaisee yhteyden ja yhdistää uudelleen verkkoon, jossa komento " "annettiin.\n" " Jos lopetus viestiä ei annettu, käyttää määritettyä lopetus viestiä\n" " (supybot.plugins.Owner.quitMsg) tai henkilön joka antoi komennon\n" " nimimerkkiä.\n" " " #: plugin.py:143 msgid "" "<network> <command> [<arg> ...]\n" "\n" " Gives the bot <command> (with its associated <arg>s) on <network>.\n" " " msgstr "" "<verkko> <komento> [<parametrit> ...]\n" "\n" " Antaa botille <komennon> (siihen liitetyillä <parametreillä>) " "<verkossa>.\n" " " #: plugin.py:151 msgid "" "<command> <args>...\n" " \n" " Perform <command> (with its associated <arg>s) on all networks.\n" " " msgstr "" "<komento> [<parametrit> ...]\n" "\n" " Suotrittaa <komennon> (siihen liitetyillä <parametreillä>) kaikissa " "verkoissa.\n" " " #: plugin.py:234 msgid "is an op on %L" msgstr "on kanavaoperaattori %L:llä" #: plugin.py:236 msgid "is a halfop on %L" msgstr "on puolioperaattori %L:llä." #: plugin.py:238 msgid "is voiced on %L" msgstr "on ääni %L:llä" #: plugin.py:241 msgid "is also on %L" msgstr "on myös %L:llä" #: plugin.py:243 msgid "is on %L" msgstr "on %L:llä." #: plugin.py:246 #, fuzzy msgid "" "isn't on any non-secret channels or is using a channel-list hiding umode." msgstr "Ei ole yhdelläkään ei-salaisella kanavalla." #: plugin.py:256 plugin.py:257 plugin.py:263 msgid "<unknown>" msgstr "<tuntematon>" #: plugin.py:270 msgid " identified" msgstr "tunnistautunut" #: plugin.py:276 #, fuzzy msgid "%s (%s) has been%s on server %s since %s (idle for %s). %s %s.%s" msgstr "%s (%s) on ollut %s palvelimella %s %s lähtien (idlannut %s) ja %s.%s" #: plugin.py:280 #, fuzzy msgid "%s (%s) has been%s on server %s and disconnected on %s." msgstr "%s (%s) on ollut %s palvelimella %s %s lähtien (jouten %s) ja %s.%s" #: plugin.py:294 #, fuzzy msgid "There is no user %s on %s." msgstr "%s:ää ei ole verkossa %s." #: plugin.py:296 #, fuzzy msgid "There was no user %s on %s." msgstr "Nimimerkkiä %s ei ole verkossa %s." #: plugin.py:304 plugin.py:320 msgid "" "[<network>] <nick>\n" "\n" " Returns the WHOIS response <network> gives for <nick>. <network> " "is\n" " only necessary if the network is different than the network the " "command\n" " is sent on.\n" " " msgstr "" "[<verkko>] <nimimerkki>\n" "\n" " Palauttaa WHOIS vastauksen, jonka <verkko> antaa <nimimerkille>. " "<Verkko> on\n" " vaadittu vain jos verkko on eri kuin se verkko, josta komento\n" " lähetettiin.\n" " " #: plugin.py:336 msgid "" "[--all]\n" "\n" " Returns the networks to which the bot is currently connected.\n" " If --all is given, also includes networks known by the bot,\n" " but not connected to.\n" " " msgstr "" "[--all]\n" " Palauttaa verkot joihin botti on yhteydessä. Mikäli --all annetaan, " "sisällyttää myös verkot, jotka botti\n" " tuntee, mutta joihin botti ei ole yhteydessä juuri nyt." #: plugin.py:347 msgid "disconnected" msgstr "yhteys katkaistu" #: plugin.py:356 msgid "%.2f seconds." msgstr "%.2f sekuntia." #: plugin.py:360 msgid "" "[<network>]\n" "\n" " Returns the current latency to <network>. <network> is only " "necessary\n" " if the message isn't sent on the network to which this command is " "to\n" " apply.\n" " " msgstr "" "[<verkko>]\n" "\n" " Palauttaa nykyisen viiveen <verkolle>. <Verkko> on vaadittu vain " "jos\n" " viestiä ei lähetetä verkossa, jolle tämä komento on tarkoitettu\n" " vaikuttamaan.\n" " " #: plugin.py:366 msgid "Latency check (from %s)." msgstr "Viiveen tarkistus (%s:ltä)." #: plugin.py:374 msgid "" "[<network>]\n" "\n" " Returns the current network driver for <network>. <network> is " "only\n" " necessary if the message isn't sent on the network to which this\n" " command is to apply.\n" " " msgstr "" "[<verkko>]\n" "\n" " Palauttaa nykyisen verkkoajurin, joka on käytössä <verkossa>. " "<Verkko> on vaadittu\n" " vain jos komentoa ei lähetetä verkossa, johon tämän komennon on " "tarkoitus\n" " vaikuttaa.\n" " " #: plugin.py:385 msgid "" "[<network>]\n" " \n" " Returns the time duration since the connection was established.\n" " " msgstr "" "[<verkko>\n" "\n" " Palauttaa ajan, joka on kulunut siitä, kun yhteys muodostettiin.]" #: plugin.py:392 msgid "I've been connected to %s for %s." msgstr "Olen ollut yhteydessä verkkoon %s ajan %s." #~ msgid "" #~ "takes no arguments\n" #~ "\n" #~ " Returns the networks to which the bot is currently connected.\n" #~ " " #~ msgstr "" #~ "ei ota parametrejä\n" #~ "\n" #~ " Palauttaa verkot, joihin botti on tällä hetkellä muodostanut " #~ "yhteyden.\n" #~ " " ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Network/locales/fr.po���������������������������������������������������0000644�0001750�0001750�00000014451�13634634532�021027� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-01-21 22:36+CET\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: Limnoria <progval@gmail.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" "Language: fr\n" #: plugin.py:58 msgid "" "[--ssl] <network> [<host[:port]>] [<password>]\n" "\n" " Connects to another network (which will be represented by the name\n" " provided in <network>) at <host:port>. If port is not provided, it\n" " defaults to 6667, the default port for IRC. If password is\n" " provided, it will be sent to the server in a PASS command. If --ssl " "is\n" " provided, an SSL connection will be attempted.\n" " " msgstr "" "[--ssl] <nom> [<hôte[:port]>] [<mot de passe>]\n" "\n" "Se connecter à un autre réseau (représenté par le <nom>) au <hôte:port>. Si " "le port n'est pas fourni, il s'agit du 6667, celui par défaut pour IRC. Si " "le <mot de passe> est fourni, l'envoie au serveur par la commande PASS. Si --" "ssl est fourni, une connexion SSL sera requise." #: plugin.py:68 msgid "I'm already connected to %s." msgstr "Je suis déjà connecté à %s." #: plugin.py:88 msgid "A server must be provided if the network is not already registered." msgstr "Un serveur doit être fourni si le réseau n'est pas déjà enregistré." #: plugin.py:96 msgid "Connection to %s initiated." msgstr "Connexion à %s initialisée." #: plugin.py:103 msgid "" "[<network>] [<quit message>]\n" "\n" " Disconnects from the network represented by the network <network>.\n" " If <quit message> is given, quits the network with the given quit\n" " message. <network> is only necessary if the network is different\n" " from the network the command is sent on.\n" " " msgstr "" "[<réseau>] [<message de quit>]\n" "\n" "Se déconnecte du <réseau>. Si le <message de quit> est fourni, quitte le " "réseau avec ce message. Le <réseau> n'est nécessaire que s'il ne s'agit pas " "du réseau sur lequel la commande est envoyée." #: plugin.py:115 msgid "Disconnection to %s initiated." msgstr "Déconnexion à %s initialisée." #: plugin.py:121 msgid "" "[<network>] [<quit message>]\n" "\n" " Disconnects and then reconnects to <network>. If no network is " "given,\n" " disconnects and then reconnects to the network the command was " "given\n" " on. If no quit message is given, uses the configured one\n" " (supybot.plugins.Owner.quitMsg) or the nick of the person giving " "the\n" " command.\n" " " msgstr "" "[<réseau>] [<message de quit>]\n" "\n" "Se déconnecte du <réseau> et s'y reconnecte. Si le <message de quit> est " "fourni, quitte le réseau avec ce message. Le <réseau> n'est nécessaire que " "s'il ne s'agit pas du réseau sur lequel la commande est envoyée." #: plugin.py:138 msgid "" "<network> <command> [<arg> ...]\n" "\n" " Gives the bot <command> (with its associated <arg>s) on <network>.\n" " " msgstr "" "<réseau> <commande> [<arg> ...]\n" "\n" "Envoie la <commande> au bot (avec les arguments) sur le <réseau>." #: plugin.py:213 msgid "is an op on %L" msgstr "est op sur %L" #: plugin.py:215 msgid "is a halfop on %L" msgstr "est halfop sur %L" #: plugin.py:217 msgid "is voiced on %L" msgstr "est voicé sur %L" #: plugin.py:220 msgid "is also on %L" msgstr "est aussi sur %L" #: plugin.py:222 msgid "is on %L" msgstr "est sur %L" #: plugin.py:225 msgid "isn't on any non-secret channels" msgstr "n'est sur aucun canal non secret" #: plugin.py:234 plugin.py:235 plugin.py:241 msgid "<unknown>" msgstr "<inconnu>" #: plugin.py:248 msgid " identified" msgstr " identifié" #: plugin.py:254 msgid "%s (%s) has been%s on server %s since %s (idle for %s) and %s.%s" msgstr "%s (%s) a été%s sur le serveur %s depuis %s (idle depuis %s) et %s.%s" #: plugin.py:258 msgid "%s (%s) has been%s on server %s and disconnect on %s." msgstr "%s (%s) a été%s sur le serveur %s puis c’est déconnecté à %s." #: plugin.py:272 msgid "There is no %s on %s." msgstr "Il n'y a pas de %s sur %s." #: plugin.py:274 msgid "There was no %s on %s." msgstr "Il n'y a pas eu de %s sur %s." #: plugin.py:282 plugin.py:298 msgid "" "[<network>] <nick>\n" "\n" " Returns the WHOIS response <network> gives for <nick>. <network> " "is\n" " only necessary if the network is different than the network the " "command\n" " is sent on.\n" " " msgstr "" "[<réseau>] <nick>\n" "\n" "Retourne les réponses WHOIS du <réseau> pour le <nick>. Le <réseau> n'est " "nécessaire que s'il ne s'agit pas du réseau sur lequel la commande est " "envoyée." #: plugin.py:314 msgid "" "takes no arguments\n" "\n" " Returns the networks to which the bot is currently connected.\n" " " msgstr "" "ne prend pas d'argument\n" " \n" "Retourne la liste des réseaux auxquels le bot est actuellement connecté." #: plugin.py:327 msgid "%.2f seconds." msgstr "%.2f secondes" #: plugin.py:331 msgid "" "[<network>]\n" "\n" " Returns the current latency to <network>. <network> is only " "necessary\n" " if the message isn't sent on the network to which this command is " "to\n" " apply.\n" " " msgstr "" "[<réseau>]\n" "\n" "Retourne la latence actuelle du <réseau>. Le <réseau> n'est nécessaire que " "s'il ne s'agit pas du réseau sur lequel la commande est envoyée." #: plugin.py:337 msgid "Latency check (from %s)." msgstr "Vérification de lance (de %s)." #: plugin.py:345 msgid "" "[<network>]\n" "\n" " Returns the current network driver for <network>. <network> is " "only\n" " necessary if the message isn't sent on the network to which this\n" " command is to apply.\n" " " msgstr "" "[<réseau>]\n" "\n" "Retourne le 'driver' actuel pour le <réseau>. Le <réseau> n'est nécessaire " "que s'il ne s'agit pas du réseau sur lequel la commande est envoyée." #: plugin.py:356 msgid "" "[<network>]\n" " \n" " Returns the time duration since the connection was established.\n" " " msgstr "" "[<network>]\n" "\n" "Indique depuis combien de temps la connexion est établie." #: plugin.py:363 msgid "I've been connected to %s for %s." msgstr "Je suis connecté à %s depuis %s." �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Network/locales/it.po���������������������������������������������������0000644�0001750�0001750�00000014631�13634634532�021034� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2012-07-04 19:51+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugin.py:57 #, docstring msgid "" "[--ssl] <network> [<host[:port]>] [<password>]\n" "\n" " Connects to another network (which will be represented by the name\n" " provided in <network>) at <host:port>. If port is not provided, it\n" " defaults to 6667, the default port for IRC. If password is\n" " provided, it will be sent to the server in a PASS command. If --ssl is\n" " provided, an SSL connection will be attempted.\n" " " msgstr "" "[--ssl] <rete> [<host[:porta]>] [<password>]\n" "\n" " Si connette a un'altra rete (rappresentata dal nome dato a <rete>) su\n" " <host:porta>. Se la porta non è fornita usa la 6667, quella predefinita per\n" " IRC. Se viene specificata la password sarà inviata al server tramite il comando\n" " PASS. Se --ssl è specificato, sarà richiesta una connessione SSL.\n" " " #: plugin.py:67 msgid "I'm already connected to %s." msgstr "Sono già connesso a %s." #: plugin.py:87 msgid "A server must be provided if the network is not already registered." msgstr "Se la rete non è già registrata è necessario fornire un server." #: plugin.py:95 msgid "Connection to %s initiated." msgstr "Connessione a %s iniziata." #: plugin.py:102 #, docstring msgid "" "[<network>] [<quit message>]\n" "\n" " Disconnects from the network represented by the network <network>.\n" " If <quit message> is given, quits the network with the given quit\n" " message. <network> is only necessary if the network is different\n" " from the network the command is sent on.\n" " " msgstr "" "[<rete>] [<messaggio di uscita>]\n" "\n" " Si disconnette dalla rete (rappresentata dal nome dato a <rete>).\n" " Se <messaggio di uscita> è specificato, esce con tale messaggio.\n" " <rete> è necessaria solo se la rete è differente da quella sulla quale è inviato il comando.\n" " " #: plugin.py:114 msgid "Disconnection to %s initiated." msgstr "Disconnessione a %s iniziata." #: plugin.py:120 #, docstring msgid "" "[<network>] [<quit message>]\n" "\n" " Disconnects and then reconnects to <network>. If no network is given,\n" " disconnects and then reconnects to the network the command was given\n" " on. If no quit message is given, uses the configured one\n" " (supybot.plugins.Owner.quitMsg) or the nick of the person giving the\n" " command.\n" " " msgstr "" "[<rete>] [<messaggio di uscita>]\n" "\n" " Si disconnette da <rete> e si riconnette. <rete> è necessaria solo se la rete\n" " è differente da quella sulla quale è inviato il comando. Se non viene specificato un\n" " messaggio di uscita, utilizza quello configurato in supybot.plugins.Owner.quitMsg\n" " o il nick di chi ha dato il comando.\n" " " #: plugin.py:137 #, docstring msgid "" "<network> <command> [<arg> ...]\n" "\n" " Gives the bot <command> (with its associated <arg>s) on <network>.\n" " " msgstr "" "<rete> <comando> [<argomento> ...]\n" "\n" " Invia <comando> al bot (con i suoi argomenti) su <rete>.\n" " " #: plugin.py:210 msgid "is an op on %L" msgstr "è un op su %L" #: plugin.py:212 msgid "is a halfop on %L" msgstr "è un halfop su %L" #: plugin.py:214 msgid "is voiced on %L" msgstr "ha il voice su %L" #: plugin.py:217 msgid "is also on %L" msgstr "è anche su %L" #: plugin.py:219 msgid "is on %L" msgstr "è su %L" #: plugin.py:221 msgid "isn't on any non-secret channels" msgstr "non è in alcun canale non segreto" #: plugin.py:228 plugin.py:229 plugin.py:233 msgid "<unknown>" msgstr "<sconosciuto>" #: plugin.py:240 msgid " identified" msgstr " identificato" #: plugin.py:245 msgid "%s (%s) has been%s on server %s since %s (idle for %s) and %s.%s" msgstr "%s (%s) è%s sul server %s dalle %s (inattivo da %s) ed %s.%s" #: plugin.py:258 msgid "There is no %s on %s." msgstr "Non c'è nessun %s su %s." #: plugin.py:264 #, docstring msgid "" "[<network>] <nick>\n" "\n" " Returns the WHOIS response <network> gives for <nick>. <network> is\n" " only necessary if the network is different than the network the command\n" " is sent on.\n" " " msgstr "" "[<rete>] <nick>\n" "\n" " Restituisce la risposta di WHOIS per <nick> sulla <rete> specificata. <rete> è\n" " necessario solo se la rete è differente da quella sulla quale è inviato il comando.\n" " " #: plugin.py:280 #, docstring msgid "" "takes no arguments\n" "\n" " Returns the networks to which the bot is currently connected.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Restituisce le reti alle quali è attualmente connesso il bot.\n" " " #: plugin.py:293 msgid "%.2f seconds." msgstr "%.2f secondi." #: plugin.py:297 #, docstring msgid "" "[<network>]\n" "\n" " Returns the current latency to <network>. <network> is only necessary\n" " if the message isn't sent on the network to which this command is to\n" " apply.\n" " " msgstr "" "[<rete>]\n" "\n" " Riporta l'attuale latenza di <rete>. <rete> è necessaria solo se la rete\n" " è differente da quella sulla quale è inviato il comando.\n" " " #: plugin.py:303 msgid "Latency check (from %s)." msgstr "Verifica di latenza (da %s)." #: plugin.py:311 #, docstring msgid "" "[<network>]\n" "\n" " Returns the current network driver for <network>. <network> is only\n" " necessary if the message isn't sent on the network to which this\n" " command is to apply.\n" " " msgstr "" "[<rete>]\n" "\n" " Riporta l'attuale driver di rete per <rete>. <rete> è necessaria\n" " solo se la rete è differente da quella sulla quale è inviato il comando.\n" " " #: plugin.py:322 #, docstring msgid "" "[<network>]\n" " \n" " Returns the time duration since the connection was established.\n" " " msgstr "" "[<rete>]\n" " \n" " Riporta da quanto tempo è connesso il bot.\n" " " #: plugin.py:329 msgid "I've been connected to %s for %s." msgstr "Sono connesso a %s da %s." �������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Network/plugin.py�������������������������������������������������������0000644�0001750�0001750�00000030506�13634634532�020305� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2010,2015 James McCoy # 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 time import functools import supybot.conf as conf import supybot.utils as utils import supybot.world as world from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.registry as registry import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Network') class Network(callbacks.Plugin): """Provides network-related commands, such as connecting to multiple networks and checking latency to the server.""" _whois = {} _latency = {} def _getIrc(self, network): irc = world.getIrc(network) if irc: return irc else: raise callbacks.Error('I\'m not currently connected to %s.' % network) @internationalizeDocstring def connect(self, irc, msg, args, opts, network, server, password): """[--nossl] <network> [<host[:port]>] [<password>] Connects to another network (which will be represented by the name provided in <network>) at <host:port>. If port is not provided, it defaults to 6697, the default port for IRC with SSL. If password is provided, it will be sent to the server in a PASS command. If --nossl is provided, an SSL connection will not be attempted, and the port will default to 6667. """ if '.' in network: irc.error("Network names cannot have a '.' in them. " "Remember, this is the network name, not the actual " "server you plan to connect to.", Raise=True) try: otherIrc = self._getIrc(network) irc.error(_('I\'m already connected to %s.') % network) return # We've gotta return here. This is ugly code, but I'm not # quite sure what to do about it. except callbacks.Error: pass ssl = True for (opt, arg) in opts: if opt == 'nossl': ssl = False if server: if ':' in server: (server, port) = server.split(':') port = int(port) elif ssl: port = 6697 else: port = 6667 serverPort = (server, port) else: try: serverPort = conf.supybot.networks.get(network).servers()[0] except (registry.NonExistentRegistryEntry, IndexError): irc.error(_('A server must be provided if the network is not ' 'already registered.')) return Owner = irc.getCallback('Owner') newIrc = Owner._connect(network, serverPort=serverPort, password=password, ssl=ssl) conf.supybot.networks().add(network) assert newIrc.callbacks is irc.callbacks, 'callbacks list is different' irc.replySuccess(_('Connection to %s initiated.') % network) connect = wrap(connect, ['owner', getopts({'nossl': ''}), 'something', additional('something'), additional('something', '')]) @internationalizeDocstring def disconnect(self, irc, msg, args, otherIrc, quitMsg): """<network> [<quit message>] Disconnects from the network represented by the network <network>. If <quit message> is given, quits the network with the given quit message. """ standard_msg = conf.supybot.plugins.Owner.quitMsg() if standard_msg: standard_msg = ircutils.standardSubstitute(irc, msg, standard_msg) quitMsg = quitMsg or standard_msg or msg.nick otherIrc.queueMsg(ircmsgs.quit(quitMsg)) otherIrc.die() conf.supybot.networks().discard(otherIrc.network) if otherIrc != irc: irc.replySuccess(_('Disconnection to %s initiated.') % otherIrc.network) disconnect = wrap(disconnect, ['owner', ('networkIrc', True), additional('text')]) @internationalizeDocstring def reconnect(self, irc, msg, args, otherIrc, quitMsg): """[<network>] [<quit message>] Disconnects and then reconnects to <network>. If no network is given, disconnects and then reconnects to the network the command was given on. If no quit message is given, uses the configured one (supybot.plugins.Owner.quitMsg) or the nick of the person giving the command. """ standard_msg = conf.supybot.plugins.Owner.quitMsg() if standard_msg: standard_msg = ircutils.standardSubstitute(irc, msg, standard_msg) quitMsg = quitMsg or standard_msg or msg.nick otherIrc.queueMsg(ircmsgs.quit(quitMsg)) if otherIrc != irc: # No need to reply if we're reconnecting ourselves. irc.replySuccess() reconnect = wrap(reconnect, ['owner', 'networkIrc', additional('text')]) @internationalizeDocstring def command(self, irc, msg, args, otherIrc, commandAndArgs): """<network> <command> [<arg> ...] Gives the bot <command> (with its associated <arg>s) on <network>. """ self.Proxy(otherIrc, msg, commandAndArgs) command = wrap(command, ['admin', ('networkIrc', True), many('something')]) def cmdall(self, irc, msg, args, commandAndArgs): """<command> [<arg> ...] Perform <command> (with its associated <arg>s) on all networks. """ ircs = world.ircs for ircd in ircs: self.Proxy(ircd, msg, commandAndArgs) cmdall = wrap(cmdall, ['admin', many('something')]) ### # whois command-related stuff. ### def do311(self, irc, msg): nick = ircutils.toLower(msg.args[1]) if (irc, nick) not in self._whois: return elif msg.command == '319': if '319' not in self._whois[(irc, nick)][2]: self._whois[(irc, nick)][2][msg.command] = [] self._whois[(irc, nick)][2][msg.command].append(msg) else: self._whois[(irc, nick)][2][msg.command] = msg # These are all sent by a WHOIS response. do301 = do311 do312 = do311 do314 = do311 do317 = do311 do319 = do311 do320 = do311 def do318(self, irc, msg): nick = msg.args[1] loweredNick = ircutils.toLower(nick) if (irc, loweredNick) not in self._whois: return (replyIrc, replyMsg, d, command) = self._whois[(irc, loweredNick)] d['318'] = msg s = ircutils.formatWhois(irc, d, caller=replyMsg.nick, channel=replyMsg.args[0], command=command) replyIrc.reply(s) del self._whois[(irc, loweredNick)] do369 = do318 def do402(self, irc, msg): nick = msg.args[1] loweredNick = ircutils.toLower(nick) if (irc, loweredNick) not in self._whois: return (replyIrc, replyMsg, d, command) = self._whois[(irc, loweredNick)] del self._whois[(irc, loweredNick)] if command == 'whois': template = _('There is no user %s on %s.') else: template = _('There was no user %s on %s.') s = template % (nick, irc.network) replyIrc.reply(s) do401 = do402 do406 = do402 @internationalizeDocstring def whois(self, irc, msg, args, otherIrc, nick): """[<network>] <nick> Returns the WHOIS response <network> gives for <nick>. <network> is only necessary if the network is different than the network the command is sent on. """ # Here we use a remote server whois (double nick) to get idle/signon time. otherIrc.queueMsg(ircmsgs.whois(nick, nick)) nick = ircutils.toLower(nick) self._whois[(otherIrc, nick)] = (irc, msg, {}, 'whois') whois = wrap(whois, ['networkIrc', 'nick']) @internationalizeDocstring def whowas(self, irc, msg, args, otherIrc, nick): """[<network>] <nick> Returns the WHOIS response <network> gives for <nick>. <network> is only necessary if the network is different than the network the command is sent on. """ # Here we use a remote server whois (double nick) to get idle/signon time. otherIrc.queueMsg(ircmsgs.whowas(nick, nick)) nick = ircutils.toLower(nick) self._whois[(otherIrc, nick)] = (irc, msg, {}, 'whowas') whowas = wrap(whowas, ['networkIrc', 'nick']) @internationalizeDocstring def networks(self, irc, msg, args, opts): """[--all] Returns the networks to which the bot is currently connected. If --all is given, also includes networks known by the bot, but not connected to. """ opts = dict(opts) L = ['%s: %s' % (ircd.network, ircd.server) for ircd in world.ircs] if 'all' in opts: for net in conf.supybot.networks._children.keys(): if net not in [ircd.network for ircd in world.ircs]: L.append('%s: (%s)' % (net, _('disconnected'))) utils.sortBy(str.lower, L) irc.reply(format('%L', L)) networks = wrap(networks, [getopts({'all': ''})]) def doPong(self, irc, msg): now = time.time() if irc in self._latency: (replyIrc, when) = self._latency.pop(irc) replyIrc.reply(_('%.2f seconds.') % (now-when)) @internationalizeDocstring def latency(self, irc, msg, args, otherIrc): """[<network>] Returns the current latency to <network>. <network> is only necessary if the message isn't sent on the network to which this command is to apply. """ otherIrc.queueMsg(ircmsgs.ping(_('Latency check (from %s).') % msg.nick)) self._latency[otherIrc] = (irc, time.time()) irc.noReply() latency = wrap(latency, ['networkIrc']) @internationalizeDocstring def driver(self, irc, msg, args, otherIrc): """[<network>] Returns the current network driver for <network>. <network> is only necessary if the message isn't sent on the network to which this command is to apply. """ irc.reply(otherIrc.driver.__class__.__module__[8:]) driver = wrap(driver, ['networkIrc']) @internationalizeDocstring def uptime(self, irc, msg, args, otherIrc): """[<network>] Returns the time duration since the connection was established. """ network = otherIrc.network now = time.time() started = otherIrc.startedAt irc.reply(_("I've been connected to %s for %s.") % (network, utils.timeElapsed(now - started))) uptime = wrap(uptime, ['networkIrc']) Class = Network # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Network/test.py���������������������������������������������������������0000644�0001750�0001750�00000003654�13634634532�017772� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * class NetworkTestCase(PluginTestCase): plugins = ['Network', 'Utilities'] def testNetworks(self): self.assertNotError('networks') def testCommand(self): self.assertResponse('network command %s echo 1' % self.irc.network, '1') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/News/�������������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�015722� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/News/__init__.py��������������������������������������������������������0000644�0001750�0001750�00000005151�13634634532�020027� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Daniel DiPaolo # 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. ### """ A module to allow each channel to have "news". News items may have expiration dates. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.strike __maintainer__ = supybot.authors.limnoria_core # 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/News/download' from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/News/config.py����������������������������������������������������������0000644�0001750�0001750�00000004653�13634634532�017543� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Daniel DiPaolo # 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 from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('News') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('News', True) News = conf.registerPlugin('News') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(News, 'someConfigVariableName', # registry.Boolean(False, _("""Help for someConfigVariableName."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/News/locales/�����������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017344� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/News/locales/fi.po������������������������������������������������������0000644�0001750�0001750�00000010777�13634634532�020310� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: News plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 11:29+EET\n" "PO-Revision-Date: 2014-12-20 11:41+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.10\n" #: plugin.py:57 msgid "%s (Subject: %q, added by %s on %s)" msgstr "%s (Aihe: %q, lisännyt %s kanavalla %s)" #: plugin.py:61 msgid "%s (Subject: %q, added by %s on %s, expires at %s)" msgstr "%s (Aihe: %q, lisännyt %s kanavalla %s, vanhenee %s)" #: plugin.py:109 msgid "This plugin provides a means of maintaining News for a channel." msgstr "Tämä plugin sallii uutisten ylläpidon kanavalla." #: plugin.py:121 msgid "" "[<channel>] <expires> <subject>: <text>\n" "\n" " Adds a given news item of <text> to a channel with the given " "<subject>.\n" " If <expires> isn't 0, that news item will expire <expires> seconds " "from\n" " now. <channel> is only necessary if the message isn't sent in the\n" " channel itself.\n" " " msgstr "" "[<kanava>] <vanhenee> <aihe>: <teksti>\n" "\n" " Lisää annetun uutisaiheen, <tekstin> kanavalle annetulla " "<aiheella>.\n" " Jos <vanhenee> ei ole 0, se <vanhenee> sekunteja tästä\n" " lähtien. <Kanava> on vaadittu vain jos viestiä ei lähetetä\n" " kanavalla itsellään.\n" " " #: plugin.py:133 msgid "(News item #%i added)" msgstr "(Uutisaihe #%i lisätty)" #: plugin.py:138 msgid "" "[<channel>] [<id>]\n" "\n" " Display the news items for <channel> in the format of '(#id) " "subject'.\n" " If <id> is given, retrieve only that news item; otherwise retrieve " "all\n" " news items. <channel> is only necessary if the message isn't sent " "in\n" " the channel itself.\n" " " msgstr "" "[<kanava>] [<id>]\n" "\n" " Näyttää <kanavan> uutisaiheet muodossa '(#id) otsikko'.\n" " Jos <id> on annettu, vain se uutisaihe haetaan; muutoin hakee " "kaikki\n" " uutisaiheet. <Kanava> on vaadittu vain, jos viestiä ei lähetetä\n" " kanavalla itsellään.\n" " " #: plugin.py:149 msgid "News for %s: %s" msgstr "Uutisia %s::lle %s" #: plugin.py:152 msgid "No news for %s." msgstr "Ei uutisia %s:lle." #: plugin.py:158 plugin.py:172 plugin.py:188 plugin.py:204 msgid "news item id" msgstr "uutisaihe id" #: plugin.py:163 msgid "" "[<channel>] <id>\n" "\n" " Removes the news item with <id> from <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] <id>\n" "\n" " Poistaa uutisaiheen <id:llä> <kanavalta>. <Kanava> on vaadittu\n" " vain jos viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:177 msgid "" "[<channel>] <id> <regexp>\n" "\n" " Changes the news item with <id> from <channel> according to the\n" " regular expression <regexp>. <regexp> should be of the form\n" " s/text/replacement/flags. <channel> is only necessary if the " "message\n" " isn't sent on the channel itself.\n" " " msgstr "" "[<kanava>] <id> <säännöllinen lauseke>\n" "\n" " Vaihtaa uutisaiheen <id:llä> <kanavalta> \n" " <säännöllisen lausekkeen> mukaan. <Säännöllisen lausekkeen> pitäisi " "olla muotoa \n" " s/teksti/korvaus/liput. <Kanava> on vaadittu vain jos viestiä ei " "lähetetä\n" " kanavalla itsellään.\n" " " #: plugin.py:193 msgid "" "[<channel>] [<id>]\n" "\n" " Returns the old news item for <channel> with <id>. If no number is\n" " given, returns all the old news items in reverse order. <channel> " "is\n" " only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] [<id>]\n" "\n" " Palauttaa vanhan uutisaiheen <Kanavalle> <id:llä>. Jos numeroa ei " "ole\n" " annettu, palauttaa kaikki uutisaiheet käänteisessä järjestyksessä. " "<Kanava> on\n" " vaadittu vain jos viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:209 msgid "Old news for %s: %s" msgstr "Vanhoja uutisia %s:lle %s" #: plugin.py:212 msgid "No old news for %s." msgstr "Ei vanhoja uutisia %s:lle." �limnoria-2020.03.17/plugins/News/locales/fr.po������������������������������������������������������0000644�0001750�0001750�00000007745�13634634532�020322� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2010-10-17 16:53+CEST\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\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:57 msgid "%s (Subject: %q, added by %s on %s)" msgstr "%s (Sujet : %q, ajouté par %s le %s)" #: plugin.py:61 msgid "%s (Subject: %q, added by %s on %s, expires at %s)" msgstr "%s (Sujet : %q, ajouté par %s le %s, expire à %s)" #: plugin.py:120 msgid "" "[<channel>] <expires> <subject>: <text>\n" "\n" " Adds a given news item of <text> to a channel with the given <subject>.\n" " If <expires> isn't 0, that news item will expire <expires> seconds from\n" " now. <channel> is only necessary if the message isn't sent in the\n" " channel itself.\n" " " msgstr "" "[<canal>] <expiration> <sujet>: <texte>\n" "\n" "Ajoute la news donnée, contenant le <texte> à un <canal>, avec le <sujet> donné. Si l'<expiration> n'est pas 0, la news expirera dans le nombre donné de secondes. <canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:132 msgid "(News item #%i added)" msgstr "(News #%i ajoutée)" #: plugin.py:137 msgid "" "[<channel>] [<id>]\n" "\n" " Display the news items for <channel> in the format of '(#id) subject'.\n" " If <id> is given, retrieve only that news item; otherwise retrieve all\n" " news items. <channel> is only necessary if the message isn't sent in\n" " the channel itself.\n" " " msgstr "" "[<canal>] [<id>]\n" "\n" "Affiche une news sur le <canal> dans le format'(#id) sujet'. Si l'<id> est donné, ne récupère que la news correspondante ; sinon, récupère toutes les news. <canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:148 msgid "News for %s: %s" msgstr "News pour %s : %s" #: plugin.py:151 msgid "No news for %s." msgstr "Pas de news pour %s." #: plugin.py:157 #: plugin.py:171 #: plugin.py:187 #: plugin.py:203 msgid "news item id" msgstr "id de news" #: plugin.py:162 msgid "" "[<channel>] <id>\n" "\n" " Removes the news item with <id> from <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] <id>\n" "\n" "Retourne la news avec l'<id> du <canal>. <canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:176 msgid "" "[<channel>] <id> <regexp>\n" "\n" " Changes the news item with <id> from <channel> according to the\n" " regular expression <regexp>. <regexp> should be of the form\n" " s/text/replacement/flags. <channel> is only necessary if the message\n" " isn't sent on the channel itself.\n" " " msgstr "" "[<canal>] <id> <regexp>\n" "\n" "Change la news ayant cet <id> sur le <canal>, en accord avec l'expression régulière <regexp>. La <regexp> doit être de la forme s/text/replacement/flags. <canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:192 msgid "" "[<channel>] [<id>]\n" "\n" " Returns the old news item for <channel> with <id>. If no number is\n" " given, returns all the old news items in reverse order. <channel> is\n" " only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] [<id>]\n" "\n" "Retourne l'ancienne news du <canal> avec l'<id>. Si aucun nombre n'est donné, retourne toutes les anciennes news, dans l'ordre inverse. <canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:208 msgid "Old news for %s: %s" msgstr "Anciennes news pour %s : %s" #: plugin.py:211 msgid "No old news for %s." msgstr "Pas d'ancienne news pour %s." ���������������������������limnoria-2020.03.17/plugins/News/locales/it.po������������������������������������������������������0000644�0001750�0001750�00000010203�13634634532�020306� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-19 12:51+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugin.py:57 msgid "%s (Subject: %q, added by %s on %s)" msgstr "%s (Soggetto: %q, aggiunto da %s il %s)" #: plugin.py:61 msgid "%s (Subject: %q, added by %s on %s, expires at %s)" msgstr "%s (Soggetto: %q, aggiunto da %s il %s, scade il %s)" #: plugin.py:120 #, docstring msgid "" "[<channel>] <expires> <subject>: <text>\n" "\n" " Adds a given news item of <text> to a channel with the given <subject>.\n" " If <expires> isn't 0, that news item will expire <expires> seconds from\n" " now. <channel> is only necessary if the message isn't sent in the\n" " channel itself.\n" " " msgstr "" "[<canale>] <scadenza> <soggetto>: <testo>\n" "\n" " Aggiunge ad un canale una notizia contenente <testo> con il <soggetto> fornito.\n" " Se <scadenza> non è uguale a 0, la notizia scadrà entro quel numero di secondi.\n" " <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:132 msgid "(News item #%i added)" msgstr "(Notizia #%i aggiunta)" #: plugin.py:137 #, docstring msgid "" "[<channel>] [<id>]\n" "\n" " Display the news items for <channel> in the format of '(#id) subject'.\n" " If <id> is given, retrieve only that news item; otherwise retrieve all\n" " news items. <channel> is only necessary if the message isn't sent in\n" " the channel itself.\n" " " msgstr "" "[<canale>] [<id>]\n" "\n" " Visualizza le notizie per <canale> nel formato '(#id) soggetto'.\n" " Se <id> è fornito, riporta solo quella notizia; altrimenti le riporta tutte.\n" " <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:148 msgid "News for %s: %s" msgstr "Notizia per %s: %s" #: plugin.py:151 msgid "No news for %s." msgstr "Nessuna notizia per %s." #: plugin.py:157 plugin.py:171 plugin.py:187 plugin.py:203 msgid "news item id" msgstr "id della notizia" #: plugin.py:162 #, docstring msgid "" "[<channel>] <id>\n" "\n" " Removes the news item with <id> from <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] <id>\n" "\n" " Rimuove da <canale> la notizia con il dato <id>. <canale> è necessario\n" " solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:176 #, docstring msgid "" "[<channel>] <id> <regexp>\n" "\n" " Changes the news item with <id> from <channel> according to the\n" " regular expression <regexp>. <regexp> should be of the form\n" " s/text/replacement/flags. <channel> is only necessary if the message\n" " isn't sent on the channel itself.\n" " " msgstr "" "[<canale>] <id> <regexp>\n" "\n" " Modifica da <canale> la notiza con il dato <id> in base all'espressione\n" " regolare <regexp>. <regexp> deve essere nella forma s/text/replacement/flags.\n" " <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:192 #, docstring msgid "" "[<channel>] [<id>]\n" "\n" " Returns the old news item for <channel> with <id>. If no number is\n" " given, returns all the old news items in reverse order. <channel> is\n" " only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] [<id>]\n" "\n" " Riporta la notizia vecchia con <id> per <canale>. Se non viene fornito\n" " alcun numero, riporta tutte le notizie vecchie in ordine inverso.\n" " <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:208 msgid "Old news for %s: %s" msgstr "Notizia vecchia per %s: %s" #: plugin.py:211 msgid "No old news for %s." msgstr "Nessuna notizia vecchia per %s." ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/News/plugin.py����������������������������������������������������������0000644�0001750�0001750�00000020647�13634634532�017575� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Daniel DiPaolo # 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 time import supybot.dbi as dbi import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('News') class DbiNewsDB(plugins.DbiChannelDB): class DB(dbi.DB): class Record(dbi.Record): __fields__ = [ 'subject', 'text', 'at', 'expires', 'by', ] def __str__(self): user = plugins.getUserName(self.by) if self.expires == 0: s = format(_('%s (Subject: %q, added by %s on %s)'), self.text, self.subject, self.by, utils.str.timestamp(self.at)) else: s = format(_('%s (Subject: %q, added by %s on %s, ' 'expires at %s)'), self.text, self.subject, user, utils.str.timestamp(self.at), utils.str.timestamp(self.expires)) return s def __init__(self, filename): # We use self.__class__ here because apparently DB isn't in our # scope. python-- self.__parent = super(self.__class__, self) self.__parent.__init__(filename) def add(self, subject, text, at, expires, by): return self.__parent.add(self.Record(at=at, by=by, text=text, subject=subject, expires=expires)) def getOld(self, id=None): now = time.time() if id: return self.get(id) else: L = [R for R in self if R.expires < now and R.expires != 0] if not L: raise dbi.NoRecordError else: return L def get(self, id=None): now = time.time() if id: return self.__parent.get(id) else: L = [R for R in self if R.expires >= now or R.expires == 0] if not L: raise dbi.NoRecordError return L def change(self, id, f): news = self.get(id) s = '%s: %s' % (news.subject, news.text) s = f(s) (news.subject, news.text) = s.split(': ', 1) self.set(id, news) NewsDB = plugins.DB('News', {'flat': DbiNewsDB}) class News(callbacks.Plugin): """This plugin provides a means of maintaining News for a channel.""" def __init__(self, irc): self.__parent = super(News, self) self.__parent.__init__(irc) self.db = NewsDB() def die(self): self.__parent.die() self.db.close() @internationalizeDocstring def add(self, irc, msg, args, channel, user, at, expires, news): """[<channel>] <expires> <subject>: <text> Adds a given news item of <text> to a channel with the given <subject>. If <expires> isn't 0, that news item will expire <expires> seconds from now. <channel> is only necessary if the message isn't sent in the channel itself. """ try: (subject, text) = news.split(': ', 1) except ValueError: raise callbacks.ArgumentError id = self.db.add(channel, subject, text, at, expires, user.id) irc.replySuccess(format(_('(News item #%i added)'), id)) add = wrap(add, ['channeldb', 'user', 'now', 'expiry', 'text']) @internationalizeDocstring def news(self, irc, msg, args, channel, id): """[<channel>] [<id>] Display the news items for <channel> in the format of '(#id) subject'. If <id> is given, retrieve only that news item; otherwise retrieve all news items. <channel> is only necessary if the message isn't sent in the channel itself. """ if not id: try: records = self.db.get(channel) items = [format('(#%i) %s', R.id, R.subject) for R in records] s = format(_('News for %s: %s'), channel, '; '.join(items)) irc.reply(s) except dbi.NoRecordError: irc.reply(format(_('No news for %s.'), channel)) else: try: record = self.db.get(channel, id) irc.reply(str(record)) except dbi.NoRecordError as id: irc.errorInvalid(_('news item id'), id) news = wrap(news, ['channeldb', additional('positiveInt')]) @internationalizeDocstring def remove(self, irc, msg, args, channel, id): """[<channel>] <id> Removes the news item with <id> from <channel>. <channel> is only necessary if the message isn't sent in the channel itself. """ try: self.db.remove(channel, id) irc.replySuccess() except dbi.NoRecordError: irc.errorInvalid(_('news item id'), id) remove = wrap(remove, ['channeldb', 'positiveInt']) @internationalizeDocstring def change(self, irc, msg, args, channel, id, replacer): """[<channel>] <id> <regexp> Changes the news item with <id> from <channel> according to the regular expression <regexp>. <regexp> should be of the form s/text/replacement/flags. <channel> is only necessary if the message isn't sent on the channel itself. """ try: self.db.change(channel, id, replacer) irc.replySuccess() except dbi.NoRecordError: irc.errorInvalid(_('news item id'), id) change = wrap(change, ['channeldb', 'positiveInt', 'regexpReplacer']) @internationalizeDocstring def old(self, irc, msg, args, channel, id): """[<channel>] [<id>] Returns the old news item for <channel> with <id>. If no number is given, returns all the old news items in reverse order. <channel> is only necessary if the message isn't sent in the channel itself. """ if id: try: record = self.db.getOld(channel, id) irc.reply(str(record)) except dbi.NoRecordError as id: irc.errorInvalid(_('news item id'), id) else: try: records = self.db.getOld(channel) items = [format('(#%i) %s', R.id, R.subject) for R in records] s = format(_('Old news for %s: %s'), channel, '; '.join(items)) irc.reply(s) except dbi.NoRecordError: irc.reply(format(_('No old news for %s.'), channel)) old = wrap(old, ['channeldb', additional('positiveInt')]) Class = News # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/News/test.py������������������������������������������������������������0000644�0001750�0001750�00000006764�13634634532�017262� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Daniel DiPaolo # 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 __future__ import print_function import time from supybot.test import * class NewsTestCase(ChannelPluginTestCase): plugins = ('News','User') def setUp(self): ChannelPluginTestCase.setUp(self) # Create a valid user to use self.prefix = 'news!bar@baz' self.irc.feedMsg(ircmsgs.privmsg(self.nick, 'register tester moo', prefix=self.prefix)) m = self.irc.takeMsg() # Response to register. def testAddnews(self): self.assertNotError('add 0 subject: foo') self.assertRegexp('news', 'subject') self.assertNotError('add 0 subject2: foo2') self.assertRegexp('news', 'subject.*subject2') self.assertNotError('add 5 subject3: foo3') self.assertRegexp('news', 'subject3') timeFastForward(6) self.assertNotRegexp('news', 'subject3') def testNews(self): # These should both fail first, as they will have nothing in the DB self.assertRegexp('news', 'no news') self.assertRegexp('news #channel', 'no news') # Now we'll add news and make sure listnews doesn't fail self.assertNotError('add #channel 0 subject: foo') self.assertNotError('news #channel') self.assertNotError('add 0 subject: foo') self.assertRegexp('news', '#1') self.assertNotError('news 1') def testChangenews(self): self.assertNotError('add 0 Foo: bar') self.assertNotError('change 1 s/bar/baz/') self.assertNotRegexp('news 1', 'bar') self.assertRegexp('news 1', 'baz') def testOldnews(self): self.assertRegexp('old', 'No old news') self.assertNotError('add 0 a: b') self.assertRegexp('old', 'No old news') self.assertNotError('add 5 foo: bar') self.assertRegexp('old', 'No old news') timeFastForward(6) self.assertNotError('old') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ������������limnoria-2020.03.17/plugins/NickAuth/���������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016514� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/NickAuth/__init__.py����������������������������������������������������0000644�0001750�0001750�00000005011�13634634532�020614� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2012, Valentin Lorentz # 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. ### """ Support authentication based on nicks and network services. """ 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__ = "" __author__ = supybot.authors.progval __maintainer__ = supybot.authors.limnoria_core # 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__ = '' from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/NickAuth/config.py������������������������������������������������������0000644�0001750�0001750�00000004672�13634634532�020336� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2012, Valentin Lorentz # 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 from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('NickAuth') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('NickAuth', True) NickAuth = conf.registerPlugin('NickAuth') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(NickAuth, 'someConfigVariableName', # registry.Boolean(False, _("""Help for someConfigVariableName."""))) # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������limnoria-2020.03.17/plugins/NickAuth/locales/�������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�020136� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/NickAuth/locales/de.po��������������������������������������������������0000644�0001750�0001750�00000007320�13634634532�021062� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# NickAuth plugin in Limnoria. # Copyright (C) 2013 Limnoria # Alexander Minges <alexander.minges@gmail.com>, 2013. # msgid "" msgstr "" "Project-Id-Version: NickAuth plugin in Limnoria\n" "POT-Creation-Date: 2012-11-04 11:10+EET\n" "PO-Revision-Date: 2013-01-25 23:59+0100\n" "Last-Translator: Alexander Minges <alexander.minges@gmail.com>\n" "Language-Team: Deutsch <alexander.minges@gmail.com>\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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Gtranslator 2.91.6\n" #: plugin.py:47 msgid "Support authentication based on nicks and network services." msgstr "" "Unterstützt die Authentifizierung basierend auf Nicknamen und Netzwerk-" "Diensten." #: plugin.py:54 plugin.py:59 msgid "You are not authenticated." msgstr "Du bist nicht authentifiziert." #: plugin.py:62 msgid "You must be owner to do that." msgstr "Du musst der Eigentümer sein, um dies tun zu können." #: plugin.py:67 msgid "" "[<network>] <user> <nick>\n" "\n" " Add <nick> to the list of nicks owned by the <user> on the\n" " <network>. You have to register this nick to the network\n" " services to be authenticated.\n" " <network> defaults to the current network.\n" " " msgstr "" "[<Netzwerk>] <Benutzer> <Nickname>\n" "\n" " Füge <Nickname> der Liste von Nicknamen zu, die dem Benutzer " "<Benutzer> im\n" " <Netzwerk> gehören. Du musst diesen Nicknamen beim Netzwerk-" "Dienst\n" " angemeldet haben, um authentifiziert werden zu können.\n" " <Netzwerk> ist standardmäßig das aktuelle Netzwerk.\n" " " #: plugin.py:80 msgid "This nick is already used by someone on this network." msgstr "Dieser Nick wird bereits von jemandem in diesem Netzwerk genutzt." #: plugin.py:89 msgid "" "[<network>] <user> <nick>\n" "\n" " Remove <nick> from the list of nicks owned by the <user> on the\n" " <network>.\n" " <network> defaults to the current network.\n" " " msgstr "" "[<Netzwerk>] <Benutzer> <Nickname>\n" "\n" " Entferne <Nickname> aus der Liste von Nicknamen, die dem " "Benutzer <Benutzer> im\n" " <Netzwerk> gehören.\n" " <Netzwerk> ist standardmäßig das aktuelle Netzwerk.\n" " " #: plugin.py:101 msgid "This nick is not registered to you on this network." msgstr "Dieser Nick ist nicht von Dir in diesem Netzwerk registriert." #: plugin.py:110 msgid "" "[<network>] [<user>]\n" "\n" " Lists nicks of the <user> on the network.\n" " <network> defaults to the current network.\n" " " msgstr "" "[<Netzwerk>] [<Benutzer>]\n" "\n" " Listet die Nicknamen des <Benutzers> im Netzwerk auf.\n" " <Netzwerk> ist standardmäßig das aktuelle Netzwerk.\n" " " #: plugin.py:125 msgid "You have no recognized nick on this network." msgstr "Du hast keinen bekannten Nick in diesem Netzwerk." #: plugin.py:132 msgid "" "takes no argument\n" "\n" " Tries to authenticate you using network services.\n" " If you get no reply, it means you are not authenticated to the\n" " network services." msgstr "" "akzeptiert keine Argumente\n" "\n" " Versucht Dich mit Hilfe des Netzwerk-Dienstes zu authentifizieren.\n" " Falls Du keine Antwort erhältst, bedeutet dies, dass Du nicht beim\n" " Netzwerk-Dienst bekannt bist.\n" " " #: plugin.py:158 msgid "You are now authenticated as %s." msgstr "Du bist nun als %s authentifiziert." #: plugin.py:160 msgid "No user has this nick on this network." msgstr "Kein Benutzer hat diesen Nick in diesem Netzwerk." ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/NickAuth/locales/fi.po��������������������������������������������������0000644�0001750�0001750�00000010074�13634634532�021070� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# NickAuth plugin in Limnoria. # Copyright (C) 2012-2014 Limnoria # Mikaela Suomalainen <mkaysi@outlook.com>, 2012-2014. # msgid "" msgstr "" "Project-Id-Version: NickAuth plugin in Limnoria\n" "POT-Creation-Date: 2014-12-20 14:04+EET\n" "PO-Revision-Date: 2014-12-20 14:16+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: suomi <>\n" "Language: fi_FI\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" "Plural-Forms: \n" "X-Generator: Poedit 1.6.10\n" # I added explaining on what this does to brackets as the explanation was a little unclear. #: plugin.py:47 msgid "Supports authentication based on nicks and network services." msgstr "" "Tukee tunnistautumista käyttäen nimimerkkejä ja verkkopalveluita.\n" " (Kirjautuessasi sisään NickServille botti tunnistaa sinut, mikäli olet \n" " kertonut botille nimimerkkisi)." #: plugin.py:54 plugin.py:59 msgid "You are not authenticated." msgstr "Et ole tunnistautunut." #: plugin.py:62 msgid "You must be owner to do that." msgstr "Vain omistaja voi tehdä tuon." #: plugin.py:67 msgid "" "[<network>] <user> <nick>\n" "\n" " Add <nick> to the list of nicks owned by the <user> on the\n" " <network>. You have to register this nick to the network\n" " services to be authenticated.\n" " <network> defaults to the current network.\n" " " msgstr "" "[<verkko>] <käyttäjä> <nimimerkki>\n" "\n" "Lisää <nimimerkin> <käyttäjän> omistamiin nimimerkkeihin\n" " <verkossa>. Tämä nimimerkki täytyy rekisteröidä verkkopalveluille, jotta " "sillä\n" " voidaan tunnistautua. <Verkko> on oletuksena nykyinen verkko." #: plugin.py:80 msgid "This nick is already used by someone on this network." msgstr "Joku muu käyttää jo tuota nimimerkkiä tässä verkossa." #: plugin.py:89 msgid "" "[<network>] <user> <nick>\n" "\n" " Remove <nick> from the list of nicks owned by the <user> on the\n" " <network>.\n" " <network> defaults to the current network.\n" " " msgstr "" "[<verkko>] <käyttäjä> <nimimerkki>\n" "\n" " Poistaa <nimimerkin> <käyttäjän> omistamista nimimerkeistä <verkossa>.\n" " <Verkko> on oletuksena nykyinen verkko." #: plugin.py:101 msgid "This nick is not registered to you on this network." msgstr "Tuota nimimerkkiä ei ole rekisteröity käyttöösi tässä verkossa." #: plugin.py:110 msgid "" "[<network>] [<user>]\n" "\n" " Lists nicks of the <user> on the network.\n" " <network> defaults to the current network.\n" " " msgstr "" "[<verkko>] [<käyttäjä>]\n" "\n" " Luettelee <käyttäjän> nimimerkit <verkossa>.\n" " <Verkko> on oletuksena nykyinen verkko." #: plugin.py:119 msgid "You are not identified and <user> is not given." msgstr "Et ole tunnistautunut, etkä antanut <käyttäjä> parametriä." #: plugin.py:130 msgid "You have no recognized nick on this network." msgstr "Sinulla ei ole tunnettua nimimerkkiä tässä verkossa." #: plugin.py:133 msgid "%s has no recognized nick on this network." msgstr "Käyttäjällä %s ei ole tunnistettua nimimerkkiä tässä verkossa." #: plugin.py:140 msgid "" "takes no argument\n" "\n" " Tries to authenticate you using network services.\n" " If you get no reply, it means you are not authenticated to the\n" " network services." msgstr "" "ei ota parametrejä\n" "\n" " Yrittää tunnistaa käyttäjän käyttäen verkkopalveluita.\n" " Mikäli et saa vastausta, se tarkoittaa ettet ole tunnistautunut " "verkkopalveluille." #: plugin.py:167 msgid "" "Your secure flag is true and your hostmask doesn't match any of your known " "hostmasks." msgstr "" "Secure-asetuksesi on true ja hostmaskisi ei täsmää yhteenkään\n" " tunnettuun hostmaskiisi." #: plugin.py:171 msgid "You are now authenticated as %s." msgstr "Olet nyt tunnistautunut käyttäjäksi %s." #: plugin.py:173 msgid "No user has this nick on this network." msgstr "Yhdelläkään käyttäjällä ei ole nimimerkkiä tässä verkossa." ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/NickAuth/plugin.py������������������������������������������������������0000644�0001750�0001750�00000021713�13634634532�020362� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2012, Valentin Lorentz # 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 time import supybot.conf as conf import supybot.utils as utils import supybot.ircdb as ircdb from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('NickAuth') @internationalizeDocstring class NickAuth(callbacks.Plugin): """Supports authentication based on nicks and network services.""" def __init__(self, irc): super(NickAuth, self).__init__(irc) self._requests = {} class nick(callbacks.Commands): def _check_auth(self, irc, msg, user): if user is None: irc.error(_('You are not authenticated.'), Raise=True) if not user.checkHostmask(msg.prefix): try: u = ircdb.users.getUser(msg.prefix) except KeyError: irc.error(_('You are not authenticated.'), Raise=True) if not u._checkCapability('owner'): irc.error(_('You must be owner to do that.'), Raise=True) @internationalizeDocstring def add(self, irc, msg, args, network, user, nick): """[<network>] <user> <nick> Add <nick> to the list of nicks owned by the <user> on the <network>. You have to register this nick to the network services to be authenticated. <network> defaults to the current network. """ network = network.network or irc.network user = user or ircdb.users.getUser(msg.prefix) self._check_auth(irc, msg, user) try: user.addNick(network, nick) except KeyError: irc.error(_('This nick is already used by someone on this ' 'network.'), Raise=True) irc.replySuccess() add = wrap(add, [optional('networkIrc'), optional('otherUser'), 'nick']) @internationalizeDocstring def remove(self, irc, msg, args, network, user, nick): """[<network>] <user> <nick> Remove <nick> from the list of nicks owned by the <user> on the <network>. <network> defaults to the current network. """ network = network.network or irc.network user = user or ircdb.users.getUser(msg.prefix) self._check_auth(irc, msg, user) try: user.removeNick(network, nick) except KeyError: irc.error(_('This nick is not registered to you on this ' 'network.'), Raise=True) irc.replySuccess() remove = wrap(remove, [optional('networkIrc'), optional('otherUser'), 'nick']) @internationalizeDocstring def list(self, irc, msg, args, network, user): """[<network>] [<user>] Lists nicks of the <user> on the network. <network> defaults to the current network. """ network = network.network or irc.network try: user = user or ircdb.users.getUser(msg.prefix) except KeyError: irc.error(_('You are not identified and <user> is not given.'), Raise=True) self._check_auth(irc, msg, user) try: list_ = user.nicks[network] if list_: irc.reply(format('%L', list_)) else: raise KeyError except KeyError: if user == ircdb.users.getUser(msg.prefix): irc.error(_('You have no recognized nick on this ' 'network.'), Raise=True) else: irc.error(_('%s has no recognized nick on this ' 'network.') % user.name, Raise=True) list = wrap(list, [optional('networkIrc'), optional('otherUser')]) @internationalizeDocstring def auth(self, irc, msg, args): """takes no argument Tries to authenticate you using network services. If you get no reply, it means you are not authenticated to the network services.""" nick = ircutils.toLower(msg.nick) self._requests[(irc.network, msg.nick)] = (time.time(), msg.prefix, irc) irc.queueMsg(ircmsgs.whois(nick, nick)) auth = wrap(auth, []) def inFilter(self, irc, msg): """If the messages has a server tag with account name, tries to authenticate it.""" if msg.server_tags and 'account' in msg.server_tags: self._auth(irc, msg.prefix, msg.server_tags['account']) return msg def do330(self, irc, msg): mynick, theirnick, theiraccount, garbage = msg.args # I would like to use a dict comprehension, but we have to support # Python 2.6 :( self._requests = dict([(x,y) for x,y in self._requests.items() if y[0]+60>time.time()]) try: (timestamp, prefix, irc) = self._requests.pop((irc.network, theirnick)) except KeyError: return user = ircdb.users.getUserFromNick(irc.network, theiraccount) if user: try: user.addAuth(prefix) except ValueError: irc.error(_('Your secure flag is true and your hostmask ' 'doesn\'t match any of your known hostmasks.'), Raise=True) ircdb.users.setUser(user, flush=False) irc.reply(_('You are now authenticated as %s.') % user.name) else: irc.error(_('No user claimed the nick %s on this network. ' 'If this is you, you should connect with an other ' 'method and use the "nickauth nick add" command, ' 'or ask the owner of the bot to do it.') % (theiraccount,)) def doAccount(self, irc, msg): account = msg.args[0] user = ircdb.users.getUserFromNick(irc.network, account) if account != '*': self._auth(irc, msg.prefix, account) def doJoin(self, irc, msg): if len(msg.args) < 2: # extended-join is not supported return account = msg.args[1] if account != '*': self._auth(irc, msg.prefix, account) def do354(self, irc, msg): if len(msg.args) != 9 or msg.args[1] != '1': return # irc.nick 1 user ip host nick status account gecos (n, t, ident, ip, host, nick, status, account, gecos) = msg.args prefix = '%s!%s@%s' % (nick, ident, host) user = ircdb.users.getUserFromNick(irc.network, account) if account != '0': self._auth(irc, prefix, account) def _auth(self, irc, prefix, account): user = ircdb.users.getUserFromNick(irc.network, account) if not user: try: user = ircdb.users.getUser(prefix) except KeyError: user = None if user: user.addAuth(prefix) ircdb.users.setUser(user, flush=False) Class = NickAuth # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������limnoria-2020.03.17/plugins/NickAuth/test.py��������������������������������������������������������0000644�0001750�0001750�00000010166�13634634532�020043� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2012, Valentin Lorentz # 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.ircdb as ircdb from supybot.test import * class NickAuthTestCase(PluginTestCase): plugins = ('NickAuth', 'User') prefix1 = 'something!user@host.tld' def _procedure(self, nickserv_reply): self.assertNotError('register foobar 123') self.assertResponse('user list', 'foobar') self.assertNotError('hostmask remove foobar %s' % self.prefix) self.assertNotError('identify foobar 123') self.assertNotError('nick add foobar baz') self.assertNotError('unidentify') self.prefix = self.prefix1 self.assertError('nick add foobar qux') self.nick = self.prefix.split('!')[0] self.assertError('hostmask list') self.irc.feedMsg(ircmsgs.privmsg(self.irc.nick, 'auth', prefix=self.prefix)) self.assertEqual(self.irc.takeMsg().command, 'WHOIS') self.assertError('hostmask list') self.irc.feedMsg(ircmsgs.privmsg(self.irc.nick, 'auth', prefix=self.prefix)) self.assertEqual(self.irc.takeMsg().command, 'WHOIS') if nickserv_reply: self.irc.feedMsg(ircmsgs.IrcMsg(':leguin.freenode.net 330 pgjrgrg ' '%s baz :is logged in as' % self.nick)) msg = self.irc.takeMsg() self.assertNotEqual(msg, None) self.assertEqual(msg.args[1], 'You are now authenticated as foobar.') self.assertResponse('hostmask list', 'foobar has no registered hostmasks.') else: msg = self.irc.takeMsg() self.assertEqual(msg, None) self.assertError('hostmask list') def testAuth(self): self._procedure(True) def testNoAuth(self): self._procedure(False) def testList(self): self.assertNotError('register foobar 123') self.assertRegexp('nick list', 'You have no recognized nick') self.assertNotError('nick add foo') self.assertRegexp('nick list', 'foo') self.assertNotError('nick add %s bar' % self.nick) self.assertRegexp('nick list', 'foo and bar') self.assertNotError('nick add %s %s baz' % (self.irc.network, self.nick)) self.assertRegexp('nick list', 'foo, bar, and baz') self.assertRegexp('nick list %s' % self.irc.network, 'foo, bar, and baz') self.assertRegexp('nick list %s foobar' % self.irc.network, 'foo, bar, and baz') # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/NickCapture/������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017216� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/NickCapture/__init__.py�������������������������������������������������0000644�0001750�0001750�00000004752�13634634532�021331� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ This module attempts to capture the bot's nick, watching for an opportunity to switch to that nick. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������limnoria-2020.03.17/plugins/NickCapture/config.py���������������������������������������������������0000644�0001750�0001750�00000005206�13634634532�021032� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('NickCapture') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('NickCapture', True) NickCapture = conf.registerPlugin('NickCapture') conf.registerPlugin('NickCapture') conf.registerGlobalValue(NickCapture, 'ison', registry.Boolean(True, _("""Determines whether the bot will check occasionally if its preferred nick is in use via the ISON command."""))) conf.registerGlobalValue(NickCapture.ison, 'period', registry.PositiveInteger(600, _("""Determines how often (in seconds) the bot will check whether its nick ISON."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/NickCapture/locales/����������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�020640� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/NickCapture/locales/de.po�����������������������������������������������0000644�0001750�0001750�00000002564�13634634532�021571� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-10-31 13:02+0100\n" "Last-Translator: Florian Besser <fbesser@gmail.com>\n" "Language-Team: German <fbesser@gmail.com>\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: German\n" "X-Poedit-Country: GERMANY\n" #: config.py:47 msgid "" "Determines whether the bot will check\n" " occasionally if its preferred nick is in use via the ISON command." msgstr "Legt fest ob der Bot bei Bedarf, mit dem ISON Befehl, checken soll ob sein geschwünschter Nick in benutzung ist." #: config.py:50 msgid "" "Determines how often (in seconds) the bot\n" " will check whether its nick ISON." msgstr "Legt fest wie oft (in Sekunden) der Bot checkt ob der Nick verfügbar ist." #: plugin.py:40 msgid "" "This plugin constantly tries to take whatever nick is configured as\n" " supybot.nick. Just make sure that's set appropriately, and thus plugin\n" " will do the rest." msgstr "Dieses Plugin versucht dauernd den Nick der in supybot.nick konfiguriert ist zu bekommen. Stelle nur sicher das dieser richtig gesetzt wurde und das Plugin macht den Rest." #: plugin.py:89 msgid "This is returned by the ISON command." msgstr "Dies wird vom ISON Befehl zurückgegeben." ��������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/NickCapture/locales/fi.po�����������������������������������������������0000644�0001750�0001750�00000003005�13634634532�021566� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# NickCapture plugin in Limnoria. # Copyright (C) 2011 Limnoria # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: \n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-08-10 15:06+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: \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" #: config.py:47 msgid "" "Determines whether the bot will check\n" " occasionally if its preferred nick is in use via the ISON command." msgstr "" "Määrittää yrittääkö botti tarkistaa\n" " silloin tällöin onko sen haluama nimimerkki käytössä ISON komennolla." #: config.py:50 msgid "" "Determines how often (in seconds) the bot\n" " will check whether its nick ISON." msgstr "" "Määrittää kuinka usein (sekunteina) botti\n" " tarkistaa nimimerkkinsä ISON komennolla." #: plugin.py:40 msgid "" "This plugin constantly tries to take whatever nick is configured as\n" " supybot.nick. Just make sure that's set appropriately, and thus plugin\n" " will do the rest." msgstr "" "Tämä lisäosa yrittää jatkuvasti ottaa sen nimimerkin, joka on määritetty\n" " asetusarvossa supybot.nick. Tee vain varmaksi, että se on määritetty kunnolla\n" " ja tämä lisäosa hoitaa loput." #: plugin.py:89 msgid "This is returned by the ISON command." msgstr "Tämä on ISON komennon palauttama." ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/NickCapture/locales/fr.po�����������������������������������������������0000644�0001750�0001750�00000002573�13634634532�021610� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2010-10-17 16:58+CEST\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\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" #: config.py:47 msgid "" "Determines whether the bot will check\n" " occasionally if its preferred nick is in use via the ISON command." msgstr "Détermine si le bot vérifiera occasionnellement si son nick préféré est en cours d'utilisation grâce à la commande ISON." #: config.py:50 msgid "" "Determines how often (in seconds) the bot\n" " will check whether its nick ISON." msgstr "Détermine tous les combien de temps (en secondes) le bot vérifiera son nick via ISON" #: plugin.py:41 msgid "" "This plugin constantly tries to take whatever nick is configured as\n" " supybot.nick. Just make sure that's set appropriately, and thus plugin\n" " will do the rest." msgstr "Ce plugin essaye constament de récupérer le nick configuré dans supybot.nick. Assurez-vous de le configurer correctement, et ce plugin fera le reste." #: plugin.py:90 msgid "This is returned by the ISON command." msgstr "Ceci est retourné par la commande ISON." �������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/NickCapture/locales/it.po�����������������������������������������������0000644�0001750�0001750�00000002521�13634634532�021606� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-08-10 02:13+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:47 msgid "" "Determines whether the bot will check\n" " occasionally if its preferred nick is in use via the ISON command." msgstr "" "Determina se di tanto in tanto il bot controllerà tramite\n" " il comando ISON se il suo nick sia in uso." #: config.py:50 msgid "" "Determines how often (in seconds) the bot\n" " will check whether its nick ISON." msgstr "" "Determina quanto spesso (in secondi) il bot controllerà il suo nick con ISON.\n" #: plugin.py:40 #, docstring msgid "" "This plugin constantly tries to take whatever nick is configured as\n" " supybot.nick. Just make sure that's set appropriately, and thus plugin\n" " will do the rest." msgstr "" "Questo plugin cerca costantemente di ottenere qualsiasi nick sia impostato\n" " come supybot.nick. Assicurati che questa sia configurata correttamente\n" " e il plugin farà il resto." #: plugin.py:89 #, docstring msgid "This is returned by the ISON command." msgstr "Questo è restituito dal comando ISON." �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/NickCapture/plugin.py���������������������������������������������������0000644�0001750�0001750�00000012373�13634634532�021066� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import time import supybot.conf as conf import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('NickCapture') class NickCapture(callbacks.Plugin): """This plugin constantly tries to take whatever nick is configured as supybot.nick. Just make sure that's set appropriately, and thus plugin will do the rest.""" public = False def __init__(self, irc): self.__parent = super(NickCapture, self) self.__parent.__init__(irc) self.lastIson = 0 self.monitoring = [] def die(self): for irc in self.monitoring: nick = self._getNick(irc.network) irc.unmonitor(nick) def _getNick(self, network): network_nick = conf.supybot.networks.get(network).nick() if network_nick == '': return conf.supybot.nick() else: return network_nick def __call__(self, irc, msg): if irc.afterConnect: nick = self._getNick(irc.network) if nick and not ircutils.strEqual(nick, irc.nick): # We used to check this, but nicksToHostmasks is never cleared # except on reconnects, which can cause trouble. # if nick not in irc.state.nicksToHostmasks: if 'monitor' in irc.state.supported: if irc not in self.monitoring: irc.monitor(nick) self.monitoring.append(irc) else: self._ison(irc, nick) self.__parent.__call__(irc, msg) def _ison(self, irc, nick): assert 'monitor' not in irc.state.supported if self.registryValue('ison'): now = time.time() if now - self.lastIson > self.registryValue('ison.period'): self.lastIson = now self._sendIson(irc, nick) def _sendIson(self, irc, nick): self.log.info('Checking if %s ISON %s.', nick, irc.network) irc.queueMsg(ircmsgs.ison(nick)) def _sendNick(self, irc, nick): self.log.info('Attempting to switch to nick %s on %s.', nick, irc.network) irc.sendMsg(ircmsgs.nick(nick)) def doQuit(self, irc, msg): nick = self._getNick(irc.network) if ircutils.strEqual(msg.nick, nick): self._sendNick(irc, nick) def doNick(self, irc, msg): nick = self._getNick(irc.network) if ircutils.strEqual(msg.nick, nick): self._sendNick(irc, nick) def do303(self, irc, msg): """This is returned by the ISON command.""" if not msg.args[1]: nick = self._getNick(irc.network) if nick: self._sendNick(irc, nick) def do731(self, irc, msg): """This is sent by the MONITOR when a nick goes offline.""" nick = self._getNick(irc.network) for target in msg.args[1].split(','): if nick == target: self._sendNick(irc, nick) self.monitoring.remove(irc) irc.unmonitor(nick) break def do437(self, irc, msg): """Nick/channel is temporarily unavailable""" if irc.isChannel(msg.args[1]): return self.log.info('Nick %s is unavailable; attempting NickServ release ' 'on %s.' % (msg.args[1], irc.network)) irc.sendMsg(ircmsgs.privmsg('NickServ', 'release %s' % msg.args[1])) NickCapture = internationalizeDocstring(NickCapture) Class = NickCapture # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/NickCapture/test.py�����������������������������������������������������0000644�0001750�0001750�00000003333�13634634532�020543� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * class NickCaptureTestCase(PluginTestCase): plugins = ('NickCapture',) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Nickometer/�������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017106� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Nickometer/__init__.py��������������������������������������������������0000644�0001750�0001750�00000006205�13634634532�021214� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, William Robinson. # Derived from work (c) 1998, Adam Spiers <adam.spiers@new.ox.ac.uk> # 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. ### ### # This algorithm is almost a direct from a the perl nickometer from # blootbot. Hardly any of the original code has been used, though most of # the comments, I copy-pasted. As a matter of courtesy, the original copyright # message follows: # # # # # Lame-o-Nickometer backend # # # # (c) 1998 Adam Spiers <adam.spiers@new.ox.ac.uk> # # # # You may do whatever you want with this code, but give me credit. # # # # $Id: Nickometer.py,v 1.13 2004/10/22 22:19:30 jamessan Exp $ # # ### """ A port of Infobot's nickometer command from Perl. This plugin provides one command (called nickometer) which will tell you how 'lame' an IRC nick is. It's an elitist hacker thing, but quite fun. """ 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__ = "" __author__ = supybot.authors.baggins __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Nickometer/config.py����������������������������������������������������0000644�0001750�0001750�00000005014�13634634532�020717� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, William Robinson. # Derived from work (c) 1998, Adam Spiers <adam.spiers@new.ox.ac.uk> # 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 from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Nickometer') def configure(advanced): # This will be called by setup.py to configure this module. Advanced is # a bool that specifies whether the user identified themself 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('Nickometer', True) Nickometer = conf.registerPlugin('Nickometer') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Nickometer, 'someConfigVariableName', # registry.Boolean(False, _("""Help for someConfigVariableName."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Nickometer/locales/�����������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�020530� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Nickometer/locales/fi.po������������������������������������������������0000644�0001750�0001750�00000002472�13634634532�021465� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: Nickometer plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 14:04+EET\n" "PO-Revision-Date: 2014-12-20 14:19+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.10\n" #: plugin.py:81 msgid "Will tell you how lame a nick is by the command 'nickometer [nick]'." msgstr "" "Kertoo kuinka laimea nimimerkki on komennolla\n" " 'nickometer [nimimerkki]'" #: plugin.py:88 msgid "" "[<nick>]\n" "\n" " Tells you how lame said nick is. If <nick> is not given, uses the\n" " nick of the person giving the command.\n" " " msgstr "" "[<nimimerkki>]\n" "\n" " Kertoo sinulle kuinka laimea sanottu nimimerkki on. Jos " "<nimimerkki> ei ole annettu, käyttää\n" " komennon antaneen henkilön nimimerkkiä.\n" " " #: plugin.py:231 msgid "The \"lame nick-o-meter\" reading for \"%s\" is %s%%." msgstr "\"Laimea nimimerkki-maatti\" lukema \"%s\":lle on %s%%." ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Nickometer/locales/fr.po������������������������������������������������0000644�0001750�0001750�00000001624�13634634532�021474� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2010-10-17 18:28+CEST\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\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:84 msgid "" "[<nick>]\n" "\n" " Tells you how lame said nick is. If <nick> is not given, uses the\n" " nick of the person giving the command.\n" " " msgstr "" "[<nick>]\n" "\n" "Mesure la décrédibilité du nick. Si le nick n'est pas donné, utilise le nick de la personne donnant la commande." #: plugin.py:226 msgid "The \"lame nick-o-meter\" reading for \"%s\" is %s%%." msgstr "Le \"décrédibilit-o-mètre\" pour \"%s\" donne %s%%." ������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Nickometer/locales/it.po������������������������������������������������0000644�0001750�0001750�00000001535�13634634532�021502� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-12 17:05+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugin.py:84 #, docstring msgid "" "[<nick>]\n" "\n" " Tells you how lame said nick is. If <nick> is not given, uses the\n" " nick of the person giving the command.\n" " " msgstr "" "[<nick>]\n" "\n" " Misura quanto sia lamer un nick. Se <nick> non è fornito, utilizza\n" " quello della persona che ha dato il comando.\n" " " #: plugin.py:226 msgid "The \"lame nick-o-meter\" reading for \"%s\" is %s%%." msgstr "Il \"nick-o-meter lamer\" per \"%s\" è %s%%." �������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Nickometer/plugin.py����������������������������������������������������0000644�0001750�0001750�00000022663�13634634532�020761� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, William Robinson. # Derived from work (c) 1998, Adam Spiers <adam.spiers@new.ox.ac.uk> # 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. ### ### # This algorithm is almost a direct from a the perl nickometer from # blootbot. Hardly any of the original code has been used, though most of # the comments, I copy-pasted. As a matter of courtesy, the original copyright # message follows: # # # # # Lame-o-Nickometer backend # # # # (c) 1998 Adam Spiers <adam.spiers@new.ox.ac.uk> # # # # You may do whatever you want with this code, but give me credit. # # # # $Id: Nickometer.py,v 1.13 2004/10/22 22:19:30 jamessan Exp $ # # ### import supybot import re import math import string import supybot.utils as utils import supybot.utils.minisix as minisix import supybot.callbacks as callbacks from supybot.commands import wrap, additional from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Nickometer') def slowExponent(x): return 1.3 * x * (1 - math.atan(x / 6.0) * 2 / math.pi) def slowPow(x, y): return math.pow(x, slowExponent(y)) def caseShifts(s): s=re.sub('[^a-zA-Z]', '', s) s=re.sub('[A-Z]+', 'U', s) s=re.sub('[a-z]+', 'l', s) return len(s)-1 def numberShifts(s): s=re.sub('[^a-zA-Z0-9]', '', s) s=re.sub('[a-zA-Z]+', 'l', s) s=re.sub('[0-9]+', 'n', s) return len(s)-1 class Nickometer(callbacks.Plugin): """Will tell you how lame a nick is by the command 'nickometer [nick]'.""" def punish(self, damage, reason): self.log.debug('%s lameness points awarded: %s', damage, reason) return damage @internationalizeDocstring def nickometer(self, irc, msg, args, nick): """[<nick>] Tells you how lame said nick is. If <nick> is not given, uses the nick of the person giving the command. """ score = minisix.L(0) if not nick: nick = msg.nick originalNick = nick if not nick: irc.error('Give me a nick to judge as the argument, please.') return specialCost = [('69', 500), ('dea?th', 500), ('dark', 400), ('n[i1]ght', 300), ('n[i1]te', 500), ('fuck', 500), ('sh[i1]t', 500), ('coo[l1]', 500), ('kew[l1]', 500), ('lame', 500), ('dood', 500), ('dude', 500), ('[l1](oo?|u)[sz]er', 500), ('[l1]eet', 500), ('e[l1]ite', 500), ('[l1]ord', 500), ('pron', 1000), ('warez', 1000), ('xx', 100), ('\\[rkx]0', 1000), ('\\0[rkx]', 1000)] letterNumberTranslator = utils.str.MultipleReplacer(dict(list(zip( '023457+8', 'ozeasttb')))) for special in specialCost: tempNick = nick if special[0][0] != '\\': tempNick = letterNumberTranslator(tempNick) if tempNick and re.search(special[0], tempNick, re.IGNORECASE): score += self.punish(special[1], 'matched special case /%s/' % special[0]) # I don't really know about either of these next two statements, # but they don't seem to do much harm. # Allow Perl referencing nick=re.sub('^\\\\([A-Za-z])', '\1', nick); # C-- ain't so bad either nick=re.sub('^C--$', 'C', nick); # Punish consecutive non-alphas matches=re.findall(r'[^\w\d]{2,}',nick) for match in matches: score += self.punish(slowPow(10, len(match)), '%s consecutive non-alphas ' % len(match)) # Remove balanced brackets ... while True: nickInitial = nick nick = re.sub(r'^([^()]*)(\()(.*)(\))([^()]*)$', r'\1\3\5', nick, 1) nick = re.sub(r'^([^{}]*)(\{)(.*)(\})([^{}]*)$', r'\1\3\5', nick, 1) nick = re.sub(r'^([^\[\]]*)(\[)(.*)(\])([^\[\]]*)$', r'\1\3\5', nick, 1) if nick == nickInitial: break self.log.debug('Removed some matching brackets %r => %r', nickInitial, nick) # ... and punish for unmatched brackets unmatched = re.findall('[][(){}]', nick) if len(unmatched) > 0: score += self.punish(slowPow(10, len(unmatched)), '%s unmatched parentheses' % len(unmatched)) # Punish k3wlt0k k3wlt0k_weights = (5, 5, 2, 5, 2, 3, 1, 2, 2, 2) for i in range(len(k3wlt0k_weights)): hits=re.findall(repr(i), nick) if (hits and len(hits)>0): score += self.punish(k3wlt0k_weights[i] * len(hits) * 30, '%s occurrences of %s ' % (len(hits), i)) # An alpha caps is not lame in middle or at end, provided the first # alpha is caps. nickOriginalCase = nick match = re.search('^([^A-Za-z]*[A-Z].*[a-z].*?)[-_]?([A-Z])', nick) if match: nick = ''.join([nick[:match.start(2)], nick[match.start(2)].lower(), nick[match.start(2)+1:]]) match = re.search('^([^A-Za-z]*)([A-Z])([a-z])', nick) if match: nick = ''.join([nick[:match.start(2)], nick[match.start(2):match.end(2)].lower(), nick[match.end(2):]]) # Punish uppercase to lowercase shifts and vice-versa, modulo # exceptions above # the commented line is the equivalent of the original, but i think # they intended my version, otherwise, the first caps alpha will # still be punished #cshifts = caseShifts(nickOriginalCase); cshifts = caseShifts(nick); if cshifts > 1 and re.match('.*[A-Z].*', nick): score += self.punish(slowPow(9, cshifts), '%s case shifts' % cshifts) # Punish lame endings if re.match('.*[XZ][^a-zA-Z]*$', nickOriginalCase): score += self.punish(50, 'the last alphanumeric character was lame') # Punish letter to numeric shifts and vice-versa nshifts = numberShifts(nick); if nshifts > 1: score += self.punish(slowPow(9, nshifts), '%s letter/number shifts' % nshifts) # Punish extraneous caps caps = re.findall('[A-Z]', nick) if caps and len(caps) > 0: score += self.punish(slowPow(7, len(caps)), '%s extraneous caps' % len(caps)) # one trailing underscore is ok. i also added a - for parasite- nick = re.sub('[-_]$','',nick) # Punish anything that's left remains = re.findall('[^a-zA-Z0-9]', nick) if remains and len(remains) > 0: score += self.punish(50*len(remains) + slowPow(9, len(remains)), '%s extraneous symbols' % len(remains)) # Use an appropriate function to map [0, +inf) to [0, 100) percentage = 100 * (1 + math.tanh((score - 400.0) / 400.0)) * \ (1 - 1 / (1 + score / 5.0)) / 2 # if it's above 99.9%, show as many digits as is interesting score_string=re.sub('(99\\.9*\\d|\\.\\d).*','\\1',repr(percentage)) irc.reply(_('The "lame nick-o-meter" reading for "%s" is %s%%.') % (originalNick, score_string)) self.log.debug('Calculated lameness score for %s as %s ' '(raw score was %s)', originalNick, score_string, score) nickometer = wrap(nickometer, [additional('text')]) Class = Nickometer # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������limnoria-2020.03.17/plugins/Nickometer/test.py������������������������������������������������������0000644�0001750�0001750�00000004133�13634634532�020432� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, aafshar@gmail.com # 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 NickometerTestCase(PluginTestCase): plugins = ('Nickometer',) def testNickometer(self): self.assertNotError('nickometer') self.assertResponse( 'nickometer jemfinch', 'The "lame nick-o-meter" reading for "jemfinch" is 0.0%.') nick = 'xXReallyObnoxious1337NickXx' self.assertResponse( 'nickometer %s' % nick, 'The "lame nick-o-meter" reading for "%s" is 99.96%%.' % nick) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Note/�������������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�015713� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Note/__init__.py��������������������������������������������������������0000644�0001750�0001750�00000005051�13634634532�020017� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ A complete messaging system that allows users to leave 'notes' for other users that can be retrieved later. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = { supybot.authors.inkedmn: ['Original implementation.'] } from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Note/config.py����������������������������������������������������������0000644�0001750�0001750�00000006551�13634634532�017533� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Note') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Note', True) Note = conf.registerPlugin('Note') conf.registerGroup(Note, 'notify') conf.registerGlobalValue(Note.notify, 'onJoin', registry.Boolean(False, """Determines whether the bot will notify people of their new messages when they join the channel. Normally it will notify them when they send a message to the channel, since oftentimes joins are the result of netsplits and not the actual presence of the user.""")) conf.registerGlobalValue(Note.notify.onJoin, 'repeatedly', registry.Boolean(False, """Determines whether the bot will repeatedly notify people of their new messages when they join the channel. That means when they join the channel, the bot will tell them they have unread messages, even if it's told them before.""")) conf.registerGlobalValue(Note.notify, 'autoSend', registry.NonNegativeInteger(0, """Determines the upper limit for automatically sending messages instead of notifications. I.e., if this value is 2 and there are 2 new messages to notify a user about, instead of sending a notification message, the bot will simply send those new messages. If there are 3 new messages, however, the bot will send a notification message.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Note/locales/�����������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017335� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Note/locales/fi.po������������������������������������������������������0000644�0001750�0001750�00000007700�13634634532�020271� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: Note plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 14:04+EET\n" "PO-Revision-Date: 2014-12-20 14:13+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.10\n" #: plugin.py:125 msgid "Allows you to send notes to other users." msgstr "Sallii muistiinpanojen lähettämisen toisille käyttäjille." #: plugin.py:183 msgid "" "<recipient>,[<recipient>,[...]] <text>\n" "\n" " Sends a new note to the user specified. Multiple recipients may be\n" " specified by separating their names by commas.\n" " " msgstr "" "<vastaanottaja>,[<vastaanottaja>,[...]] <teksti>\n" "\n" " Lähettää uuden muistiinpanon määritetylle käyttäjälle. Monia " "vastaanottajia voidaan\n" " määrittää erottamalla nimet pilkuilla.\n" " " #: plugin.py:199 msgid "" "<id> <text>\n" "\n" " Sends a note in reply to <id>.\n" " " msgstr "" "<id> <teksti>\n" "\n" " Lähettää muistiinpanon vastaukseksi <id:seen>.\n" " " #: plugin.py:223 msgid "" "<id>\n" "\n" " Unsends the note with the id given. You must be the\n" " author of the note, and it must be unread.\n" " " msgstr "" "<id>\n" "\n" " Kumoaa muistiinpanon lähetyksen. Sinun täytyy olla\n" " muistiinpanon tekijä, ja sen täytyy olla lukematon.\n" " " #: plugin.py:255 msgid "" "<id>\n" "\n" " Retrieves a single note by its unique note id. Use the 'note list'\n" " command to see what unread notes you have.\n" " " msgstr "" "<id>\n" "\n" " Hakee muistiinpanon sen ainutlaatuisella muistiinpano id:llä. Käytä " "'note list'\n" " komentoa nähdäksesi mitä lukemattomia muistiinpanoja sinulla on.\n" " " #: plugin.py:285 msgid "" "[--{regexp} <value>] [--sent] [<glob>]\n" "\n" " Searches your received notes for ones matching <glob>. If --regexp " "is\n" " given, its associated value is taken as a regexp and matched " "against\n" " the notes. If --sent is specified, only search sent notes.\n" " " msgstr "" "[--{regexp} <arvo>] [--sent] [<glob>]\n" "\n" " Etsii vastaanotetuista muistiinpanojasi niitä muistiinpanoja, jotka " "täsmäävät <globiin>. Jos --regexp on\n" " annettu, sen liitetty arvo on otettu säännöllisestä lausekkeesta ja " "täsmätty muistiinpanoihin.\n" " the notes. Jos --sent on määritetty, hakee vain lähetetyistä " "muistiinpanoista .\n" " " #: plugin.py:327 msgid "" "[--{old,sent}] [--{from,to} <user>]\n" "\n" " Retrieves the ids of all your unread notes. If --old is given, " "list\n" " read notes. If --sent is given, list notes that you have sent. If\n" " --from is specified, only lists notes sent to you from <user>. If\n" " --to is specified, only lists notes sent by you to <user>.\n" " " msgstr "" "[--{old,sent}] [--{from,to} <käyttäjä>]\n" "\n" " Hakee kaikki lukemattomat muistiinpanosi. Jos --old on annettu, " "luettelee\n" " luetut muistiinpanot. Jos --sent on annettu, luettelee " "muistiinpanot, jotka olet lähettänyt. Jos\n" " --from on määritetty, luettelee vain muistiinpanot, jotka <käyttäjä> " "on lähettänyt sinulle. Jos\n" " --to on määritetty, luettelee vain muistiinpanot, jotka ovat " "lähettänyt <käyttäjälle>.\n" " " #: plugin.py:368 msgid "" "takes no arguments\n" "\n" " Retrieves your next unread note, if any.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Hakee lukemattomat muistiinpanosi, jos yhtään.\n" " " ����������������������������������������������������������������limnoria-2020.03.17/plugins/Note/locales/fr.po������������������������������������������������������0000644�0001750�0001750�00000006410�13634634532�020277� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2010-10-29 13:54+CEST\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\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:179 msgid "" "<recipient>,[<recipient>,[...]] <text>\n" "\n" " Sends a new note to the user specified. Multiple recipients may be\n" " specified by separating their names by commas.\n" " " msgstr "" "<destinataire>,[<destinataire>,...] <texte>\n" "\n" "Envoie la nouvelle note aux utilisateurs spécifiés. De multiples destainaires peuvent être spécifiés en séparant leur noms par des virgules." #: plugin.py:195 msgid "" "<id> <text>\n" "\n" " Sends a note in reply to <id>.\n" " " msgstr "" "<id> <texte>\n" "\n" "envoie une note en réponse à celle <id>." #: plugin.py:219 msgid "" "<id>\n" "\n" " Unsends the note with the id given. You must be the\n" " author of the note, and it must be unread.\n" " " msgstr "" "<id>\n" "\n" "Désenvoie la note d'<id> donné. Vous devez être l'auteur de la note, et elle ne doit pas être lue." #: plugin.py:251 msgid "" "<id>\n" "\n" " Retrieves a single note by its unique note id. Use the 'note list'\n" " command to see what unread notes you have.\n" " " msgstr "" "<id>\n" "\n" "Récupère une seule note par son <id> unique. Utilisez la commande 'note list' pour voir combien de notes non lues vous avez." #: plugin.py:281 msgid "" "[--{regexp} <value>] [--sent] [<glob>]\n" "\n" " Searches your received notes for ones matching <glob>. If --regexp is\n" " given, its associated value is taken as a regexp and matched against\n" " the notes. If --sent is specified, only search sent notes.\n" " " msgstr "" "[--{regexp} <valeur>] [--sent] [<glob>]\n" "\n" "Recherche les notes correspondant au <glob> dans vos notes reçues. Si --regexp est donné, sa valeur associée est utilisé comme expression régulière et est à nouveau recherchée dans les notes. Si --sent est donné, ne recherche que dans les notes envoyées." #: plugin.py:320 msgid "" "[--{old,sent}] [--{from,to} <user>]\n" "\n" " Retrieves the ids of all your unread notes. If --old is given, list\n" " read notes. If --sent is given, list notes that you have sent. If\n" " --from is specified, only lists notes sent to you from <user>. If\n" " --to is specified, only lists notes sent by you to <user>.\n" " " msgstr "" "[--{old,sent}] [--{from,to} <utilisateur>]\n" "\n" "Récupère les id de toutes vos news non lues. Si --old est donné, liste les news lues. Si --sent est donné, liste les news que vous avez envoyées. Si --from est spécifié, liste seulement les news envoyées par l'<utilisateur>. Si --to est spécifié, liste seulement les news envoyées par l'<utilisateur>." #: plugin.py:361 msgid "" "takes no arguments\n" "\n" " Retrieves your next unread note, if any.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Récupère votre note non lue suivant, s'il y en a une." ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Note/locales/it.po������������������������������������������������������0000644�0001750�0001750�00000006476�13634634532�020320� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-15 13:13+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugin.py:181 #, docstring msgid "" "<recipient>,[<recipient>,[...]] <text>\n" "\n" " Sends a new note to the user specified. Multiple recipients may be\n" " specified by separating their names by commas.\n" " " msgstr "" "<destinatario>,[<destinatario>,[...]] <testo>\n" "\n" " Invia una nuova nota all'utente specificato. Possono essere specificati\n" " destinatari multipli separando i nomi con una virgola.\n" " " #: plugin.py:197 #, docstring msgid "" "<id> <text>\n" "\n" " Sends a note in reply to <id>.\n" " " msgstr "" "<id> <testo>\n" "\n" " Invia una nota in risposta a <id>.\n" " " #: plugin.py:221 #, docstring msgid "" "<id>\n" "\n" " Unsends the note with the id given. You must be the\n" " author of the note, and it must be unread.\n" " " msgstr "" "<id>\n" "\n" " Annulla l'invio di una nota tramite l'id specificato.\n" " È necessario esserne l'autore e non deve essere stata letta.\n" " " #: plugin.py:253 #, docstring msgid "" "<id>\n" "\n" " Retrieves a single note by its unique note id. Use the 'note list'\n" " command to see what unread notes you have.\n" " " msgstr "" "<id>\n" "\n" " Recupera una singola nota tramite il suo id univoco. Per sapere quali\n" " note hai da leggere, utilizza il comando \"note list\".\n" " " #: plugin.py:283 #, docstring msgid "" "[--{regexp} <value>] [--sent] [<glob>]\n" "\n" " Searches your received notes for ones matching <glob>. If --regexp is\n" " given, its associated value is taken as a regexp and matched against\n" " the notes. If --sent is specified, only search sent notes.\n" " " msgstr "" "[--{regexp} <valore>] [--sent] [<glob>]\n" "\n" " Cerca le note ricevute che corrispondono a <glob>. Se --regexp è\n" " usato, i valori ad essa associati sono presi come regexp e confrontati\n" " con le note. Se si specifica --sent, verranno cercate solo le note spedite.\n" " " #: plugin.py:322 #, docstring msgid "" "[--{old,sent}] [--{from,to} <user>]\n" "\n" " Retrieves the ids of all your unread notes. If --old is given, list\n" " read notes. If --sent is given, list notes that you have sent. If\n" " --from is specified, only lists notes sent to you from <user>. If\n" " --to is specified, only lists notes sent by you to <user>.\n" " " msgstr "" "[--{old,sent}] [--{from,to} <utente>]\n" "\n" " Recupera gli id di tutte le note non lette. Se --old è specificato,\n" " elenca quelle lette; --sent elenca quelle spedite; --from solo quelle\n" " inviate da <utente>; mentre --to elenca le note inviate a <utente>.\n" " " #: plugin.py:363 #, docstring msgid "" "takes no arguments\n" "\n" " Retrieves your next unread note, if any.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Recupera la successiva nota non letta, se presente.\n" " " ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Note/plugin.py����������������������������������������������������������0000644�0001750�0001750�00000040101�13634634532�017551� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Brett Kelly # Copyright (c) 2010, James McCoy # 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 re import time import operator import supybot.dbi as dbi import supybot.log as log import supybot.conf as conf import supybot.utils as utils import supybot.ircdb as ircdb from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot import commands from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Note') class NoteRecord(dbi.Record): __fields__ = [ 'frm', 'to', 'at', 'notified', 'read', 'public', 'text', ] class DbiNoteDB(dbi.DB): Mapping = 'flat' Record = NoteRecord def __init__(self, *args, **kwargs): dbi.DB.__init__(self, *args, **kwargs) self.unRead = {} self.unNotified = {} for record in self: self._addCache(record) def _addCache(self, record): if not record.read: self.unRead.setdefault(record.to, []).append(record.id) if not record.notified: self.unNotified.setdefault(record.to, []).append(record.id) def _removeCache(self, record): if record.notified: try: self.unNotified[record.to].remove(record.id) except (KeyError, ValueError): pass if record.read: try: self.unRead[record.to].remove(record.id) except (KeyError, ValueError): pass def setRead(self, id): n = self.get(id) n.read = True n.notified = True self._removeCache(n) self.set(id, n) def setNotified(self, id): n = self.get(id) n.notified = True self._removeCache(n) self.set(id, n) def getUnnotifiedIds(self, to): return self.unNotified.get(to, []) def getUnreadIds(self, to): return self.unRead.get(to, []) def send(self, frm, to, public, text): n = self.Record(frm=frm, to=to, text=text, at=time.time(), public=public) id = self.add(n) self._addCache(n) return id def unsend(self, id): self.remove(id) for cache in self.unRead, self.unNotified: for (to, ids) in cache.items(): while id in ids: ids.remove(id) NoteDB = plugins.DB('Note', {'flat': DbiNoteDB}) class Note(callbacks.Plugin): """Allows you to send notes to other users.""" def __init__(self, irc): self.__parent= super(Note, self) self.__parent.__init__(irc) self.db = NoteDB() def die(self): self.__parent.die() self.db.close() def doPrivmsg(self, irc, msg): if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg): return self._notify(irc, msg) def doJoin(self, irc, msg): if self.registryValue('notify.onJoin'): repeatedly = self.registryValue('notify.onJoin.repeatedly') self._notify(irc, msg, repeatedly) def _notify(self, irc, msg, repeatedly=False): irc = callbacks.SimpleProxy(irc, msg) try: to = ircdb.users.getUserId(msg.prefix) except KeyError: return ids = self.db.getUnnotifiedIds(to) if len(ids) <= self.registryValue('notify.autoSend'): for id in ids: irc.reply(self._formatNote(self.db.get(id), to), private=True) self.db.setRead(id) return unnotifiedIds = ['#%s' % nid for nid in ids] unnotified = len(unnotifiedIds) if unnotified or repeatedly: unreadIds = ['#%s' % nid for nid in self.db.getUnreadIds(to)] unread = len(unreadIds) s = format('You have %n; %i that I haven\'t told you about ' 'before now. %L %b still unread.', (unread, 'unread', 'note'), unnotified, unreadIds, unread) # Later we'll have a user value for allowing this to be a NOTICE. irc.reply(s, private=True) for nid in unnotifiedIds: id = int(nid[1:]) self.db.setNotified(id) def _getUserId(self, irc, name): if ircdb.users.hasUser(name): return ircdb.users.getUserId(name) else: try: hostmask = irc.state.nickToHostmask(name) return ircdb.users.getUserId(hostmask) except KeyError: return None def send(self, irc, msg, args, user, targets, text): """<recipient>,[<recipient>,[...]] <text> Sends a new note to the user specified. Multiple recipients may be specified by separating their names by commas. """ # Let's get the from user. public = bool(msg.channel) sent = [] for target in targets: id = self.db.send(user.id, target.id, public, text) s = format('note #%i sent to %s', id, target.name) sent.append(s) irc.reply(format('%L.', sent).capitalize()) send = wrap(send, ['user', commalist('otherUser'), 'text']) def reply(self, irc, msg, args, user, id, text): """<id> <text> Sends a note in reply to <id>. """ try: note = self.db.get(id) except dbi.NoRecordError: irc.error('That\'s not a note in my database.', Raise=True) if note.to != user.id: irc.error('You may only reply to notes ' 'that have been sent to you.', Raise=True) self.db.setRead(id) text += ' (in reply to #%s)' % id public = bool(msg.channel) try: target = ircdb.users.getUser(note.frm) except KeyError: irc.error('The user who sent you that note ' 'is no longer in my user database.', Raise=True) id = self.db.send(user.id, note.frm, public, text) irc.reply(format('Note #%i sent to %s.', id, target.name)) reply = wrap(reply, ['user', ('id', 'note'), 'text']) def unsend(self, irc, msg, args, user, id): """<id> Unsends the note with the id given. You must be the author of the note, and it must be unread. """ try: note = self.db.get(id) except dbi.NoRecordError: irc.errorInvalid('note id') if note.frm == user.id: if not note.read: self.db.unsend(id) irc.replySuccess() else: irc.error('That note has been read already.') else: irc.error('That note wasn\'t sent by you.') unsend = wrap(unsend, ['user', ('id', 'note')]) def _formatNote(self, note, to): elapsed = utils.timeElapsed(time.time() - note.at) if note.to == to: author = plugins.getUserName(note.frm) return format('#%i: %s (Sent by %s %s ago)', note.id, note.text, author, elapsed) else: assert note.frm == to, 'Odd, userid isn\'t frm either.' recipient = plugins.getUserName(note.to) return format('#%i: %s (Sent to %s %s ago)', note.id, note.text, recipient, elapsed) def note(self, irc, msg, args, user, id): """<id> Retrieves a single note by its unique note id. Use the 'note list' command to see what unread notes you have. """ try: note = self.db.get(id) except dbi.NoRecordError: irc.errorInvalid('note id') if user.id != note.frm and user.id != note.to: s = 'You may only retrieve notes you\'ve sent or received.' irc.error(s) return newnote = self._formatNote(note, user.id) irc.reply(newnote, private=(not note.public)) self.db.setRead(id) note = wrap(note, ['user', ('id', 'note')]) def _formatNoteId(self, irc, msg, note, sent=False): if note.public or not msg.channel: if sent: sender = plugins.getUserName(note.to) return format('#%i to %s', note.id, sender) else: sender = plugins.getUserName(note.frm) return format('#%i from %s', note.id, sender) else: return format('#%i (private)', note.id) def search(self, irc, msg, args, user, optlist, glob): """[--{regexp} <value>] [--sent] [<glob>] Searches your received notes for ones matching <glob>. If --regexp is given, its associated value is taken as a regexp and matched against the notes. If --sent is specified, only search sent notes. """ criteria = [] def to(note): return note.to == user.id def frm(note): return note.frm == user.id own = to for (option, arg) in optlist: if option == 'regexp': criteria.append(lambda s: regexp_wrapper(s, reobj=arg, timeout=0.1, plugin_name=self.name(), fcn_name='search')) elif option == 'sent': own = frm if glob: glob = utils.python.glob2re(glob) criteria.append(re.compile(glob).search) def match(note): for p in criteria: if not p(note.text): return False return True notes = list(self.db.select(lambda n: match(n) and own(n))) if not notes: irc.reply('No matching notes were found.') else: utils.sortBy(operator.attrgetter('id'), notes) ids = [self._formatNoteId(irc, msg, note) for note in notes] ids = self._condense(ids) irc.reply(format('%L', ids)) search = wrap(search, ['user', getopts({'regexp': ('regexpMatcher', True), 'sent': ''}), additional('glob')]) def list(self, irc, msg, args, user, optlist): """[--{old,sent}] [--{from,to} <user>] Retrieves the ids of all your unread notes. If --old is given, list read notes. If --sent is given, list notes that you have sent. If --from is specified, only lists notes sent to you from <user>. If --to is specified, only lists notes sent by you to <user>. """ (sender, receiver, old, sent) = (None, None, False, False) for (option, arg) in optlist: if option == 'old': old = True if option == 'sent': sent = True if option == 'from': sender = arg if option == 'to': receiver = arg sent = True if old: return self._oldnotes(irc, msg, sender) if sent: return self._sentnotes(irc, msg, receiver) def p(note): return not note.read and note.to == user.id if sender: originalP = p def p(note): return originalP(note) and note.frm == sender.id notes = list(self.db.select(p)) if not notes: irc.reply('You have no unread notes.') else: utils.sortBy(operator.attrgetter('id'), notes) ids = [self._formatNoteId(irc, msg, note) for note in notes] ids = self._condense(ids) irc.reply(format('%L.', ids)) list = wrap(list, ['user', getopts({'old': '', 'sent': '', 'from': 'otherUser', 'to': 'otherUser'})]) def next(self, irc, msg, args, user): """takes no arguments Retrieves your next unread note, if any. """ notes = self.db.getUnreadIds(user.id) if not notes: irc.reply('You have no unread notes.') else: found = False for id in notes: try: note = self.db.get(id) except KeyError: continue found = True break if not found: irc.reply('You have no unread notes.') else: irc.reply(self._formatNote(note, user.id), private=(not note.public)) self.db.setRead(note.id) next = wrap(next, ['user']) def _condense(self, notes): temp = {} for note in notes: note = note.split(' ', 1) if note[1] in temp: temp[note[1]].append(note[0]) else: temp[note[1]] = [note[0]] notes = [] for (k,v) in temp.items(): if '(private)' in k: k = k.replace('(private)', format('%b private', len(v))) notes.append(format('%L %s', v, k)) return notes def _sentnotes(self, irc, msg, receiver): try: user = ircdb.users.getUser(msg.prefix) except KeyError: irc.errorNotRegistered() return def p(note): return note.frm == user.id if receiver: originalP = p def p(note): return originalP(note) and note.to == receiver.id notes = list(self.db.select(p)) if not notes: irc.error('I couldn\'t find any sent notes for your user.') else: utils.sortBy(operator.attrgetter('id'), notes) notes.reverse() # Most recently sent first. ids = [self._formatNoteId(irc, msg, note, sent=True) for note in notes] ids = self._condense(ids) irc.reply(format('%L.', ids)) def _oldnotes(self, irc, msg, sender): try: user = ircdb.users.getUser(msg.prefix) except KeyError: irc.errorNotRegistered() return def p(note): return note.to == user.id and note.read if sender: originalP = p def p(note): return originalP(note) and note.frm == sender.id notes = list(self.db.select(p)) if not notes: irc.reply('I couldn\'t find any matching read notes ' 'for your user.') else: utils.sortBy(operator.attrgetter('id'), notes) notes.reverse() ids = [self._formatNoteId(irc, msg, note) for note in notes] ids = self._condense(ids) irc.reply(format('%L.', ids)) Class = Note # vim: shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Note/test.py������������������������������������������������������������0000644�0001750�0001750�00000007634�13634634532�017250� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003, Brett Kelly # 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 NoteTestCase(PluginTestCase): plugins = ('Note', 'Misc', 'User') config = {'supybot.reply.whenNotCommand': False} def setUp(self): PluginTestCase.setUp(self) # setup a user self.prefix = 'foo!bar@baz' self.assertNotError('register inkedmn bar') self.assertNotError('hostmask add inkedmn test2!bar@baz') def testSendnote(self): self.assertRegexp('note send inkedmn test', '#1') # have to getMsg(' ') after each Note.send to absorb supybot's # automatic "You have an unread note" message _ = self.getMsg(' ') self.assertError('note send alsdkjfasldk foo') self.assertNotError('note send inkedmn test2') _ = self.getMsg(' ') # verify that sending a note to a user via their nick instead of their # ircdb user name works self.prefix = 'test2!bar@baz' self.assertNotError('note send test2 foo') _ = self.getMsg(' ') def testNote(self): self.assertNotError('note send inkedmn test') _ = self.getMsg(' ') self.assertRegexp('note 1', 'test') self.assertError('note blah') def testList(self): self.assertResponse('note list', 'You have no unread notes.') self.assertNotError('note send inkedmn testing') _ = self.getMsg(' ') self.assertNotError('note send inkedmn 1,2,3') _ = self.getMsg(' ') self.assertRegexp('note list --sent', r'#2.*#1') self.assertRegexp('note list --sent --to inkedmn', r'#2.*#1') self.assertRegexp('note list', r'#1.*#2') self.assertRegexp('note 1', 'testing') self.assertRegexp('note list --old', '#1 from inkedmn') self.assertRegexp('note list --old --from inkedmn','#1 from inkedmn') def testSearch(self): self.assertNotError('note send inkedmn testing') _ = self.getMsg(' ') self.assertNotError('note send inkedmn 1,2,3') _ = self.getMsg(' ') self.assertRegexp('note search test', r'#1') self.assertRegexp('note search --regexp m/1,2/', r'#2') self.assertRegexp('note search --sent test', r'#1') def testNext(self): self.assertNotError('note send inkedmn testing') _ = self.getMsg(' ') self.assertRegexp('note next', 'testing') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Owner/������������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016100� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Owner/__init__.py�������������������������������������������������������0000644�0001750�0001750�00000004517�13634634532�020212� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Provides commands useful to the owner of the bot; the commands here require their caller to have the 'owner' capability. This plugin is loaded by default. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload reload(plugin) # In case we're being reloaded. if world.testing: from . import test Class = plugin.Class configure = config.configure ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Owner/config.py���������������������������������������������������������0000644�0001750�0001750�00000006210�13634634532�017710� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Owner') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Owner', True) Owner = conf.registerPlugin('Owner', True) conf.registerGlobalValue(Owner, 'public', registry.Boolean(True, """Determines whether this plugin is publicly visible.""")) conf.registerGlobalValue(Owner, 'announceFormat', registry.String('Announcement from my owner ($owner): $text', """Determines the format of messages sent by the 'announce' command. $owner may be used for the username of the owner calling this command, and $text for the announcement being made.""")) conf.registerGlobalValue(Owner, 'quitMsg', registry.String('Limnoria $version', """Determines what quit message will be used by default. If the quit command is called without a quit message, this will be used. If this value is empty, the nick of the person giving the quit command will be used. The standard substitutions ($version, $nick, etc.) are all handled appropriately.""")) conf.registerGroup(conf.supybot.commands, 'renames', orderAlphabetically=True) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Owner/locales/����������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017522� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Owner/locales/de.po�����������������������������������������������������0000644�0001750�0001750�00000017050�13634634532�020447� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2012-03-11 20:58+UTC\n" "PO-Revision-Date: 2012-04-27 15:38+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: German <fbesser@gmail.com>\n" "Language: de\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: de\n" #: plugin.py:273 msgid "" "<text>\n" "\n" " Logs <text> to the global Supybot log at critical priority. Useful for\n" " marking logfiles for later searching.\n" " " msgstr "" "<Text>\n" "\n" "Schreibt <Text> in die globale Supybot Logdatei mit kritischer Priorität. Nützlich um Logdateien zu markieren um sie später zu durchsuchen." #: plugin.py:283 msgid "" "<text>\n" "\n" " Sends <text> to all channels the bot is currently on and not\n" " lobotomized in.\n" " " msgstr "" "<Text>\n" "\n" "Sendet <Text> an alle Kanäle in denen der Bot momentan ist und nicht hirnamputiert ist." #: plugin.py:298 msgid "" "[--remove] <command> [<plugin>]\n" "\n" " Sets the default plugin for <command> to <plugin>. If --remove is\n" " given, removes the current default plugin for <command>. If no plugin\n" " is given, returns the current default plugin set for <command>. See\n" " also, supybot.commands.defaultPlugins.importantPlugins.\n" " " msgstr "" "[--remove] <Befehl> [<Plugin>]\n" "\n" "Setzt das Standardplugin für <Befehl> auf <Plugin>. Falls --remove angegeben wird, wird das momentane Standardplugin für <Befehl> entfernt. Falls kein Plugin angegeben wird, wird das momentane Standardplugin für <Befehl> ausgegeben. Schau auch nach supybot.commands.defaultPlugins.importantPlugins. " #: plugin.py:336 msgid "" "<string to be sent to the server>\n" "\n" " Sends the raw string given to the server.\n" " " msgstr "" "<Zeichenkette die zum Server gesendet wird>\n" "\n" "Sendet die Zeichenketten zum angegeben Server." #: plugin.py:350 msgid "" "[<text>]\n" "\n" " Exits the bot with the QUIT message <text>. If <text> is not given,\n" " the default quit message (supybot.plugins.Owner.quitMsg) will be used.\n" " If there is no default quitMsg set, your nick will be used.\n" " " msgstr "" "[<Text>]\n" "\n" "Beendet den Bot mit der Nachricht <Text>. Falls <Text> nicht angegeben wird, wird die Standard Endnachricht (supybot.plugins.Owner.quitMsg) benutzt. Wenn es keine standard quitMsg gesetzt ist, wird dein Nickname benutzt." #: plugin.py:366 msgid "" "takes no arguments\n" "\n" " Runs all the periodic flushers in world.flushers. This includes\n" " flushing all logs and all configuration changes to disk.\n" " " msgstr "" "hat keine Argumente\n" "\n" "Führt alle periodischen Flusher in world.flushers aus. Das beinhaltet alle Log und Konfigurationsänderung auf die Festplatte zu schreiben." #: plugin.py:376 msgid "" "[<level>]\n" "\n" " Runs the standard upkeep stuff (flushes and gc.collects()). If given\n" " a level, runs that level of upkeep (currently, the only supported\n" " level is \"high\", which causes the bot to flush a lot of caches as well\n" " as do normal upkeep stuff).\n" " " msgstr "" "[<Stufe>]\n" "\n" "Führt standard Instandhaltungen durch (flushes und gc.collects()). Falls eine Stufe angegeben wird, wird diese Stufe der Instandhaltungausgeführt (momentan ist die einzig unsterstütze Stufe \"high\", welche den Bot dazu veranlässt viele Caches aufzuräumen und normale Instandhaltung zu bestreiben)." #: plugin.py:415 msgid "" "[--deprecated] <plugin>\n" "\n" " Loads the plugin <plugin> from any of the directories in\n" " conf.supybot.directories.plugins; usually this includes the main\n" " installed directory and 'plugins' in the current directory.\n" " --deprecated is necessary if you wish to load deprecated plugins.\n" " " msgstr "" "[--deprecated] <Plugin>\n" "\n" "Läd das Plugin <Plugin> von irgendeinem Verzeichnis das in conf.supybot.directories.plugins ist; normalerweise enthält diese das Hauptinstallationsverzeichnis und 'plugins' im momentanen Verzeichnis. --deprecated ist nötig falls du veraltete Plugins laden möchtest." #: plugin.py:450 msgid "" "<plugin>\n" "\n" " Unloads and subsequently reloads the plugin by name; use the 'list'\n" " command to see a list of the currently loaded plugins.\n" " " msgstr "" "<Plugin>\n" "\n" "Entläd und läd das das Plugin neu; benutze den Befehl 'list' um dir die momentan geladenen Plugins anzuzeigen zu lassen." #: plugin.py:479 #, fuzzy msgid "" "<plugin>\n" "\n" " Unloads the callback by name; use the 'list' command to see a list\n" " of the currently loaded plugins. Obviously, the Owner plugin can't\n" " be unloaded.\n" " " msgstr "" "<Plugin>\n" "\n" "Entläd den Callback nach Name; benutze den 'list' Befehl um zu sehen welche Callbacks momentan geladen sind. Es ist wohl klar, dass das Owner Plugin nicht entladen werden kann." #: plugin.py:503 msgid "" "{add|remove} <capability>\n" "\n" " Adds or removes (according to the first argument) <capability> from the\n" " default capabilities given to users (the configuration variable\n" " supybot.capabilities stores these).\n" " " msgstr "" "{add|remove} <Fähigkeit>\n" "\n" "Hinzufügen oder entfernen (abhängig vom ersten Argument) der <Fähigkeit> von den Standardfähigkeiten die den Benutzern gegeben werden (die Konfigurationsvariable supybot.capabilities speichert diese)." #: plugin.py:528 msgid "" "[<plugin>] <command>\n" "\n" " Disables the command <command> for all users (including the owners).\n" " If <plugin> is given, only disables the <command> from <plugin>. If\n" " you want to disable a command for most users but not for yourself, set\n" " a default capability of -plugin.command or -command (if you want to\n" " disable the command in all plugins).\n" " " msgstr "" "[<Plugin>] <Befehl>\n" "\n" "Deaktiviert den Befehl <Befehl> für alle Nutzer (auch den Besitzer). Falls <Plugin angegen wird nur der <Befehl> von <Plugin> deaktiviert. Dieser Befehl ist das gegenteil von disable.Falls du den Befehl nur für die meisten Nutzer deaktiveren willst setze eine standard Fähigkeit -plugin.befehl oder -befehl (falls du den Befehl von allen Plugins deaktivieren willst)." #: plugin.py:555 msgid "" "[<plugin>] <command>\n" "\n" " Enables the command <command> for all users. If <plugin>\n" " if given, only enables the <command> from <plugin>. This command is\n" " the inverse of disable.\n" " " msgstr "" "[<Plugin>] <Befehl>\n" "\n" "Aktiviert den Befehl <Befehl> für alle Nutzer. Falls <Plugin angegen wird nur der <Befehl> von <Plugin> aktiviert. Dieser Befehl ist das gegenteil von disable." #: plugin.py:574 msgid "" "<plugin> <command> <new name>\n" "\n" " Renames <command> in <plugin> to the <new name>.\n" " " msgstr "" "<Plugin> <Befehl> <Neuer Name>\n" "\n" "Benennt <Befehl> von <Plugin> in <Neuer Name> um." #: plugin.py:591 msgid "" "<plugin>\n" "\n" " Removes all renames in <plugin>. The plugin will be reloaded after\n" " this command is run.\n" " " msgstr "" "<Plugin>\n" "\n" "Entfernt alle Namensänderungen in <Plugin>. Das Plugin wird neu geladen nach diesem Befehl." #: plugin.py:604 msgid "" "takes no argument\n" "\n" " Reloads the locale of the bot." msgstr "" "hat kein Argument\n" "\n" "Läd die Lokalisations des Bots neu." ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Owner/locales/fi.po�����������������������������������������������������0000755�0001750�0001750�00000011114�13634634532�020453� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Owner plugin in Limnoria. # Copyright (C) 2011 Limnoria # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # WARNING: Translations of Owner plugin will not work, because it's loaded directly by supybot # and translations are more like a hack to it. # msgid "" msgstr "" "Project-Id-Version: \n" "POT-Creation-Date: 2012-03-11 20:58+UTC\n" "PO-Revision-Date: 2012-03-15 08:28+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: \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" #: plugin.py:273 msgid "" "<text>\n" "\n" " Logs <text> to the global Supybot log at critical priority. Useful for\n" " marking logfiles for later searching.\n" " " msgstr "" #: plugin.py:283 msgid "" "<text>\n" "\n" " Sends <text> to all channels the bot is currently on and not\n" " lobotomized in.\n" " " msgstr "" #: plugin.py:298 msgid "" "[--remove] <command> [<plugin>]\n" "\n" " Sets the default plugin for <command> to <plugin>. If --remove is\n" " given, removes the current default plugin for <command>. If no plugin\n" " is given, returns the current default plugin set for <command>. See\n" " also, supybot.commands.defaultPlugins.importantPlugins.\n" " " msgstr "" #: plugin.py:336 msgid "" "<string to be sent to the server>\n" "\n" " Sends the raw string given to the server.\n" " " msgstr "" #: plugin.py:350 msgid "" "[<text>]\n" "\n" " Exits the bot with the QUIT message <text>. If <text> is not given,\n" " the default quit message (supybot.plugins.Owner.quitMsg) will be used.\n" " If there is no default quitMsg set, your nick will be used.\n" " " msgstr "" #: plugin.py:366 msgid "" "takes no arguments\n" "\n" " Runs all the periodic flushers in world.flushers. This includes\n" " flushing all logs and all configuration changes to disk.\n" " " msgstr "" #: plugin.py:376 msgid "" "[<level>]\n" "\n" " Runs the standard upkeep stuff (flushes and gc.collects()). If given\n" " a level, runs that level of upkeep (currently, the only supported\n" " level is \"high\", which causes the bot to flush a lot of caches as well\n" " as do normal upkeep stuff).\n" " " msgstr "" #: plugin.py:415 msgid "" "[--deprecated] <plugin>\n" "\n" " Loads the plugin <plugin> from any of the directories in\n" " conf.supybot.directories.plugins; usually this includes the main\n" " installed directory and 'plugins' in the current directory.\n" " --deprecated is necessary if you wish to load deprecated plugins.\n" " " msgstr "" #: plugin.py:450 msgid "" "<plugin>\n" "\n" " Unloads and subsequently reloads the plugin by name; use the 'list'\n" " command to see a list of the currently loaded plugins.\n" " " msgstr "" #: plugin.py:479 msgid "" "<plugin>\n" "\n" " Unloads the callback by name; use the 'list' command to see a list\n" " of the currently loaded plugins. Obviously, the Owner plugin can't\n" " be unloaded.\n" " " msgstr "" #: plugin.py:503 msgid "" "{add|remove} <capability>\n" "\n" " Adds or removes (according to the first argument) <capability> from the\n" " default capabilities given to users (the configuration variable\n" " supybot.capabilities stores these).\n" " " msgstr "" #: plugin.py:528 msgid "" "[<plugin>] <command>\n" "\n" " Disables the command <command> for all users (including the owners).\n" " If <plugin> is given, only disables the <command> from <plugin>. If\n" " you want to disable a command for most users but not for yourself, set\n" " a default capability of -plugin.command or -command (if you want to\n" " disable the command in all plugins).\n" " " msgstr "" #: plugin.py:555 msgid "" "[<plugin>] <command>\n" "\n" " Enables the command <command> for all users. If <plugin>\n" " if given, only enables the <command> from <plugin>. This command is\n" " the inverse of disable.\n" " " msgstr "" #: plugin.py:574 msgid "" "<plugin> <command> <new name>\n" "\n" " Renames <command> in <plugin> to the <new name>.\n" " " msgstr "" #: plugin.py:591 msgid "" "<plugin>\n" "\n" " Removes all renames in <plugin>. The plugin will be reloaded after\n" " this command is run.\n" " " msgstr "" #: plugin.py:604 msgid "" "takes no argument\n" "\n" " Reloads the locale of the bot." msgstr "" ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Owner/locales/fr.po�����������������������������������������������������0000644�0001750�0001750�00000016572�13634634532�020476� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2012-03-11 20:58+UTC\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\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:273 msgid "" "<text>\n" "\n" " Logs <text> to the global Supybot log at critical priority. Useful for\n" " marking logfiles for later searching.\n" " " msgstr "" "<texte>\n" "\n" "Log le <texte> aux logs globaux de Supybot avec une priorité critique. Utile pour marquer les fichiers de logs pour des recherches ultérieures." #: plugin.py:283 msgid "" "<text>\n" "\n" " Sends <text> to all channels the bot is currently on and not\n" " lobotomized in.\n" " " msgstr "" "<texte>\n" "\n" "Envoie le <texte> à tous les canaux sur lesquels le bot est sans être lobotomisé." #: plugin.py:298 msgid "" "[--remove] <command> [<plugin>]\n" "\n" " Sets the default plugin for <command> to <plugin>. If --remove is\n" " given, removes the current default plugin for <command>. If no plugin\n" " is given, returns the current default plugin set for <command>. See\n" " also, supybot.commands.defaultPlugins.importantPlugins.\n" " " msgstr "" "[--remove] <commande> [<plugin>]\n" "\n" "Défini le <plugin> par défaut de la <commande>. Si --remove est donné, le commande par défaut actuelle est supprimée. Si aucun plugin n'est donné, retourne le plugin par défaut actuel. Voyez supybot.commands.defaultPlugins.importantPlugins pour plus d'informations." #: plugin.py:336 msgid "" "<string to be sent to the server>\n" "\n" " Sends the raw string given to the server.\n" " " msgstr "" "<chaîne à envoyer au serveur>\n" "\n" "Envoie la chaîne directement au serveur." #: plugin.py:350 msgid "" "[<text>]\n" "\n" " Exits the bot with the QUIT message <text>. If <text> is not given,\n" " the default quit message (supybot.plugins.Owner.quitMsg) will be used.\n" " If there is no default quitMsg set, your nick will be used.\n" " " msgstr "" "[<texte>]\n" "\n" "Fait quitter le bot avec le message de quit <texte>. Si le <texte> n'est pas donné, le message de quit par défaut (supybot.plugins.Owner.quitMsg) est utilisé. Si quitMsg est vide, votre nick sera utilisé." #: plugin.py:366 msgid "" "takes no arguments\n" "\n" " Runs all the periodic flushers in world.flushers. This includes\n" " flushing all logs and all configuration changes to disk.\n" " " msgstr "" "ne prend pas d'argument.\n" "\n" "Lance tous les 'flushers' périodiques dans world.flushers. Ceci inclue l'écriture des logs et de la configuration sur le disque." #: plugin.py:376 msgid "" "[<level>]\n" "\n" " Runs the standard upkeep stuff (flushes and gc.collects()). If given\n" " a level, runs that level of upkeep (currently, the only supported\n" " level is \"high\", which causes the bot to flush a lot of caches as well\n" " as do normal upkeep stuff).\n" " " msgstr "" "[<niveau>]\n" "\n" "Lance le 'upkeep' standard (flushes et gc.collect()). Si un niveau est donné, lance le niveau d'upkeep (actuellement, le seul niveau supporté est \"high\", ce qui fait que le bot vide beaucoup plus de cache que ce qu'il fait normalement)." #: plugin.py:415 msgid "" "[--deprecated] <plugin>\n" "\n" " Loads the plugin <plugin> from any of the directories in\n" " conf.supybot.directories.plugins; usually this includes the main\n" " installed directory and 'plugins' in the current directory.\n" " --deprecated is necessary if you wish to load deprecated plugins.\n" " " msgstr "" "[--deprecated] <plugin>\n" "\n" "Charge le <plugin> de n'importe lequel des répertoires dans conf.supybot.directories.plugins, ce qui inclue généralement le répertoire principal de l'installation, et 'plugins' dans le répertoire courrant. Utilisez --deprected si nécessaire pour charger des plugins dépréciés." #: plugin.py:450 msgid "" "<plugin>\n" "\n" " Unloads and subsequently reloads the plugin by name; use the 'list'\n" " command to see a list of the currently loaded plugins.\n" " " msgstr "" "<plugin>\n" "\n" "Décharger et recharge immédiatement le <plugin> ; utilisez la commande 'list' pour lister les plugins actuellement chargés." #: plugin.py:479 msgid "" "<plugin>\n" "\n" " Unloads the callback by name; use the 'list' command to see a list\n" " of the currently loaded plugins. Obviously, the Owner plugin can't\n" " be unloaded.\n" " " msgstr "" "<plugin>\n" "\n" "Décharge le <plugin> ; utilisez la commande 'list' pour lister les plugins actuellement chargés. Évidemment, le plugin Owner ne peut être déchargé." #: plugin.py:503 msgid "" "{add|remove} <capability>\n" "\n" " Adds or removes (according to the first argument) <capability> from the\n" " default capabilities given to users (the configuration variable\n" " supybot.capabilities stores these).\n" " " msgstr "" "{add|remove} <capacité>\n" "\n" "Ajoute ou supprime (en fonction du premier argument) la <capacité> à la liste des capacités par défaut données aux utilisateurs (stockée dans la variable de configuration supybot.capabilities)." #: plugin.py:528 msgid "" "[<plugin>] <command>\n" "\n" " Disables the command <command> for all users (including the owners).\n" " If <plugin> is given, only disables the <command> from <plugin>. If\n" " you want to disable a command for most users but not for yourself, set\n" " a default capability of -plugin.command or -command (if you want to\n" " disable the command in all plugins).\n" " " msgstr "" "[<plugin>] <commande>\n" "\n" "Désactive la <commande> pour tous les utilisateurs (y compris le propriétaire. Si le <plugin> est donné, ne désactive la <commande> que pour le <plugin>. Si vous voulez désactiver la commande pour tous les utilisateurs sauf vous-même, définissez la capacité par défaut -plugin.command ou -command." #: plugin.py:555 msgid "" "[<plugin>] <command>\n" "\n" " Enables the command <command> for all users. If <plugin>\n" " if given, only enables the <command> from <plugin>. This command is\n" " the inverse of disable.\n" " " msgstr "" "[<plugin>] <commande>\n" "\n" "Active la <commande> pour tous les utilisateurs. Si le <plugin> est donné, ne réactive la <commande> que pour le <plugin>. Cette commande est l'inverse de disable." #: plugin.py:574 msgid "" "<plugin> <command> <new name>\n" "\n" " Renames <command> in <plugin> to the <new name>.\n" " " msgstr "" "<plugin> <commande> <nouveau nom>\n" "\n" "Renomme la <commande> du <plugin> par un <nouveau nom>." #: plugin.py:591 msgid "" "<plugin>\n" "\n" " Removes all renames in <plugin>. The plugin will be reloaded after\n" " this command is run.\n" " " msgstr "" "<plugin>\n" "\n" "Supprime tous les renommages du <plugin>. Ce plugin sera rechargé après que cette commande ait été lancée." #: plugin.py:604 msgid "" "takes no argument\n" "\n" " Reloads the locale of the bot." msgstr "" "ne prend pas d'argument\n" "\n" "Recharge la locale du bot." ��������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Owner/locales/hu.po�����������������������������������������������������0000644�0001750�0001750�00000017540�13634634532�020477� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # msgid "" msgstr "" "Project-Id-Version: Limnoria Owner\n" "POT-Creation-Date: 2012-03-11 20:58+UTC\n" "PO-Revision-Date: 2012-04-27 15:13+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: \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" #: plugin.py:273 msgid "" "<text>\n" "\n" " Logs <text> to the global Supybot log at critical priority. Useful for\n" " marking logfiles for later searching.\n" " " msgstr "" "<szöveg>\n" "\n" "Naplózza <szöveg>-et a globális Supybot naplóba kritikus prioritással. Hasznos a naplófájlok megjelölésére későbbi kereséshez." #: plugin.py:283 msgid "" "<text>\n" "\n" " Sends <text> to all channels the bot is currently on and not\n" " lobotomized in.\n" " " msgstr "" "<text>\n" "\n" "Elküldi <szöveg>-et minden csatornára, ahol a bot van és nincs némítva." #: plugin.py:298 msgid "" "[--remove] <command> [<plugin>]\n" "\n" " Sets the default plugin for <command> to <plugin>. If --remove is\n" " given, removes the current default plugin for <command>. If no plugin\n" " is given, returns the current default plugin set for <command>. See\n" " also, supybot.commands.defaultPlugins.importantPlugins.\n" " " msgstr "" "[--remove] <parancs> [<bővítmény>]\n" "<parancs> alapértelmezett bővítményét <bővítmény>-re állítja. Ha --remove meg van adva, eltávolítja <parancs> jelenlegi alapértelmezett bővítményét. Ha nincs bővítmény megadva, kiírja <parancs> jelenlegi alapértelmezett bővítményét. Lásd még, supybot.commands.defaultPlugins.importantPlugins." #: plugin.py:336 msgid "" "<string to be sent to the server>\n" "\n" " Sends the raw string given to the server.\n" " " msgstr "" "<a szervernek elküldendő karakterlánc>\n" "\n" "Elküldi a megadott nyers karakterláncot a szervernek." #: plugin.py:350 msgid "" "[<text>]\n" "\n" " Exits the bot with the QUIT message <text>. If <text> is not given,\n" " the default quit message (supybot.plugins.Owner.quitMsg) will be used.\n" " If there is no default quitMsg set, your nick will be used.\n" " " msgstr "" "[<szöveg>]\n" "\n" "Kilép a botból <szöveg> kilépési üzenettel. Ha <szöveg> nincs meg adva, az alapértelmezett kilépési üzenet (supybot.plugins.Owner.quitMsg) lesz használva. Ha nincs alapértelmezett kilépési üzenet beállítva, a neved lesz használva." #: plugin.py:366 msgid "" "takes no arguments\n" "\n" " Runs all the periodic flushers in world.flushers. This includes\n" " flushing all logs and all configuration changes to disk.\n" " " msgstr "" "paraméter nélküli\n" "\n" "Futtatja az összes időszakos öblítőket world.flushers-ban. Ez magában foglalja az összes naplófájl és konfigurációs fájl mentését a lemezre." #: plugin.py:376 msgid "" "[<level>]\n" "\n" " Runs the standard upkeep stuff (flushes and gc.collects()). If given\n" " a level, runs that level of upkeep (currently, the only supported\n" " level is \"high\", which causes the bot to flush a lot of caches as well\n" " as do normal upkeep stuff).\n" " " msgstr "" "[<szint>]\n" "\n" "Futtatja a szabványos karbantartási dolgokat (öblítés és gc.collects()). Ha meg van adva egy szint, a megadott szintű karbantartást futtatja (jelenleg, az egyetlen témogatott szint a \"high\", amellyel a bot öblít nagyon sok gyorsítótárat és megcsinálja a normális karbantartási dolgokat." #: plugin.py:415 msgid "" "[--deprecated] <plugin>\n" "\n" " Loads the plugin <plugin> from any of the directories in\n" " conf.supybot.directories.plugins; usually this includes the main\n" " installed directory and 'plugins' in the current directory.\n" " --deprecated is necessary if you wish to load deprecated plugins.\n" " " msgstr "" "[--deprecated] <bővítmény>\n" "\n" "Betölti a <bővítmény> bővítményt a supybot.directories.plugins könyvtárai közül bármelyikből; általában ebbe beletarzozik a fő telepítési könyvtár és egy 'plugins' a jelenlegi könyvtárban. --deprecated akkor szükséges, ha elavult bővítményeket szeretnél betölteni." #: plugin.py:450 msgid "" "<plugin>\n" "\n" " Unloads and subsequently reloads the plugin by name; use the 'list'\n" " command to see a list of the currently loaded plugins.\n" " " msgstr "" "<bővítmény>\n" "\n" "Kirakja és aztán újratölti a megadott bővítményt név szerint; használd a 'list' parancsot, hogy lásd a jelenleg betöltött bővítményeket." #: plugin.py:479 #, fuzzy msgid "" "<plugin>\n" "\n" " Unloads the callback by name; use the 'list' command to see a list\n" " of the currently loaded plugins. Obviously, the Owner plugin can't\n" " be unloaded.\n" " " msgstr "" "<bővítmény>\n" "\n" "Kirakja a megadott bővítményt név szerint; használd a 'list' parancsot, hogy lásd a jelenleg betöltött bővítményeket. Nyilvánvalóan az Owner bővítményt nem lehet kirakni." #: plugin.py:503 msgid "" "{add|remove} <capability>\n" "\n" " Adds or removes (according to the first argument) <capability> from the\n" " default capabilities given to users (the configuration variable\n" " supybot.capabilities stores these).\n" " " msgstr "" "{add|remove} <képesség>\n" "\n" "Hozzáadja vagy eltávolítja (az első paraméter szerint) <képesség>-et a felhasználóknak adott alapértelmezett képességekhez/-ből (a supybot.capabilities konfigurációs változó tárolja ezeket)." #: plugin.py:528 msgid "" "[<plugin>] <command>\n" "\n" " Disables the command <command> for all users (including the owners).\n" " If <plugin> is given, only disables the <command> from <plugin>. If\n" " you want to disable a command for most users but not for yourself, set\n" " a default capability of -plugin.command or -command (if you want to\n" " disable the command in all plugins).\n" " " msgstr "" "[<bővítmény>] <parancs>\n" "\n" "Letiltja <parancs> parancsot minden felhasználónak (a tulajdonosokat is beleértve). Ha <bővítmény> meg van adva, csak <bővítmény>-ben tiltja le <parancs>-ot. Ha le szeretnél tiltani egy parancsot a legtöbb felhasználónak de nem magadnak, állíts be egy -bővítmény.parancs vagy -parancs alapértelmezett képességet (ha minden bővítményben le szeretnéd tiltani a parancsot)." #: plugin.py:555 msgid "" "[<plugin>] <command>\n" "\n" " Enables the command <command> for all users. If <plugin>\n" " if given, only enables the <command> from <plugin>. This command is\n" " the inverse of disable.\n" " " msgstr "" "[<bővítmény>] <parancs>\n" "\n" "Engedélyezi <parancs> parancsot minden felhasználónak. Ha <bővítmény> meg van adva, csak <bővítmény>-ben engedélyezi <parancs>-ot. Ez a parancs a fordítottja a disable-nek." #: plugin.py:574 msgid "" "<plugin> <command> <new name>\n" "\n" " Renames <command> in <plugin> to the <new name>.\n" " " msgstr "" "<bővítmény> <parancs> <új név>\n" "\n" "Átnevezi <parancs>-ot <bővítmény>-ben az <új név>-re." #: plugin.py:591 msgid "" "<plugin>\n" "\n" " Removes all renames in <plugin>. The plugin will be reloaded after\n" " this command is run.\n" " " msgstr "" "<bővítmény>\n" "\n" "Eltávolítja az összes átnevezést <bővítmény>-ben. A bővítmény újra lesz töltve miután ez a parancs lefutott." #: plugin.py:604 msgid "" "takes no argument\n" "\n" " Reloads the locale of the bot." msgstr "" "paraméter nélküli\n" "\n" "Újratölti a bot nyelvét." ����������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Owner/locales/it.po�����������������������������������������������������0000644�0001750�0001750�00000017542�13634634532�020501� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2012-03-15 23:59+0100\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugin.py:273 #, docstring msgid "" "<text>\n" "\n" " Logs <text> to the global Supybot log at critical priority. Useful for\n" " marking logfiles for later searching.\n" " " msgstr "" "<testo>\n" "\n" " Registra <testo> nel log globale di Supybot con priorità critica.\n" " Utile per contrassegnare i file di log per ricerche successive.\n" " " #: plugin.py:283 #, docstring msgid "" "<text>\n" "\n" " Sends <text> to all channels the bot is currently on and not\n" " lobotomized in.\n" " " msgstr "" "<testo>\n" "\n" " Invia <testo> a tutti i canali in cui il bot è attualmente presente e non è lobotomizzato.\n" " " #: plugin.py:298 #, docstring msgid "" "[--remove] <command> [<plugin>]\n" "\n" " Sets the default plugin for <command> to <plugin>. If --remove is\n" " given, removes the current default plugin for <command>. If no plugin\n" " is given, returns the current default plugin set for <command>. See\n" " also, supybot.commands.defaultPlugins.importantPlugins.\n" " " msgstr "" "[--remove] <comando> [<plugin>]\n" "\n" " Imposta il plugin predefinito per <comando>. Se --remove è specificato, rimuove\n" " l'attuale plugin per <comando>. Se non viene fornito alcun plugin, riporta quello\n" " impostato per <comando>. Vedi anche supybot.commands.defaultPlugins.importantPlugins.\n" " " #: plugin.py:336 #, docstring msgid "" "<string to be sent to the server>\n" "\n" " Sends the raw string given to the server.\n" " " msgstr "" "<stringa da inviare al server>\n" "\n" " Invia la data stringa direttamente al server.\n" " " #: plugin.py:350 #, docstring msgid "" "[<text>]\n" "\n" " Exits the bot with the QUIT message <text>. If <text> is not given,\n" " the default quit message (supybot.plugins.Owner.quitMsg) will be used.\n" " If there is no default quitMsg set, your nick will be used.\n" " " msgstr "" "[<testo>]\n" "\n" " Fa uscire il bot con un certo messaggio di QUIT. Se <testo> non è specificato verrà\n" " utilizzato quello configurato in supybot.plugins.Owner.quitMsg, altrimenti il tuo nick.\n" " " #: plugin.py:366 #, docstring msgid "" "takes no arguments\n" "\n" " Runs all the periodic flushers in world.flushers. This includes\n" " flushing all logs and all configuration changes to disk.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Esegue tutti i periodici \"flusher\" in world.flushers. Ciò include\n" " il salvataggio su disco di tutti i log e tutte le modifiche di configurazione.\n" " " #: plugin.py:376 #, docstring msgid "" "[<level>]\n" "\n" " Runs the standard upkeep stuff (flushes and gc.collects()). If given\n" " a level, runs that level of upkeep (currently, the only supported\n" " level is \"high\", which causes the bot to flush a lot of caches as well\n" " as do normal upkeep stuff).\n" " " msgstr "" "[<livello>]\n" "\n" " Esegue tutte le operazioni standard di mantenimento (flush e gc.collects()).\n" " Se si specifica un livello, esegue quel livello di mantenimento (al momento\n" " l'unico gestito è \"high\", che svuota la cache molto più del normale).\n" " " #: plugin.py:415 #, docstring msgid "" "[--deprecated] <plugin>\n" "\n" " Loads the plugin <plugin> from any of the directories in\n" " conf.supybot.directories.plugins; usually this includes the main\n" " installed directory and 'plugins' in the current directory.\n" " --deprecated is necessary if you wish to load deprecated plugins.\n" " " msgstr "" "[--deprecated] <plugin>\n" "\n" " Carica <plugin> da qualsiasi directory indicata dalla variabile\n" " conf.supybot.directories.plugins; solitamente questa include quella\n" " principale dell'installazione e \"plugins\" nella directory attuale.\n" " --deprecated è necessario per caricare plugin deprecati.\n" " " #: plugin.py:450 #, docstring msgid "" "<plugin>\n" "\n" " Unloads and subsequently reloads the plugin by name; use the 'list'\n" " command to see a list of the currently loaded plugins.\n" " " msgstr "" "<plugin>\n" "\n" " Ricarica <plugin>; utilizzare il comando \"list\" per ottenere l'elenco di quelli attualmente caricati.\n" " " #: plugin.py:479 #, docstring msgid "" "<plugin>\n" "\n" " Unloads the callback by name; use the 'list' command to see a list\n" " of the currently loaded plugins. Obviously, the Owner plugin can't\n" " be unloaded.\n" " " msgstr "" "<plugin>\n" "\n" " De-carica <plugin>; utilizzare il comando \"list\" per ottenere l'elenco\n" " di quelli attualmente caricati.\n. Il plugin Owner non può essere de-caricato.\n" " " #: plugin.py:503 #, docstring msgid "" "{add|remove} <capability>\n" "\n" " Adds or removes (according to the first argument) <capability> from the\n" " default capabilities given to users (the configuration variable\n" " supybot.capabilities stores these).\n" " " msgstr "" "{add|remove} <capacità>\n" "\n" " Aggiunge o rimuove (in base al primo argomento) <capacità> da quelle predefinite\n" " date agli utenti (salvate nella variabile di configurazione supybot.capabilities).\n" " " #: plugin.py:528 #, docstring msgid "" "[<plugin>] <command>\n" "\n" " Disables the command <command> for all users (including the owners).\n" " If <plugin> is given, only disables the <command> from <plugin>. If\n" " you want to disable a command for most users but not for yourself, set\n" " a default capability of -plugin.command or -command (if you want to\n" " disable the command in all plugins).\n" " " msgstr "" "[<plugin>] <comando>\n" "\n" " Disabilita <comando> per tutti gli utenti (compreso il proprietario).\n" " Se <plugin> è specificato, viene disabilitato solo <comando> da <plugin>.\n" " Se si vuole disabilitare un comando per tutti gli utenti tranne se stessi,\n" " impostare una capacità predefinita -plugin.command o -command (questa\n" " disabilita il comando in tutti i plugin).\n" " " #: plugin.py:555 #, docstring msgid "" "[<plugin>] <command>\n" "\n" " Enables the command <command> for all users. If <plugin>\n" " if given, only enables the <command> from <plugin>. This command is\n" " the inverse of disable.\n" " " msgstr "" "[<plugin>] <comando>\n" "\n" " Abilita <comando> per tutti gli utenti. Se <plugin> è specificato,\n" " viene disabilitato solo <comando> da <plugin>. È il contrario di \"disable\".\n" " " #: plugin.py:574 #, docstring msgid "" "<plugin> <command> <new name>\n" "\n" " Renames <command> in <plugin> to the <new name>.\n" " " msgstr "" "<plugin> <comando> <nuovo nome>\n" "\n" " Rinomina <comando> in <plugin> con <nuovo nome>.\n" " " #: plugin.py:591 #, docstring msgid "" "<plugin>\n" "\n" " Removes all renames in <plugin>. The plugin will be reloaded after\n" " this command is run.\n" " " msgstr "" "<plugin>\n" "\n" " Rimuove tutte le rinominazioni in <plugin>. Dopo questo comando il plugin verrà ricaricato.\n" " " #: plugin.py:604 #, docstring msgid "" "takes no argument\n" "\n" " Reloads the locale of the bot." msgstr "" "non necessita argomenti\n" "\n" " Ricarica la localizzazione del bot." ��������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Owner/plugin.py���������������������������������������������������������0000644�0001750�0001750�00000065563�13634634532�017761� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2008-2009, James McCoy # 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 gc import os import sys import time import socket import string import linecache import re import supybot.log as log import supybot.conf as conf import supybot.i18n as i18n import supybot.utils as utils import supybot.world as world import supybot.ircdb as ircdb from supybot.commands import * import supybot.irclib as irclib import supybot.plugin as plugin import supybot.plugins as plugins import supybot.drivers as drivers import supybot.utils.minisix as minisix import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.registry as registry import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Owner') ### # supybot.commands. ### def registerDefaultPlugin(command, plugin): command = callbacks.canonicalName(command) conf.registerGlobalValue(conf.supybot.commands.defaultPlugins, command, registry.String(plugin, '')) # This must be set, or the quotes won't be removed. conf.supybot.commands.defaultPlugins.get(command).set(plugin) registerDefaultPlugin('list', 'Misc') registerDefaultPlugin('help', 'Misc') registerDefaultPlugin('ignore', 'Admin') registerDefaultPlugin('reload', 'Owner') registerDefaultPlugin('enable', 'Owner') registerDefaultPlugin('disable', 'Owner') registerDefaultPlugin('unignore', 'Admin') registerDefaultPlugin('capabilities', 'User') registerDefaultPlugin('addcapability', 'Admin') registerDefaultPlugin('removecapability', 'Admin') class Owner(callbacks.Plugin): """Owner-only commands for core Supybot. This is a core Supybot module that should not be removed!""" # This plugin must be first; its priority must be lowest; otherwise odd # things will happen when adding callbacks. def __init__(self, irc=None): if irc is not None: assert not irc.getCallback(self.name()) self.__parent = super(Owner, self) self.__parent.__init__(irc) # Setup command flood detection. self.commands = ircutils.FloodQueue(conf.supybot.abuse.flood.interval()) conf.supybot.abuse.flood.interval.addCallback(self.setFloodQueueTimeout) # Setup plugins and default plugins for commands. # # This needs to be done before we connect to any networks so that the # children of supybot.plugins (the actual plugins) exist and can be # loaded. for (name, s) in registry._cache.items(): if 'alwaysLoadDefault' in name or 'alwaysLoadImportant' in name: continue if name.startswith('supybot.plugins'): try: (_, _, name) = registry.split(name) except ValueError: # unpack list of wrong size. continue # This is just for the prettiness of the configuration file. # There are no plugins that are all-lowercase, so we'll at # least attempt to capitalize them. if name == name.lower(): name = name.capitalize() conf.registerPlugin(name) if name.startswith('supybot.commands.defaultPlugins'): try: (_, _, _, name) = registry.split(name) except ValueError: # unpack list of wrong size. continue registerDefaultPlugin(name, s) # Setup Irc objects, connected to networks. If world.ircs is already # populated, chances are that we're being reloaded, so don't do this. if not world.ircs: for network in conf.supybot.networks(): try: self._connect(network) except socket.error as e: self.log.error('Could not connect to %s: %s.', network, e) except Exception as e: self.log.exception('Exception connecting to %s:', network) self.log.error('Could not connect to %s: %s.', network, e) def callPrecedence(self, irc): return ([], [cb for cb in irc.callbacks if cb is not self]) def outFilter(self, irc, msg): if msg.command == 'PRIVMSG' and not world.testing: if ircutils.strEqual(msg.args[0], irc.nick): self.log.warning('Tried to send a message to myself: %r.', msg) return None return msg def reset(self): # This has to be done somewhere, I figure here is as good place as any. callbacks.IrcObjectProxy._mores.clear() self.__parent.reset() def _connect(self, network, serverPort=None, password='', ssl=False): try: group = conf.supybot.networks.get(network) (server, port) = group.servers()[0] except (registry.NonExistentRegistryEntry, IndexError): if serverPort is None: raise ValueError('connect requires a (server, port) ' \ 'if the network is not registered.') conf.registerNetwork(network, password, ssl) serverS = '%s:%s' % serverPort conf.supybot.networks.get(network).servers.append(serverS) assert conf.supybot.networks.get(network).servers(), \ 'No servers are set for the %s network.' % network self.log.debug('Creating new Irc for %s.', network) newIrc = irclib.Irc(network) driver = drivers.newDriver(newIrc) self._loadPlugins(newIrc) return newIrc def _loadPlugins(self, irc): self.log.debug('Loading plugins (connecting to %s).', irc.network) alwaysLoadImportant = conf.supybot.plugins.alwaysLoadImportant() important = conf.supybot.commands.defaultPlugins.importantPlugins() for (name, value) in conf.supybot.plugins.getValues(fullNames=False): if irc.getCallback(name) is None: load = value() if not load and name in important: if alwaysLoadImportant: s = '%s is configured not to be loaded, but is being '\ 'loaded anyway because ' \ 'supybot.plugins.alwaysLoadImportant is True.' self.log.warning(s, name) load = True if load: # We don't load plugins that don't start with a capital # letter. if name[0].isupper() and not irc.getCallback(name): # This is debug because each log logs its beginning. self.log.debug('Loading %s.', name) try: m = plugin.loadPluginModule(name, ignoreDeprecation=True) plugin.loadPluginClass(irc, m) except callbacks.Error as e: # This is just an error message. log.warning(str(e)) except plugins.NoSuitableDatabase as e: s = 'Failed to load %s: no suitable database(%s).' % (name, e) log.warning(s) except ImportError as e: e = str(e) if e.endswith(name): s = 'Failed to load {0}: No plugin named {0} exists.'.format( utils.str.dqrepr(name)) elif "No module named 'config'" in e: s = ("Failed to load %s: This plugin may be incompatible " "with your current Python version. If this error is appearing " "with stock Supybot plugins, remove the stock plugins directory " "(usually ~/Limnoria/plugins) from 'config directories.plugins'." % name) else: s = 'Failed to load %s: import error (%s).' % (name, e) log.warning(s) except Exception as e: log.exception('Failed to load %s:', name) else: # Let's import the module so configuration is preserved. try: _ = plugin.loadPluginModule(name) except Exception as e: log.debug('Attempted to load %s to preserve its ' 'configuration, but load failed: %s', name, e) world.starting = False def do376(self, irc, msg): msgs = conf.supybot.networks.get(irc.network).channels.joins() if msgs: for msg in msgs: irc.queueMsg(msg) do422 = do377 = do376 def setFloodQueueTimeout(self, *args, **kwargs): self.commands.timeout = conf.supybot.abuse.flood.interval() def doPrivmsg(self, irc, msg): assert self is irc.callbacks[0], \ 'Owner isn\'t first callback: %r' % irc.callbacks if ircmsgs.isCtcp(msg): return s = callbacks.addressed(irc.nick, msg) if s: ignored = ircdb.checkIgnored(msg.prefix) if ignored: self.log.info('Ignoring command from %s.', msg.prefix) return maximum = conf.supybot.abuse.flood.command.maximum() self.commands.enqueue(msg) if conf.supybot.abuse.flood.command() \ and self.commands.len(msg) > maximum \ and not ircdb.checkCapability(msg.prefix, 'trusted'): punishment = conf.supybot.abuse.flood.command.punishment() banmask = conf.supybot.protocols.irc.banmask \ .makeBanmask(msg.prefix) self.log.info('Ignoring %s for %s seconds due to an apparent ' 'command flood.', banmask, punishment) ircdb.ignores.add(banmask, time.time() + punishment) if conf.supybot.abuse.flood.command.notify(): irc.reply('You\'ve given me %s commands within the last ' '%i seconds; I\'m now ignoring you for %s.' % (maximum, conf.supybot.abuse.flood.interval(), utils.timeElapsed(punishment, seconds=False))) return try: tokens = callbacks.tokenize(s, channel=msg.channel, network=irc.network) self.Proxy(irc, msg, tokens) except SyntaxError as e: irc.error(str(e)) def logmark(self, irc, msg, args, text): """<text> Logs <text> to the global Supybot log at critical priority. Useful for marking logfiles for later searching. """ self.log.critical(text) irc.replySuccess() logmark = wrap(logmark, ['text']) def announce(self, irc, msg, args, text): """<text> Sends <text> to all channels the bot is currently on and not lobotomized in. """ u = ircdb.users.getUser(msg.prefix) template = self.registryValue('announceFormat') text = ircutils.standardSubstitute( irc, msg, template, env={'owner': u.name, 'text': text}) for channel in irc.state.channels: c = ircdb.channels.getChannel(channel) if not c.lobotomized: irc.queueMsg(ircmsgs.privmsg(channel, text)) irc.noReply() announce = wrap(announce, ['text']) def defaultplugin(self, irc, msg, args, optlist, command, plugin): """[--remove] <command> [<plugin>] Sets the default plugin for <command> to <plugin>. If --remove is given, removes the current default plugin for <command>. If no plugin is given, returns the current default plugin set for <command>. See also, supybot.commands.defaultPlugins.importantPlugins. """ remove = False for (option, arg) in optlist: if option == 'remove': remove = True (_, cbs) = irc.findCallbacksForArgs([command]) if remove: try: conf.supybot.commands.defaultPlugins.unregister(command) irc.replySuccess() except registry.NonExistentRegistryEntry: s = 'I don\'t have a default plugin set for that command.' irc.error(s) elif not cbs: irc.errorInvalid('command', command) elif plugin: if not plugin.isCommand(command): irc.errorInvalid('command in the %s plugin' % plugin.name(), command) registerDefaultPlugin(command, plugin.name()) irc.replySuccess() else: try: irc.reply(conf.supybot.commands.defaultPlugins.get(command)()) except registry.NonExistentRegistryEntry: s = 'I don\'t have a default plugin set for that command.' irc.error(s) defaultplugin = wrap(defaultplugin, [getopts({'remove': ''}), 'commandName', additional('plugin')]) def ircquote(self, irc, msg, args, s): """<string to be sent to the server> Sends the raw string given to the server. """ try: m = ircmsgs.IrcMsg(s) except Exception as e: irc.error(utils.exnToString(e)) else: irc.queueMsg(m) irc.noReply() ircquote = wrap(ircquote, ['text']) def quit(self, irc, msg, args, text): """[<text>] Exits the bot with the QUIT message <text>. If <text> is not given, the default quit message (supybot.plugins.Owner.quitMsg) will be used. If there is no default quitMsg set, your nick will be used. The standard substitutions ($version, $nick, etc.) are all handled appropriately. """ text = text or self.registryValue('quitMsg') or msg.nick text = ircutils.standardSubstitute(irc, msg, text) irc.noReply() m = ircmsgs.quit(text) world.upkeep() for irc in world.ircs[:]: irc.queueMsg(m) irc.die() quit = wrap(quit, [additional('text')]) def flush(self, irc, msg, args): """takes no arguments Runs all the periodic flushers in world.flushers. This includes flushing all logs and all configuration changes to disk. """ world.flush() irc.replySuccess() flush = wrap(flush) def upkeep(self, irc, msg, args, level): """[<level>] Runs the standard upkeep stuff (flushes and gc.collects()). If given a level, runs that level of upkeep (currently, the only supported level is "high", which causes the bot to flush a lot of caches as well as do normal upkeep stuff). """ L = [] if level == 'high': L.append(format('Regexp cache flushed: %n cleared.', (len(re._cache), 'regexp'))) re.purge() L.append(format('Pattern cache flushed: %n cleared.', (len(ircutils._patternCache), 'compiled pattern'))) ircutils._patternCache.clear() L.append(format('hostmaskPatternEqual cache flushed: %n cleared.', (len(ircutils._hostmaskPatternEqualCache), 'result'))) ircutils._hostmaskPatternEqualCache.clear() L.append(format('ircdb username cache flushed: %n cleared.', (len(ircdb.users._nameCache), 'username to id mapping'))) ircdb.users._nameCache.clear() L.append(format('ircdb hostmask cache flushed: %n cleared.', (len(ircdb.users._hostmaskCache), 'hostmask to id mapping'))) ircdb.users._hostmaskCache.clear() L.append(format('linecache line cache flushed: %n cleared.', (len(linecache.cache), 'line'))) linecache.clearcache() if minisix.PY2: sys.exc_clear() collected = world.upkeep() if gc.garbage: L.append('Garbage! %r.' % gc.garbage) if collected is not None: # Some time between 5.2 and 7.1, Pypy (3?) started returning None # when gc.collect() is called. L.append(format('%n collected.', (collected, 'object'))) if L: irc.reply(' '.join(L)) else: irc.replySuccess() upkeep = wrap(upkeep, [additional(('literal', ['high']))]) def load(self, irc, msg, args, optlist, name): """[--deprecated] <plugin> Loads the plugin <plugin> from any of the directories in conf.supybot.directories.plugins; usually this includes the main installed directory and 'plugins' in the current directory. --deprecated is necessary if you wish to load deprecated plugins. """ ignoreDeprecation = False for (option, argument) in optlist: if option == 'deprecated': ignoreDeprecation = True if name.endswith('.py'): name = name[:-3] if irc.getCallback(name): irc.error('%s is already loaded.' % name.capitalize()) return try: module = plugin.loadPluginModule(name, ignoreDeprecation) except plugin.Deprecated: irc.error('%s is deprecated. Use --deprecated ' 'to force it to load.' % name.capitalize()) return except ImportError as e: if str(e).endswith(name): irc.error('No plugin named %s exists.' % utils.str.dqrepr(name)) elif "No module named 'config'" in str(e): irc.error('This plugin may be incompatible with your current Python ' 'version. Try running 2to3 on it.') else: irc.error(str(e)) return cb = plugin.loadPluginClass(irc, module) name = cb.name() # Let's normalize this. conf.registerPlugin(name, True) irc.replySuccess() load = wrap(load, [getopts({'deprecated': ''}), 'something']) def reload(self, irc, msg, args, name): """<plugin> Unloads and subsequently reloads the plugin by name; use the 'list' command to see a list of the currently loaded plugins. """ if ircutils.strEqual(name, self.name()): irc.error('You can\'t reload the %s plugin.' % name) return callbacks = irc.removeCallback(name) if callbacks: module = sys.modules[callbacks[0].__module__] if hasattr(module, 'reload'): x = module.reload() try: module = plugin.loadPluginModule(name) if hasattr(module, 'reload') and 'x' in locals(): module.reload(x) if hasattr(module, 'config'): from importlib import reload reload(module.config) for callback in callbacks: callback.die() del callback gc.collect() # This makes sure the callback is collected. callback = plugin.loadPluginClass(irc, module) irc.replySuccess() except ImportError: for callback in callbacks: irc.addCallback(callback) irc.error('No plugin named %s exists.' % name) else: irc.error('There was no plugin %s.' % name) reload = wrap(reload, ['something']) def unload(self, irc, msg, args, name): """<plugin> Unloads the callback by name; use the 'list' command to see a list of the currently loaded plugins. Obviously, the Owner plugin can't be unloaded. """ if ircutils.strEqual(name, self.name()): irc.error('You can\'t unload the %s plugin.' % name) return # Let's do this so even if the plugin isn't currently loaded, it doesn't # stay attempting to load. old_callback = irc.getCallback(name) if old_callback: # Normalize the plugin case to prevent duplicate registration # entries, https://github.com/ProgVal/Limnoria/issues/1295 name = old_callback.name() conf.registerPlugin(name, False) callbacks = irc.removeCallback(name) if callbacks: for callback in callbacks: callback.die() del callback gc.collect() irc.replySuccess() return irc.error('There was no plugin %s.' % name) unload = wrap(unload, ['something']) def defaultcapability(self, irc, msg, args, action, capability): """{add|remove} <capability> Adds or removes (according to the first argument) <capability> from the default capabilities given to users (the configuration variable supybot.capabilities stores these). """ if action == 'add': conf.supybot.capabilities().add(capability) irc.replySuccess() elif action == 'remove': try: conf.supybot.capabilities().remove(capability) irc.replySuccess() except KeyError: if ircdb.isAntiCapability(capability): irc.error('That capability wasn\'t in ' 'supybot.capabilities.') else: anticap = ircdb.makeAntiCapability(capability) conf.supybot.capabilities().add(anticap) irc.replySuccess() defaultcapability = wrap(defaultcapability, [('literal', ['add','remove']), 'capability']) def disable(self, irc, msg, args, plugin, command): """[<plugin>] <command> Disables the command <command> for all users (including the owners). If <plugin> is given, only disables the <command> from <plugin>. If you want to disable a command for most users but not for yourself, set a default capability of -plugin.command or -command (if you want to disable the command in all plugins). """ if command in ('enable', 'identify'): irc.error('You can\'t disable %s.' % command) return if plugin: if plugin.isCommand(command): pluginCommand = '%s.%s' % (plugin.name(), command) conf.supybot.commands.disabled().add(pluginCommand) plugin._disabled.add(command, plugin.name()) else: irc.error('%s is not a command in the %s plugin.' % (command, plugin.name())) return else: conf.supybot.commands.disabled().add(command) self._disabled.add(command) irc.replySuccess() disable = wrap(disable, [optional('plugin'), 'commandName']) def enable(self, irc, msg, args, plugin, command): """[<plugin>] <command> Enables the command <command> for all users. If <plugin> if given, only enables the <command> from <plugin>. This command is the inverse of disable. """ try: if plugin: plugin._disabled.remove(command, plugin.name()) command = '%s.%s' % (plugin.name(), command) else: self._disabled.remove(command) conf.supybot.commands.disabled().remove(command) irc.replySuccess() except KeyError: irc.error('That command wasn\'t disabled.') enable = wrap(enable, [optional('plugin'), 'commandName']) def rename(self, irc, msg, args, command_plugin, command, newName): """<plugin> <command> <new name> Renames <command> in <plugin> to the <new name>. """ if not command_plugin.isCommand(command): what = 'command in the %s plugin' % command_plugin.name() irc.errorInvalid(what, command) if hasattr(command_plugin, newName): irc.error('The %s plugin already has an attribute named %s.' % (command_plugin, newName)) return plugin.registerRename(command_plugin.name(), command, newName) plugin.renameCommand(command_plugin, command, newName) irc.replySuccess() rename = wrap(rename, ['plugin', 'commandName', 'commandName']) def unrename(self, irc, msg, args, plugin): """<plugin> Removes all renames in <plugin>. The plugin will be reloaded after this command is run. """ try: conf.supybot.commands.renames.unregister(plugin.name()) except registry.NonExistentRegistryEntry: irc.errorInvalid('plugin', plugin.name()) self.reload(irc, msg, [plugin.name()]) # This makes the replySuccess. unrename = wrap(unrename, ['plugin']) def reloadlocale(self, irc, msg, args): """takes no argument Reloads the locale of the bot.""" i18n.reloadLocales() irc.replySuccess() Class = Owner # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Owner/test.py�����������������������������������������������������������0000644�0001750�0001750�00000011143�13634634532�017423� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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 sys if sys.version_info >= (2, 7, 0): from unittest import skip else: # Workaround def skip(string): return lambda x:None from supybot.test import * import supybot.conf as conf import supybot.plugin as plugin class OwnerTestCase(PluginTestCase): plugins = ('Owner', 'Config', 'Misc', 'Admin') def testHelpLog(self): self.assertHelp('help logmark') def testSrcAmbiguity(self): self.assertError('capability add foo bar') def testIrcquote(self): self.assertResponse('ircquote PRIVMSG %s :foo' % self.irc.nick, 'foo') def testFlush(self): self.assertNotError('flush') def testUpkeep(self): self.assertNotError('upkeep') def testLoad(self): self.assertError('load Owner') self.assertError('load owner') self.assertNotError('load Channel') self.assertNotError('list Owner') def testReload(self): self.assertError('reload Channel') self.assertNotError('load Channel') self.assertNotError('reload Channel') self.assertNotError('reload Channel') def testUnload(self): self.assertError('unload Foobar') self.assertNotError('load Channel') self.assertNotError('unload Channel') self.assertError('unload Channel') self.assertNotError('load Channel') self.assertNotError('unload CHANNEL') def testDisable(self): self.assertError('disable enable') self.assertError('disable identify') def testEnable(self): self.assertError('enable enable') def testEnableIsCaseInsensitive(self): self.assertNotError('disable Foo') self.assertNotError('enable foo') def testRename(self): self.assertError('rename Admin join JOIN') self.assertError('rename Admin join jo-in') self.assertNotError('rename Admin join testcommand') self.assertRegexp('list Admin', 'testcommand') self.assertNotRegexp('list Admin', 'join') self.assertError('help join') self.assertRegexp('help testcommand', 'Tell the bot to join') self.assertRegexp('join', 'not a valid command') self.assertHelp('testcommand') self.assertNotError('unrename Admin') self.assertNotRegexp('list Admin', 'testcommand') @skip('Nested commands cannot be renamed yet.') def testRenameNested(self): self.assertNotError('rename Admin "capability remove" rmcap') self.assertNotRegexp('list Admin', 'capability remove') self.assertRegexp('list Admin', 'rmcap') self.assertNotError('reload Admin') self.assertNotRegexp('list Admin', 'capability remove') self.assertRegexp('list Admin', 'rmcap') self.assertNotError('unrename Admin') self.assertRegexp('list Admin', 'capability remove') self.assertNotRegexp('list Admin', 'rmcap') def testDefaultPluginErrorsWhenCommandNotInPlugin(self): self.assertError('defaultplugin foobar owner') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Plugin/�����������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016244� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Plugin/__init__.py������������������������������������������������������0000644�0001750�0001750�00000005076�13634634532�020357� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ This plugin handles various plugin-related things, such as getting help for a plugin or retrieving author info. """ import supybot import supybot.world as world __version__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. # Note: the tests for this plugin look at some of this data - update that too # as necessary __contributors__ = { supybot.authors.skorobeus: ['original contributors command'], supybot.authors.jlu: ['refactored contributors command'], } from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Plugin/config.py��������������������������������������������������������0000644�0001750�0001750�00000004657�13634634532�020071� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Plugin') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Plugin', True) Plugin = conf.registerPlugin('Plugin') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Plugin, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Plugin/locales/���������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017666� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Plugin/locales/de.po����������������������������������������������������0000644�0001750�0001750�00000015253�13634634532�020616� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-11-01 20:55+0100\n" "Last-Translator: Florian Besser <fbesser@gmail.com>\n" "Language-Team: \n" "Language: de\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" #: 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" " of a plugin; use the 'plugin' command itself to determine what plugin a\n" " command exists in." msgstr "Dieses Plugin gibt es um Nutzern zu helfen ihre Plugins zu verwalten. Benutze 'plugin list' um alle geladenen Plugins aufzulisten; benutze 'plugin help' um eine Beschreibung des Plugins zu bekommen; benutze 'plugin' selbst um herauszufinden in welchem Plugin ein Befehl existiert." #: plugin.py:48 msgid "" "<plugin>\n" "\n" " Returns a useful description of how to use <plugin>, if the plugin has\n" " one.\n" " " msgstr "" "<plugin>\n" "\n" "Gibt eine nützliche Beschreibung aus wie man das <plugin> benutzt, falls das Plugin eine hat" #: plugin.py:57 msgid "That plugin is loaded, but has no plugin help." msgstr "Das Plugin ist geladen, hat aber keine Hilfe." #: plugin.py:62 msgid "" "takes no arguments\n" "\n" " Returns a list of the currently loaded plugins.\n" " " msgstr "" "hat keine Argumente\n" "\n" "Gibt eine liste aller geladenen Plugins aus." #: plugin.py:73 msgid "" "<command>\n" "\n" " Returns the name of the plugin that would be used to call <command>.\n" " \n" " If it is not uniquely determined, returns list of all plugins that\n" " contain <command>.\n" " " msgstr "" "<Befehl>\n" "\n" "Gibt den Namen des Plugins aus das benutzt wird um <Befehl> aufzurufen. Wenn dieser nicht einzigartig ist, wird eine Liste aller Plugins ausgegeben die über den <Befehl> verfügen." #: plugin.py:91 msgid "plugins" msgstr "Plugins" #: plugin.py:93 msgid "plugin" msgstr "Plugin" #: plugin.py:94 msgid "The %q command is available in the %L %s." msgstr "Der %q Befehl ist im %L %s verfügbar." #: plugin.py:97 msgid "There is no command %q." msgstr "Es gibt keinen Befehl %q." #: plugin.py:113 msgid "" "<command>\n" "\n" " Returns the names of all plugins that contain <command>.\n" " " msgstr "" "<Befehl>\n" "\n" "Gibt die Namen aller Plugins aus die <Befehl> beinhalten." #: plugin.py:135 msgid "" "<plugin>\n" "\n" " Returns the author of <plugin>. This is the person you should talk to\n" " if you have ideas, suggestions, or other comments about a given plugin.\n" " " msgstr "" "<Plugin>\n" "\n" "Gibt den Autor von <Plugin> aus. Das ist die Person mit der du kontakt aufnehmen sollte falls du Ideen, Vorschläge oder andere Kommentare für das Plugin hast." #: plugin.py:141 msgid "That plugin does not seem to be loaded." msgstr "Das Plugin ist wohl nicht geladen." #: plugin.py:147 msgid "That plugin doesn't have an author that claims it." msgstr "Kein Autor hat das Plugin beansprucht." #: plugin.py:152 msgid "" "<plugin> [<nick>]\n" "\n" " Replies with a list of people who made contributions to a given plugin.\n" " If <nick> is specified, that person's specific contributions will\n" " be listed. Note: The <nick> is the part inside of the parentheses\n" " in the people listing.\n" " " msgstr "" "<plugin> [<nick>]\n" "\n" "Antwortet mit einer Liste von Personen die zum angegeben Plugin beigetragen haben.Falls <Nick> angegeben wird, werden die personenspezifischen Beitröge gelistet. Notiz: Der <Nick> ist der eingeklammerte Bereich in der Personenliste." #: plugin.py:160 msgid "" "\n" " Take an Authors object, and return only the name and nick values\n" " in the format 'First Last (nick)'.\n" " " msgstr "" "\n" " Nimmt ein Autoren Objekt und gibt nur die Namens und Nick Werte im format 'Vorname Nachname (Nick)' aus." #: plugin.py:166 msgid "" "\n" " Take a list of long names and turn it into :\n" " shortname[, shortname and shortname].\n" " " msgstr "" "\n" "Nimmt eine Liste von langen Namen und macht daraus: Kurzname[, Kurzname und Kurzname]." #: plugin.py:173 msgid "" "\n" " Sort the list of 'long names' based on the number of contributions\n" " associated with each.\n" " " msgstr "" "\n" "Sortiert die Liste von 'langen Namen' nach der Nummer der Beiträge, mit denen sie in Verbindung gebracht werden." #: plugin.py:183 msgid "" "\n" " Build the list of author + contributors (if any) for the requested\n" " plugin.\n" " " msgstr "" "\n" "Erstellt eine Liste der Autoren + Mitwirkenden (falls es welche gibt) des abgefragten Plugins." #: plugin.py:187 msgid "The %s plugin" msgstr "Das %s Plugin" #: plugin.py:188 msgid "has not been claimed by an author" msgstr "wurde von keinem Autor beansprucht." #: plugin.py:189 msgid "and" msgstr "und" #: plugin.py:190 msgid "has no contributors listed." msgstr "hat keine Mitwirkenden gelistet." #: plugin.py:195 msgid "was written by %s" msgstr "wurde geschrieben von %s" #: plugin.py:206 msgid "%s %h contributed to it." msgstr "%s %h hat dazu beigesteuert." #: plugin.py:211 msgid "has no additional contributors listed." msgstr "hat keine zusätzliche Mitwirkende." #: plugin.py:213 msgid "but" msgstr "aber" #: plugin.py:216 msgid "" "\n" " Build the list of contributions (if any) for the requested person\n" " for the requested plugin\n" " " msgstr "" "\n" "Erstelle eine Liste von Beiträgen (falls es welche gibt) die von der erfragten Person für das erfragte Plugin gemacht wurden." #: plugin.py:230 msgid "The nick specified (%s) is not a registered contributor." msgstr "Der gegebene Nick (%s) hat keinen registrierten Mitwirkenden." #: plugin.py:236 msgid "The %s plugin does not have '%s' listed as a contributor." msgstr "Das Plugin %s hat keinen '%s' als Mitwirkenden aufgeführt." #: plugin.py:244 msgid "command" msgstr "Befehl" #: plugin.py:247 msgid "the %L %s" msgstr "das %L %s" #: plugin.py:249 msgid "the %L" msgstr "das %L" #: plugin.py:252 msgid "%s wrote the %s plugin and also contributed %L." msgstr "%s hat das Plugin %s geschrieben und auch %L beigesteuert." #: plugin.py:255 msgid "%s contributed %L to the %s plugin." msgstr "%s hat %L zum %s Plugin beigesteuert." #: plugin.py:258 msgid "%s wrote the %s plugin" msgstr "%s hat das %s Plugin geschrieben" #: plugin.py:261 msgid "%s has no listed contributions for the %s plugin." msgstr "%s hat keine gelisteten Beiträge für das %s Plugin." �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Plugin/locales/fi.po����������������������������������������������������0000755�0001750�0001750�00000016537�13634634532�020635� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: \n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-07-24 17:55+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\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" #: 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" " of a plugin; use the 'plugin' command itself to determine what plugin a\n" " command exists in." msgstr "" "Tämä lisäosa on olemassa auttaakseen ihmisiä hallitsemaan lisäosiaan. Käytä komentoa 'plugin\n" " list' nähdäksesi listan ladatuista lisäosista; käytä 'plugin help' komentoa saadaksesi kuvauksen\n" " lisäosasta; käytä 'plugin' komentoa itseään määrittääksesi mitkä komennot lisäosassa\n" " on olemassa." #: plugin.py:48 msgid "" "<plugin>\n" "\n" " Returns a useful description of how to use <plugin>, if the plugin has\n" " one.\n" " " msgstr "" "<lisäosa>\n" "\n" " Palauttaa hyödyllistä tietoa, kuinka <lisäosaa> käytetään, jos lisäosalla on\n" " sellainen.\n" " " #: plugin.py:57 msgid "That plugin is loaded, but has no plugin help." msgstr "Tuo lisäosa on ladattu, mutta sillä ei ole \"plugin help\" ohjetta." #: plugin.py:62 msgid "" "takes no arguments\n" "\n" " Returns a list of the currently loaded plugins.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Palauttaa listan tällä hetkellä ladatuista lisäosista.\n" " " #: plugin.py:73 msgid "" "<command>\n" "\n" " Returns the name of the plugin that would be used to call <command>.\n" " \n" " If it is not uniquely determined, returns list of all plugins that\n" " contain <command>.\n" " " msgstr "" "<komento>\n" "\n" " Palauttaa lisäosan nimen, jota voisi käyttää kutsumaan <komentoa>.\n" " \n" " Jos se ei ole ainutlaatuisesti määritetty, palauttaa listan kaikista lisäosista, jotka\n" " sisältävät <komennon>.\n" " " #: plugin.py:91 msgid "plugins" msgstr "lisäosissa" #: plugin.py:93 msgid "plugin" msgstr "lisäosassa" #: plugin.py:94 msgid "The %q command is available in the %L %s." msgstr "Komento %q on saatavilla %L %s." #: plugin.py:97 msgid "There is no command %q." msgstr "%q komentoa %q ei ole." #: plugin.py:113 msgid "" "<command>\n" "\n" " Returns the names of all plugins that contain <command>.\n" " " msgstr "" "<komento>\n" "\n" " Palauttaa kaikkien lisäosien nimet, jotka sisältävät <komennon>.\n" " " #: plugin.py:135 msgid "" "<plugin>\n" "\n" " Returns the author of <plugin>. This is the person you should talk to\n" " if you have ideas, suggestions, or other comments about a given plugin.\n" " " msgstr "" "<lisäosa>\n" "\n" " Palauttaa <lisäosan> kirjoittajan. Tämä on henkilö, jolle sinun pitäisi puhua\n" " jos sinulla on ideaoita, ehdotuksia, tai muita kommentteja annetusta lisäosasta.\n" " " #: plugin.py:141 msgid "That plugin does not seem to be loaded." msgstr "Tuo lisäosa ei vaikuta olevan ladattu." #: plugin.py:147 msgid "That plugin doesn't have an author that claims it." msgstr "Tuolla lisäosalla ei ole tekijää, joka ilmoittaa sen omakseen." #: plugin.py:152 msgid "" "<plugin> [<nick>]\n" "\n" " Replies with a list of people who made contributions to a given plugin.\n" " If <nick> is specified, that person's specific contributions will\n" " be listed. Note: The <nick> is the part inside of the parentheses\n" " in the people listing.\n" " " msgstr "" "<lisäosa> [<nimimerkki>]\n" "\n" " Vastaa listalla ihmisistä, jotka ovat osallistuneet annetun lisäosan kehittämiseen.\n" " Jos <nimimerkki> on annettu, juuri tuon henkilön osallistuminen\n" " luetellaan. Huomaa: <nimimerkki> on se sulkeissa oleva osa\n" " henkilöluetteloinnissa..\n" " " #: plugin.py:160 msgid "" "\n" " Take an Authors object, and return only the name and nick values\n" " in the format 'First Last (nick)'.\n" " " msgstr "" "\n" " Ota kirjoittajan objekti, ja palauta vain nimi ja nimimerkki arvot\n" " muodossa 'Etu- Sukunimi (nimimerkki)'.\n" " " #: plugin.py:166 #, fuzzy msgid "" "\n" " Take a list of long names and turn it into :\n" " shortname[, shortname and shortname].\n" " " msgstr "" "\n" " Ottaa listan pitkistä nimistä ja muuttaa sen :\n" " lyhyeksi nimeksi[, lyhyt nimi ja lyhyt nimi].\n" " " #: plugin.py:173 msgid "" "\n" " Sort the list of 'long names' based on the number of contributions\n" " associated with each.\n" " " msgstr "" "\n" " Lajittelee listan 'pitkistä nimistä' perustuen osallistumisen määrän\n" " liitettynä toisiinsa.\n" " " #: plugin.py:183 msgid "" "\n" " Build the list of author + contributors (if any) for the requested\n" " plugin.\n" " " msgstr "" "\n" " Rakentaa listan tekijöistä + osallistujista (jos yhtään) pyydetylle\n" " lisäosalle.\n" " " #: plugin.py:187 msgid "The %s plugin" msgstr "%s lisäosa" #: plugin.py:188 msgid "has not been claimed by an author" msgstr "ei ole kirjoittajan omakseen väittämä" #: plugin.py:189 msgid "and" msgstr "ja" #: plugin.py:190 msgid "has no contributors listed." msgstr "ei ole lueteltuja osallistumisia." #: plugin.py:195 msgid "was written by %s" msgstr "kirjoittanut %s" #: plugin.py:206 msgid "%s %h contributed to it." msgstr "%s %h osallistuivat siihen." #: plugin.py:211 msgid "has no additional contributors listed." msgstr "ei ole vaihtoehtoisia osallistujia lueteltuna." #: plugin.py:213 msgid "but" msgstr "mutta" #: plugin.py:216 msgid "" "\n" " Build the list of contributions (if any) for the requested person\n" " for the requested plugin\n" " " msgstr "" "\n" " Rakentaa listan osallistumisista (jos yhtään) pyydetylle henkilölle\n" " pyydetystä lisäosasta.\n" " " #: plugin.py:230 msgid "The nick specified (%s) is not a registered contributor." msgstr "Määritetty nimimerkki (%s) ei ole rekisteröitynyt osallistuja." #: plugin.py:236 msgid "The %s plugin does not have '%s' listed as a contributor." msgstr "Lisäosalla %s ei ole '%s':ää lueteltuna osallistujaksi." #: plugin.py:244 msgid "command" msgstr "komento" #: plugin.py:247 msgid "the %L %s" msgstr "%L %s" #: plugin.py:249 msgid "the %L" msgstr "%L" #: plugin.py:252 msgid "%s wrote the %s plugin and also contributed %L." msgstr "%s kirjoitti lisäosan %s ja osallistui myös %L:ään." #: plugin.py:255 #, fuzzy msgid "%s contributed %L to the %s plugin." msgstr "%s osallistui %L:stä %s lisäosaan." #: plugin.py:258 msgid "%s wrote the %s plugin" msgstr "%s kirjoitti lisäosan %s" #: plugin.py:261 msgid "%s has no listed contributions for the %s plugin." msgstr "%s:llä ei ole lueteltuja osallistujia lisäosaan %s." �����������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Plugin/locales/fr.po����������������������������������������������������0000644�0001750�0001750�00000014367�13634634532�020642� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\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: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" " of a plugin; use the 'plugin' command itself to determine what plugin a\n" " 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:48 msgid "" "<plugin>\n" "\n" " Returns a useful description of how to use <plugin>, if the plugin has\n" " one.\n" " " msgstr "" "<plugin>\n" "\n" "Retourne une description utile de comment utiliser le <plugin>, si le plugin en a une." #: 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:62 msgid "" "takes no arguments\n" "\n" " Returns a list of the currently loaded plugins.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne une liste des plugins actuellement chargés." #: plugin.py:73 msgid "" "<command>\n" "\n" " Returns the name of the plugin that would be used to call <command>.\n" " \n" " If it is not uniquely determined, returns list of all plugins that\n" " contain <command>.\n" " " msgstr "" "<commande>\n" "\n" "Retourne le nom du plugin qui serait utilisé pour appeller la <commande>. 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:91 msgid "plugins" msgstr "plugins" #: plugin.py:93 msgid "plugin" msgstr "plugin" #: 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:97 msgid "There is no command %q." msgstr "Il n'y a pas de commande %q." #: plugin.py:113 msgid "" "<command>\n" "\n" " Returns the names of all plugins that contain <command>.\n" " " msgstr "" "<commande>\n" "\n" "Retourne les noms de tous les plugins contenant la <commande>." #: plugin.py:135 msgid "" "<plugin>\n" "\n" " Returns the author of <plugin>. This is the person you should talk to\n" " if you have ideas, suggestions, or other comments about a given plugin.\n" " " msgstr "" "<plugin>\n" "\n" "Retourne l'auteur du <plugin>. 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:141 msgid "That plugin does not seem to be loaded." msgstr "Ce plugin ne semble pas être chargé." #: plugin.py:147 msgid "That plugin doesn't have an author that claims it." msgstr "Ce plugin n'a pas d'auteur." #: plugin.py:152 msgid "" "<plugin> [<nick>]\n" "\n" " Replies with a list of people who made contributions to a given plugin.\n" " If <nick> is specified, that person's specific contributions will\n" " be listed. Note: The <nick> is the part inside of the parentheses\n" " in the people listing.\n" " " msgstr "" "<plugin> [<nick>]\n" "\n" "Renvoie une liste des personnes ayant contribué à un plugin donné. Si le <nick> est spécifié, les contributions de cette personne seront lisées. Note : <nick> est la partie entre parenthèses lors du listing des personnes." #: plugin.py:160 msgid "" "\n" " Take an Authors object, and return only the name and nick values\n" " in the format 'First Last (nick)'.\n" " " msgstr "" #: plugin.py:166 msgid "" "\n" " Take a list of long names and turn it into :\n" " shortname[, shortname and shortname].\n" " " msgstr "" #: plugin.py:173 msgid "" "\n" " Sort the list of 'long names' based on the number of contributions\n" " associated with each.\n" " " msgstr "" #: plugin.py:183 msgid "" "\n" " Build the list of author + contributors (if any) for the requested\n" " plugin.\n" " " msgstr "" #: plugin.py:187 msgid "The %s plugin" msgstr "Le plugin s" #: plugin.py:188 msgid "has not been claimed by an author" msgstr "n'a aucun auteur" #: plugin.py:189 msgid "and" msgstr "et" #: plugin.py:190 msgid "has no contributors listed." msgstr "n'a pas de contributeur listé." #: plugin.py:195 msgid "was written by %s" msgstr "a été écrit par %s" #: plugin.py:206 msgid "%s %h contributed to it." msgstr "%s y %h contribué." #: plugin.py:211 msgid "has no additional contributors listed." msgstr "n'a pas d'autre contributeur listé." #: plugin.py:213 msgid "but" msgstr "mais" #: plugin.py:216 msgid "" "\n" " Build the list of contributions (if any) for the requested person\n" " for the requested plugin\n" " " msgstr "" #: 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: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:244 msgid "command" msgstr "commande" #: plugin.py:247 msgid "the %L %s" msgstr "La/les commande(s) %L%v" #: plugin.py:249 msgid "the %L" msgstr "La/les %L" #: 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:255 msgid "%s contributed %L to the %s plugin." msgstr "%s a contribué à %L et au plugin %s" #: plugin.py:258 msgid "%s wrote the %s plugin" msgstr "%s a écrit le plugin %s" #: 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." �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Plugin/locales/it.po����������������������������������������������������0000644�0001750�0001750�00000015606�13634634532�020644� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-07-10 11:43+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugin.py:42 #, docstring 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" " of a plugin; use the 'plugin' command itself to determine what plugin a\n" " command exists in." msgstr "" "Questo plugin è per aiutare gli utenti a gestire i loro plugin. Usa \"plugin\n" " list\" per elencare tutti quelli caricati; \"plugin help\" per ottenere la\n" " descrizione di un plugin; lo stesso comando \"plugin\" per determinare in quale\n" " plugin è presente un certo comando." #: plugin.py:48 #, docstring msgid "" "<plugin>\n" "\n" " Returns a useful description of how to use <plugin>, if the plugin has\n" " one.\n" " " msgstr "" "<plugin>\n" "\n" " Riporta un'utile descrizione di come utilizzare <plugin>, se disponibile.\n" " " #: plugin.py:57 msgid "That plugin is loaded, but has no plugin help." msgstr "Il plugin è caricato ma non ha un help." #: plugin.py:62 #, docstring msgid "" "takes no arguments\n" "\n" " Returns a list of the currently loaded plugins.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Riporta un elenco dei plugin attualmente caricati.\n" " " #: plugin.py:73 #, docstring msgid "" "<command>\n" "\n" " Returns the name of the plugin that would be used to call <command>.\n" " \n" " If it is not uniquely determined, returns list of all plugins that\n" " contain <command>.\n" " " msgstr "" "<comando>\n" "\n" " Restituisce il nome del plugin che sarà utilizzato per richiamare <comando>.\n" " \n" " Se non è unico riporta l'elenco di tutti i plugin che contengono <comando>.\n" " " #: plugin.py:91 msgid "plugins" msgstr "plugin" #: plugin.py:93 msgid "plugin" msgstr "plugin" #: plugin.py:94 msgid "The %q command is available in the %L %s." msgstr "Il comando %q è disponibile nel %L %s." #: plugin.py:97 msgid "There is no command %q." msgstr "Non c'è un comando %q." #: plugin.py:113 #, docstring msgid "" "<command>\n" "\n" " Returns the names of all plugins that contain <command>.\n" " " msgstr "" "<comando>\n" "\n" " Restituisce i nomi di tutti i plugin che contengono <comando>.\n" " " #: plugin.py:135 #, docstring msgid "" "<plugin>\n" "\n" " Returns the author of <plugin>. This is the person you should talk to\n" " if you have ideas, suggestions, or other comments about a given plugin.\n" " " msgstr "" "<plugin>\n" "\n" " Riporta l'autore di <plugin>. È la persona con cui dovresti parlare se hai\n" " idee, suggerimenti o altri commenti a proposito del plugin.\n" " " #: plugin.py:141 msgid "That plugin does not seem to be loaded." msgstr "Questo plugin non sembra caricato." #: plugin.py:147 msgid "That plugin doesn't have an author that claims it." msgstr "Questo plugin non ha un autore." #: plugin.py:152 #, docstring msgid "" "<plugin> [<nick>]\n" "\n" " Replies with a list of people who made contributions to a given plugin.\n" " If <nick> is specified, that person's specific contributions will\n" " be listed. Note: The <nick> is the part inside of the parentheses\n" " in the people listing.\n" " " msgstr "" "<plugin> [<nick>]\n" "\n" " Risponde con un elenco di persone che hanno contribuito al dato plugin.\n" " Se <nick> è specificato, verranno elencati i contributi di quella determinata\n" " persona. Nota: <nick> è la parte tra parentesi.\n" " " #: plugin.py:160 #, docstring msgid "" "\n" " Take an Authors object, and return only the name and nick values\n" " in the format 'First Last (nick)'.\n" " " msgstr "" "\n" " Prende l'oggetto Authors e riporta nome e nick nella forma \"Nome Cognome (nick)\".\n" " " #: plugin.py:166 #, docstring msgid "" "\n" " Take a list of long names and turn it into :\n" " shortname[, shortname and shortname].\n" " " msgstr "" "\n" " Prende una lista di nomi lunghi e li trasforma in: nomecorto[, nomecorto e nomecorto].\n" " " #: plugin.py:173 #, docstring msgid "" "\n" " Sort the list of 'long names' based on the number of contributions\n" " associated with each.\n" " " msgstr "" "\n" " Ordina l'elenco di \"nomi lunghi\" in base al numero di contributi associati a ognuno.\n" " " #: plugin.py:183 #, docstring msgid "" "\n" " Build the list of author + contributors (if any) for the requested\n" " plugin.\n" " " msgstr "" "\n" " Crea l'elenco di autori + contributori (eventuali) per il plugin richiesto.\n" " " #: plugin.py:187 msgid "The %s plugin" msgstr "Il plugin %s" #: plugin.py:188 msgid "has not been claimed by an author" msgstr "non ha alcun autore" #: plugin.py:189 msgid "and" msgstr "e" #: plugin.py:190 msgid "has no contributors listed." msgstr "non ha contributori elencati." #: plugin.py:195 msgid "was written by %s" msgstr "è stato scritto da %s" #: plugin.py:206 msgid "%s %h contributed to it." msgstr "%s %h contribuito." #: plugin.py:211 msgid "has no additional contributors listed." msgstr "non ha contributori aggiuntivi elencati." #: plugin.py:213 msgid "but" msgstr "ma" #: plugin.py:216 #, docstring msgid "" "\n" " Build the list of contributions (if any) for the requested person\n" " for the requested plugin\n" " " msgstr "" "\n" " Crea l'elenco di contributi (eventuali) per la persona richiesta nel dato plugin.\n" " " #: plugin.py:230 msgid "The nick specified (%s) is not a registered contributor." msgstr "Il nick specificato (%s) non è un contributore registrato." #: plugin.py:236 msgid "The %s plugin does not have '%s' listed as a contributor." msgstr "Il plugin %s non ha \"%s\" elencato come contributore." #: plugin.py:244 msgid "command" msgstr "comando" #: plugin.py:247 msgid "the %L %s" msgstr "il %L %s" #: plugin.py:249 msgid "the %L" msgstr "il %L" #: plugin.py:252 msgid "%s wrote the %s plugin and also contributed %L." msgstr "%s ha scritto il plugin %s e ha contribuito anche a %L." #: plugin.py:255 msgid "%s contributed %L to the %s plugin." msgstr "%s ha contribuito a %L per il plugin %s." #: plugin.py:258 msgid "%s wrote the %s plugin" msgstr "%s ha scritto il plugin %s" #: plugin.py:261 msgid "%s has no listed contributions for the %s plugin." msgstr "%s non ha contributi elencati per il plugin %s." ��������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Plugin/plugin.py��������������������������������������������������������0000644�0001750�0001750�00000023271�13634634532�020113� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # Copyright (c) 2019, James Lu <james@overdrivenetworks.com> # 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 import supybot.utils as utils from supybot.commands import * import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Plugin') class Plugin(callbacks.Plugin): """This plugin exists to help users manage their plugins. Use 'plugin list' to list the loaded plugins; use 'plugin help' to get the description of a plugin; use the 'plugin' command itself to determine what plugin a command exists in.""" @internationalizeDocstring def help(self, irc, msg, args, cb): """<plugin> Returns a useful description of how to use <plugin>, if the plugin has one. """ doc = cb.getPluginHelp() if doc: irc.reply(utils.str.normalizeWhitespace(doc)) else: irc.reply(_('That plugin is loaded, but has no plugin help.')) help = wrap(help, ['plugin']) @internationalizeDocstring def plugin(self, irc, msg, args, command): """<command> Returns the name of the plugin that would be used to call <command>. If it is not uniquely determined, returns list of all plugins that contain <command>. """ (maxL, cbs) = irc.findCallbacksForArgs(command) L = [] if maxL == command: for cb in cbs: L.append(cb.name()) 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)) plugin = wrap(plugin, [many('something')]) def _findCallbacks(self, irc, command): command = list(map(callbacks.canonicalName, command)) plugin_list = [] for cb in irc.callbacks: if not hasattr(cb, 'getCommand'): continue longest_matching_command = cb.getCommand(command) if len(longest_matching_command) >= len(command): # Actually, this is equivalent to use == plugin_list.append(cb.name()) return plugin_list @internationalizeDocstring def plugins(self, irc, msg, args, command): """<command> Returns the names of all plugins that contain <command>. """ 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')]) def author(self, irc, msg, args, cb): """<plugin> Returns the author of <plugin>. This is the person you should talk to if you have ideas, suggestions, or other comments about a given plugin. """ if cb is None: irc.error(_('That plugin does not seem to be loaded.')) return module = cb.classModule author = getattr(module, '__author__', None) # Allow for a maintainer field, which better represents plugins that have changed hands # over time. Of course, assume that the author is the maintainer if no other info is given. maintainer = getattr(module, '__maintainer__', None) or author if author: if maintainer == author: irc.reply(_("%s was written by %s") % (cb.name(), author)) else: irc.reply(_("%s was written by %s and is maintained by %s.") % \ (cb.name(), author, maintainer)) else: irc.reply(_('%s does not have any author listed.') % cb.name()) author = wrap(author, [('plugin')]) @internationalizeDocstring def contributors(self, irc, msg, args, cb, nick): """<plugin> [<name>] Replies with a list of people who made contributions to a given plugin. If <name> is specified, that person's specific contributions will be listed. You can specify a person's name by their full name or their nick, which is shown inside brackets if available. """ def buildContributorsString(longList): """ Take a list of long names and turn it into : shortname[, shortname and shortname]. """ L = [authorInfo.format(short=True) for authorInfo in longList] return format('%L', L) def buildPeopleString(module): """ Build the list of author + contributors (if any) for the requested plugin. """ author = getattr(module, '__author__', supybot.authors.unknown) if author != supybot.authors.unknown: s = _('The %s plugin was written by %s. ' % (cb.name(), author)) else: s = _('The %s plugin has not been claimed by an author. ') % cb.name() contribs = getattr(module, '__contributors__', {}) if contribs: s += format(_('%s %h contributed to it.'), buildContributorsString(contribs.keys()), len(contribs)) else: s += _('No additional contributors are listed.') return s def buildPersonString(module): """ Build the list of contributions (if any) for the requested person for the requested plugin. """ contributors = getattr(module, '__contributors__', {}) # Make a mapping of nicks and names to author instances contributorNicks = {} for contributor in contributors.keys(): if contributor.nick: contributorNicks[contributor.nick.lower()] = contributor if contributor.name: contributorNicks[contributor.name.lower()] = contributor lnick = nick.lower() author = getattr(module, '__author__', supybot.authors.unknown) if author != supybot.authors.unknown and \ (lnick == (author.name or '').lower() or lnick == (author.nick or '').lower()): # Special case for the plugin author. We remove legacy handling of the case where # someone is listed both as author and contributor, which should never really happen? return _('%s wrote the %s plugin.') % (author, cb.name()) elif lnick not in contributorNicks: return _('%s is not listed as a contributor to %s.') % (nick, cb.name()) authorInfo = contributorNicks[lnick] contributions = contributors[authorInfo] fullName = authorInfo.format(short=True) if contributions: return format(_('%s contributed the following to %s: %s'), fullName, cb.name(), ', '.join(contributions)) else: return _('%s did not list any specific contributions to the %s ' 'plugin.') % (fullName, cb.name()) # First we need to check and see if the requested plugin is loaded module = cb.classModule if not nick: irc.reply(buildPeopleString(module)) else: nick = ircutils.toLower(nick) irc.reply(buildPersonString(module)) contributors = wrap(contributors, ['plugin', additional('text')]) Plugin = internationalizeDocstring(Plugin) Class = Plugin # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Plugin/test.py����������������������������������������������������������0000644�0001750�0001750�00000010204�13634634532�017564� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot from supybot.test import * class PluginTestCase(PluginTestCase): plugins = ('Plugin', 'Utilities', 'Admin', 'Format') def testPlugin(self): self.assertRegexp('plugin ignore', 'available.*Utilities plugin') self.assertResponse('echo [plugin ignore]', 'Utilities') def testPlugins(self): self.assertRegexp('plugins join', '(Format.*Admin|Admin.*Format)') self.assertRegexp('plugins plugin', 'Plugin') self.assertNotRegexp('plugins ignore add', 'Utilities') self.assertNotRegexp('plugins ignore', 'Admin') def testHelp(self): self.assertRegexp('plugin help plugin', 'manage their plugins') def testAuthor(self): self.assertRegexp('plugin author plugin', 'jemfinch') self.assertRegexp('plugin author plugin', 'maintained by %s' % supybot.authors.limnoria_core.name) def testContributors(self): # Test ability to list contributors self.assertNotError('contributors Plugin') # Test ability to list contributions # As of 2019-10-19 there is no more distinction between commands and non-commands self.assertRegexp('contributors Plugin skorobeus', 'original contributors command') self.assertRegexp('contributors Plugin Kevin Murphy', 'original contributors command') self.assertRegexp('contributors Plugin James Lu', 'refactored contributors command') # Test handling of the plugin author, who is usually not listed in __contributors__ self.assertRegexp('contributors Plugin jemfinch', 'wrote the Plugin plugin') self.assertRegexp('contributors Plugin Jeremy Fincher', 'wrote the Plugin plugin') # TODO: test handling of a person with multiple contributions to a command # Test handling of invalid plugin self.assertRegexp('contributors InvalidPlugin', 'not a valid plugin') # Test handling of unknown person. As of 2019-10-19 it doesn't matter whether # they're listed in supybot.authors or not. self.assertRegexp('contributors Plugin noname', 'not listed as a contributor') self.assertRegexp('contributors Plugin bwp', 'not listed as a contributor') def testContributorsIsCaseInsensitive(self): self.assertNotError('contributors Plugin Skorobeus') self.assertNotError('contributors Plugin sKoRoBeUs') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/PluginDownloader/�������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�020263� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/PluginDownloader/__init__.py��������������������������������������������0000644�0001750�0001750�00000005151�13634634532�022370� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2011, Valentin Lorentz # 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. ### """ This plugin allows you to quickly download and install a plugin from other repositories. """ 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__ = "" __author__ = supybot.authors.progval __maintainer__ = supybot.authors.limnoria_core # 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/PluginDownloader/download' from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/PluginDownloader/config.py����������������������������������������������0000644�0001750�0001750�00000004742�13634634532�022103� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2011, Valentin Lorentz # 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 from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('PluginDownloader') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('PluginDownloader', True) PluginDownloader = conf.registerPlugin('PluginDownloader') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(PluginDownloader, 'someConfigVariableName', # registry.Boolean(False, _("""Help for someConfigVariableName."""))) # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: ������������������������������limnoria-2020.03.17/plugins/PluginDownloader/locales/�����������������������������������������������0000755�0001750�0001750�00000000000�13634634547�021705� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/PluginDownloader/locales/de.po������������������������������������������0000644�0001750�0001750�00000002561�13634634532�022633� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2012-03-11 20:58+UTC\n" "PO-Revision-Date: 2012-04-27 15:39+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: German <fbesser@gmail.com>\n" "Language: de\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" #: plugin.py:219 msgid "" "Add the help for 'plugin help PluginDownloader' here\n" " This should describe *how* to use this plugin." msgstr "" #: plugin.py:224 msgid "" "[<repository>]\n" "\n" " Displays the list of plugins in the <repository>.\n" " If <repository> is not given, returns a list of available\n" " repositories." msgstr "" #: plugin.py:232 #: plugin.py:243 msgid ", " msgstr ", " #: plugin.py:234 #: plugin.py:253 msgid "This repository does not exist or is not known by this bot." msgstr "Diese Quelle existiert nicht, oder sie ist dem Bot nicht bekannt." #: plugin.py:241 msgid "No plugin found in this repository." msgstr "Kein Plugin in dieser Quelle gefunden." #: plugin.py:248 msgid "" "<repository> <plugin>\n" "\n" " Downloads and installs the <plugin> from the <repository>." msgstr "" #: plugin.py:258 msgid "This plugin does not exist in this repository." msgstr "Das Plugin ist in dieser Quelle nicht vorhanden." �����������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/PluginDownloader/locales/fi.po������������������������������������������0000644�0001750�0001750�00000010027�13634634532�022635� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# PluginDownloader plugin in Limnoria. # Copyright (C) 2011 Limnoria # Mikaela Suomalainen <mkaysi@outlook.com>, 2011, 2012. # msgid "" msgstr "" "Project-Id-Version: \n" "POT-Creation-Date: 2014-03-22 16:35+EET\n" "PO-Revision-Date: 2014-03-22 16:38+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: Finnish <>\n" "Language: fi\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" "Plural-Forms: nplurals=2; plural=(n!=1);\n" "X-Generator: Poedit 1.5.4\n" #: plugin.py:171 msgid "" "Plugin is probably not compatible with your Python version (3.x) and could " "not be converted because 2to3 is not installed." msgstr "" "Lisä-osa ei luultavasti ole yhteensopiva Python-versiosi (3.x) kanssa, eikä " "sitä voitu muuntaa, koska 2to3 ei ole asennettu." #: plugin.py:178 msgid "" "Plugin was designed for Python 2, but an attempt to convert it to Python 3 " "has been made. There is no guarantee it will work, though." msgstr "" "Lisä-osa suunniteltiin Python 2:lle, mutta yritys muuntaa se Python 3:lle on " "tehty. Ei kuitenkaan ole takuuta, että se toimisi." #: plugin.py:182 msgid "Plugin successfully installed." msgstr "Lisä-osa asennettiin onnistuneesti." #: plugin.py:307 msgid "" "This plugin allows you to install unofficial plugins from\n" " multiple repositories easily. Use the \"repolist\" command to see list " "of\n" " available repositories and \"repolist <repository>\" to list plugins, \n" " which are available in that repository. When you want to install " "plugin,\n" " just run command \"install <repository> <plugin>\"." msgstr "" "Tämä lisäosa sallii epävirallisten lisäosien asentamisen monista " "ohjelmistolähteistä\n" " helposti. Käytä \"repolist\" komentoa nähdäksesi listan saatavilla " "olevista\n" " ohjelmistolähteistä ja \"repolist <ohjelmistolähde>\" saadaksesi listan " "lisäosista,\n" " jotka ovat saatavilla kyseisessä ohjelmistolähteessä. Kun tahdot asentaa " "lisäosan,\n" " suorita vain komento \"install <ohjelmistolähde> <lisäosa>\"." #: plugin.py:317 msgid "" "[<repository>]\n" "\n" " Displays the list of plugins in the <repository>.\n" " If <repository> is not given, returns a list of available\n" " repositories." msgstr "" "[<ohjelmistolähde>]\n" "\n" " Näyttää listan lisäosista, jotka löytyvät <ohjelmistolähteestä>. Jos\n" " <ohjelmistolähdettä> ei ole annettu, palauttaa listan kaikista saataville " "olevista\n" " ohjelmistolähteistä." #: plugin.py:325 plugin.py:336 msgid ", " msgstr ", " #: plugin.py:327 plugin.py:346 plugin.py:371 msgid "This repository does not exist or is not known by this bot." msgstr "" "Tämä ohjelmistolähde ei ole olemassakaan tai tämä botti ei tiedä siitä." #: plugin.py:334 msgid "No plugin found in this repository." msgstr "Lisäosaa ei löytynyt tästä ohjelmistolähteestä." #: plugin.py:341 msgid "" "<repository> <plugin>\n" "\n" " Downloads and installs the <plugin> from the <repository>." msgstr "" "<ohjelmistolähde> <lisäosa>\n" "\n" " Lataa ja asentaa <lisäosan> <ohjelmistolähteestä>." #: plugin.py:351 plugin.py:376 msgid "This plugin does not exist in this repository." msgstr "Tämä lisäosa ei ole tässä ohjelmistolähteessä." #: plugin.py:366 msgid "" "<repository> <plugin>\n" "\n" " Displays informations on the <plugin> in the <repository>." msgstr "" "<ohjelmistolähde> <lisäosa>\n" "\n" " Näyttää tietoja <lisäosasta> <ohjelmistolähteessä>." #: plugin.py:380 msgid "No README found for this plugin." msgstr "Tämän lisäosan README-tiedostoa ei löydy." #: plugin.py:383 msgid "This plugin has no description." msgstr "Tällä lisäosalla ei ole kuvausta." #~ msgid "" #~ "Add the help for 'plugin help PluginDownloader' here\n" #~ " This should describe *how* to use this plugin." #~ msgstr "" #~ "Lisää ohjeteksti komennolle 'plugin help PluginDownloader' tähän.\n" #~ " Tämän pitäisi kuvata *kuinka* tätä lisäosaa käytetään." ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/PluginDownloader/locales/fr.po������������������������������������������0000644�0001750�0001750�00000007213�13634634532�022651� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# French translation for limnoria # Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 # This file is distributed under the same license as the limnoria package. # FIRST AUTHOR <EMAIL@ADDRESS>, 2012. # msgid "" msgstr "" "Project-Id-Version: limnoria\n" "Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n" "POT-Creation-Date: 2014-01-21 16:39+CET\n" "PO-Revision-Date: 2014-01-22 07:52+0100\n" "Last-Translator: \n" "Language-Team: French <fr@li.org>\n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2012-04-19 19:44+0000\n" "X-Generator: Poedit 1.5.4\n" #: plugin.py:171 msgid "" "Plugin is probably not compatible with your Python version (3.x) but could " "not be converted because 2to3 is not installed." msgstr "" "Ce plugin n’est probablement pas compatible avec votre version de Python (3." "x), mais il n’a pas pu être converti car 2to3 n’est pas installé." #: plugin.py:178 msgid "" "Plugin was designed for Python 2, but an attempt to convert it to Python 3 " "has been made. There is no guarantee it will work, though." msgstr "" "Ce plugin a été conçu pour Python 2, mais une tentative de conversion à " "Python 3 a été faite. Cependant, il n’y a pas de garantie que le plugin " "fonctionnera." #: plugin.py:182 msgid "Plugin successfully installed." msgstr "Plugin installé avec succès." #: plugin.py:299 msgid "" "This plugin allows you to install unofficial plugins from\n" " multiple repositories easily. Use the \"repolist\" command to see list " "of\n" " available repositories and \"repolist <repository>\" to list plugins, \n" " which are available in that repository. When you want to install " "plugin,\n" " just run command \"install <repository> <plugin>\"." msgstr "" "Ce plugin vous permet d'installer des plugins non officiels depuis de " "multiples dépôts facilement. Utilisez la commande \"repolist\" pour avoir " "une liste des dépôts disponibles et \"repolist <dépôt>\" pour lister les " "plugins qui sont disponibles dans le dépôt. Lorsque vous voulez installer un " "plugin, utilisez simplement la commande \"install <dépôt> <plugin>\"." #: plugin.py:309 msgid "" "[<repository>]\n" "\n" " Displays the list of plugins in the <repository>.\n" " If <repository> is not given, returns a list of available\n" " repositories." msgstr "" "[<dépôt>]\n" "\n" "Affiche une liste des plugins du <dépôt>. Si <dépôt> n'est pas donné, " "retourne une liste des dépôts disponibles." #: plugin.py:317 plugin.py:328 msgid ", " msgstr ", " #: plugin.py:319 plugin.py:338 plugin.py:363 msgid "This repository does not exist or is not known by this bot." msgstr "Ce dépôt n'existe pas ou n'est pas connu de ce bot." #: plugin.py:326 msgid "No plugin found in this repository." msgstr "Aucun plugin trouvé dans ce dépôt." #: plugin.py:333 msgid "" "<repository> <plugin>\n" "\n" " Downloads and installs the <plugin> from the <repository>." msgstr "" "<dépôt> <plugin>\n" "\n" "Télécharge et installe le <plugin> depuis le <dépôt>." #: plugin.py:343 plugin.py:368 msgid "This plugin does not exist in this repository." msgstr "Ce plugin n'existe pas dans ce dépôt." #: plugin.py:358 msgid "" "<repository> <plugin>\n" "\n" " Displays informations on the <plugin> in the <repository>." msgstr "" "<dépôt> <plugin>\n" "\n" "Affiche des informations sur <plugin> depuis le <dépôt>." #: plugin.py:372 msgid "No README found for this plugin." msgstr "Aucun README trouvé pour ce plugin." #: plugin.py:375 msgid "This plugin has no description." msgstr "Ce plugin n'a pas de description." �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/PluginDownloader/locales/it.po������������������������������������������0000644�0001750�0001750�00000005243�13634634532�022657� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2012-03-11 20:58+UTC\n" "PO-Revision-Date: 2012-05-04 01:17+020\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugin.py:260 #, docstring msgid "" "This plugin allows you to install unofficial plugins from\n" " multiple repositories easily. Use the \"repolist\" command to see list of\n" " available repositories and \"repolist <repository>\" to list plugins, \n" " which are available in that repository. When you want to install plugin,\n" " just run command \"install <repository> <plugin>\"." msgstr "" "Questo plugin permette di installare facilmente plugin non ufficiali da vari\n" " repository. Usare il comando \"repolist\" per ottenere l'elenco dei repository\n" " disponibili e \"repolist <repository>\" per elencare i plugin disponibili nel\n" " dato repository. Per installare il plugin basta eseguire il comando\n" " \"install <repository> <plugin>\"." #: plugin.py:268 #, docstring msgid "" "[<repository>]\n" "\n" " Displays the list of plugins in the <repository>.\n" " If <repository> is not given, returns a list of available\n" " repositories." msgstr "" "[<repository>]\n" "\n" " Mostra l'elenco dei plugin presenti in <repository>.\n" " Se <repository> non è specificato riporta una lista di quelli disponibili." #: plugin.py:276 plugin.py:287 msgid ", " msgstr ", " #: plugin.py:278 plugin.py:297 plugin.py:322 msgid "This repository does not exist or is not known by this bot." msgstr "Questo repository non esiste o non è riconosciuto." #: plugin.py:285 msgid "No plugin found in this repository." msgstr "Nessun plugin trovato in questo repository." #: plugin.py:292 #, docstring msgid "" "<repository> <plugin>\n" "\n" " Downloads and installs the <plugin> from the <repository>." msgstr "" "<repository> <plugin>\n" "\n" " Scarica e installa <plugin> da <repository>." #: plugin.py:302 plugin.py:327 msgid "This plugin does not exist in this repository." msgstr "Il plugin non esiste in questo repository." #: plugin.py:317 #, docstring msgid "" "<repository> <plugin>\n" "\n" " Displays informations on the <plugin> in the <repository>." msgstr "" "<repository> <plugin>\n" "\n" " Visualizza informazioni riguardo <plugin> in <repository>." #: plugin.py:331 msgid "No README found for this plugin" msgstr "Non è stato trovato nessun file README per questo plugin." #: plugin.py:334 msgid "This plugin has no description." msgstr "Questo plugin non ha una descrizione." �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/PluginDownloader/plugin.py����������������������������������������������0000644�0001750�0001750�00000047423�13634634532�022137� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2011, Valentin Lorentz # 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 os import io import sys import json import shutil import tarfile import supybot.log as log import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.utils.minisix as minisix import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('PluginDownloader') class Repository: pass class VersionnedRepository(Repository): pass class GitRepository(VersionnedRepository): pass class GithubRepository(GitRepository): def __init__(self, username, reponame, path='/', branch='master'): self._username = username self._reponame = reponame self._branch = branch if not path.startswith('/'): path = '/' + path if not path.endswith('/'): path += '/' self._path = path self._downloadUrl = 'https://github.com/%s/%s/tarball/%s' % \ ( self._username, self._reponame, self._branch ) _apiUrl = 'https://api.github.com' def _query(self, type_, uri_end, args={}): args = dict([(x,y) for x,y in args.items() if y is not None]) url = '%s/%s/%s?%s' % (self._apiUrl, type_, uri_end, utils.web.urlencode(args)) return json.loads(utils.web.getUrl(url).decode('utf8')) def getPluginList(self): plugins = self._query('repos', '%s/%s/contents%s' % (self._username, self._reponame, self._path), args={'ref': self._branch} ) if plugins is None: log.error(( 'Cannot get plugins list from repository %s/%s ' 'at Github' ) % (self._username, self._reponame)) return [] plugins = [x['name'] for x in plugins if x['type'] == 'dir'] return plugins def _download(self, plugin): try: response = utils.web.getUrlFd(self._downloadUrl) if minisix.PY2: assert response.getcode() == 200, response.getcode() else: assert response.status == 200, response.status fileObject = minisix.io.BytesIO() fileObject.write(response.read()) finally: # urllib does not handle 'with' statements :( response.close() fileObject.seek(0) return tarfile.open(fileobj=fileObject, mode='r:gz') def install(self, plugin): archive = self._download(plugin) prefix = archive.getnames()[0] dirname = ''.join((self._path, plugin)) directories = conf.supybot.directories.plugins() directory = self._getWritableDirectoryFromList(directories) assert directory is not None, \ 'No valid directory in supybot.directories.plugins.' try: assert archive.getmember(prefix + dirname).isdir(), \ 'This is not a valid plugin (it is a file, not a directory).' run_2to3 = minisix.PY3 for file in archive.getmembers(): if file.name.startswith(prefix + dirname): extractedFile = archive.extractfile(file) newFileName = os.path.join(*file.name.split('/')[1:]) newFileName = newFileName[len(self._path)-1:] newFileName = os.path.join(directory, newFileName) if os.path.exists(newFileName): assert os.path.isdir(newFileName), newFileName + \ 'should not be a file.' shutil.rmtree(newFileName) if extractedFile is None: os.mkdir(newFileName) else: with open(newFileName, 'ab') as fd: reload_imported = False for line in extractedFile.readlines(): if minisix.PY3: if b'import reload' in line: reload_imported = True elif not reload_imported and \ b'reload(' in line: fd.write(b'from importlib import reload\n') reload_imported = True fd.write(line) if newFileName.endswith('__init__.py'): with open(newFileName) as fd: lines = list(filter(lambda x:'import plugin' in x, fd.readlines())) if lines and lines[0].startswith('from . import'): # This should be already Python 3-compatible run_2to3 = False finally: archive.close() del archive if run_2to3: try: import lib2to3 except ImportError: return _('Plugin is probably not compatible with your ' 'Python version (3.x) and could not be converted ' 'because 2to3 is not installed.') import subprocess fixers = [] subprocess.Popen(['2to3', '-wn', os.path.join(directory, plugin)]) \ .wait() return _('Plugin was designed for Python 2, but an attempt to ' 'convert it to Python 3 has been made. There is no ' 'guarantee it will work, though.') else: return _('Plugin successfully installed.') def getInfo(self, plugin): archive = self._download(plugin) prefix = archive.getnames()[0] dirname = ''.join((self._path, plugin)) for file in archive.getmembers(): if file.name.startswith(prefix + dirname + '/README'): extractedFile = archive.extractfile(file) content = extractedFile.read() if minisix.PY3: content = content.decode() return content def _getWritableDirectoryFromList(self, directories): for directory in directories: if os.access(directory, os.W_OK): return directory return None repositories = utils.InsensitivePreservingDict({ 'ProgVal': GithubRepository( 'ProgVal', 'Supybot-plugins' ), 'quantumlemur': GithubRepository( 'quantumlemur', 'Supybot-plugins', ), 'stepnem': GithubRepository( 'stepnem', 'supybot-plugins', ), 'code4lib-snapshot':GithubRepository( 'code4lib', 'supybot-plugins', 'Supybot-plugins-20060723', ), 'code4lib-edsu': GithubRepository( 'code4lib', 'supybot-plugins', 'edsu-plugins', ), 'code4lib': GithubRepository( 'code4lib', 'supybot-plugins', 'plugins', ), 'nanotube-bitcoin': GithubRepository( 'nanotube', 'supybot-bitcoin-' 'marketmonitor', ), 'mtughan-weather': GithubRepository( 'mtughan', 'Supybot-Weather', ), 'SpiderDave': GithubRepository( 'SpiderDave', 'spidey-supybot-plugins', 'Plugins', ), 'doorbot': GithubRepository( 'hacklab', 'doorbot', ), 'boombot': GithubRepository( 'nod', 'boombot', 'plugins', ), 'mailed-notifier': GithubRepository( 'tbielawa', 'supybot-mailed-notifier', ), 'pingdom': GithubRepository( 'rynop', 'supyPingdom', 'plugins', ), 'scrum': GithubRepository( 'amscanne', 'supybot-scrum', ), 'Hoaas': GithubRepository( 'Hoaas', 'Supybot-plugins' ), 'nyuszika7h': GithubRepository( 'nyuszika7h', 'limnoria-plugins' ), 'nyuszika7h-old': GithubRepository( 'nyuszika7h', 'Supybot-plugins' ), 'resistivecorpse': GithubRepository( 'resistivecorpse', 'supybot-plugins' ), 'frumious': GithubRepository( 'frumiousbandersnatch', 'sobrieti-plugins', 'plugins', ), 'jonimoose': GithubRepository( 'Jonimoose', 'Supybot-plugins', ), 'skgsergio': GithubRepository( 'skgsergio', 'Limnoria-plugins', ), 'jlu5': GithubRepository( 'jlu5', 'SupyPlugins', ), 'jlu5-py2legacy': GithubRepository( 'jlu5', 'SupyPlugins', branch='python2-legacy' ), 'GLolol': GithubRepository( 'jlu5', 'SupyPlugins', ), 'GLolol-py2legacy': GithubRepository( 'jlu5', 'SupyPlugins', branch='python2-legacy' ), 'Iota': GithubRepository( 'IotaSpencer', 'supyplugins', ), 'waratte': GithubRepository( 'waratte', 'supybot', ), 't3chguy': GithubRepository( 't3chguy', 'Limnoria-Plugins', ), 'prgmrbill': GithubRepository( 'prgmrbill', 'limnoria-plugins', ), 'fudster': GithubRepository( 'fudster', 'supybot-plugins', ), 'oddluck': GithubRepository( 'oddluck', 'limnoria-plugins', ), }) class PluginDownloader(callbacks.Plugin): """This plugin allows you to install unofficial plugins from multiple repositories easily. Use the "repolist" command to see list of available repositories and "repolist <repository>" to list plugins, which are available in that repository. When you want to install plugin, just run command "install <repository> <plugin>".""" threaded = True @internationalizeDocstring def repolist(self, irc, msg, args, repository): """[<repository>] Displays the list of plugins in the <repository>. If <repository> is not given, returns a list of available repositories.""" global repositories if repository is None: irc.reply(_(', ').join(sorted(x for x in repositories))) elif repository not in repositories: irc.error(_( 'This repository does not exist or is not known by ' 'this bot.' )) else: plugins = repositories[repository].getPluginList() if plugins == []: irc.error(_('No plugin found in this repository.')) else: irc.reply(_(', ').join([x for x in plugins])) repolist = wrap(repolist, [optional('something')]) @internationalizeDocstring def install(self, irc, msg, args, repository, plugin): """<repository> <plugin> Downloads and installs the <plugin> from the <repository>.""" if not conf.supybot.commands.allowShell(): irc.error(_('This command is not available, because ' 'supybot.commands.allowShell is False.'), Raise=True) global repositories if repository not in repositories: irc.error(_( 'This repository does not exist or is not known by ' 'this bot.' )) elif plugin not in repositories[repository].getPluginList(): irc.error(_('This plugin does not exist in this repository.')) else: try: irc.reply(repositories[repository].install(plugin)) except Exception as e: import traceback traceback.print_exc() log.error(str(e)) irc.error('The plugin could not be installed. Check the logs ' 'for a more detailed error.') install = wrap(install, ['owner', 'something', 'something']) @internationalizeDocstring def info(self, irc, msg, args, repository, plugin): """<repository> <plugin> Displays informations on the <plugin> in the <repository>.""" global repositories if repository not in repositories: irc.error(_( 'This repository does not exist or is not known by ' 'this bot.' )) elif plugin not in repositories[repository].getPluginList(): irc.error(_('This plugin does not exist in this repository.')) else: info = repositories[repository].getInfo(plugin) if info is None: irc.error(_('No README found for this plugin.')) else: if info.startswith('Insert a description of your plugin here'): irc.error(_('This plugin has no description.')) else: info = info.split('\n\n')[0] irc.reply(info.replace('\n', ' ')) info = wrap(info, ['something', optional('something')]) PluginDownloader = internationalizeDocstring(PluginDownloader) Class = PluginDownloader # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/PluginDownloader/test.py������������������������������������������������0000644�0001750�0001750�00000011536�13634634532�021614� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2011, Valentin Lorentz # 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 os import sys import shutil from supybot.test import * import supybot.utils.minisix as minisix pluginsPath = '%s/test-plugins' % os.getcwd() class PluginDownloaderTestCase(PluginTestCase): plugins = ('PluginDownloader',) config = {'supybot.directories.plugins': [pluginsPath]} def setUp(self): PluginTestCase.setUp(self) try: shutil.rmtree(pluginsPath) except: pass os.mkdir(pluginsPath) def tearDown(self): try: shutil.rmtree(pluginsPath) finally: PluginTestCase.tearDown(self) def _testPluginInstalled(self, name): assert os.path.isdir(pluginsPath + '/%s/' % name) assert os.path.isfile(pluginsPath + '/%s/plugin.py' % name) assert os.path.isfile(pluginsPath + '/%s/config.py' % name) def testRepolist(self): self.assertRegexp('repolist', '(.*, )?ProgVal(, .*)?') self.assertRegexp('repolist', '(.*, )?quantumlemur(, .*)?') self.assertRegexp('repolist ProgVal', '(.*, )?AttackProtector(, .*)?') def testInstallProgVal(self): self.assertError('plugindownloader install ProgVal Darcs') self.assertNotError('plugindownloader install ProgVal AttackProtector') self.assertError('plugindownloader install ProgVal Darcs') self._testPluginInstalled('AttackProtector') def testShellForbidden(self): with conf.supybot.commands.allowShell.context(False): self.assertRegexp('plugindownloader install ProgVal Darcs', 'Error:.*not available.*supybot.commands.allowShell') def testInstallQuantumlemur(self): self.assertError('plugindownloader install quantumlemur AttackProtector') self.assertNotError('plugindownloader install quantumlemur Listener') self.assertError('plugindownloader install quantumlemur AttackProtector') self._testPluginInstalled('Listener') def testInstallStepnem(self): self.assertNotError('plugindownloader install stepnem Freenode') self._testPluginInstalled('Freenode') def testInstallNanotubeBitcoin(self): self.assertNotError('plugindownloader install nanotube-bitcoin GPG') self._testPluginInstalled('GPG') def testInstallMtughanWeather(self): self.assertNotError('plugindownloader install mtughan-weather ' 'WunderWeather') self._testPluginInstalled('WunderWeather') def testInstallSpiderDave(self): self.assertNotError('plugindownloader install SpiderDave Pastebin') self._testPluginInstalled('Pastebin') def testInstallNonAsciiInit(self): self.assertNotError('plugindownloader install Hoaas DuckDuckGo') self._testPluginInstalled('DuckDuckGo') def testInfo(self): self.assertResponse('plugindownloader info ProgVal Twitter', 'Advanced Twitter plugin for Supybot, with capabilities ' 'handling, and per-channel user account.') if minisix.PY3: def test_2to3(self): self.assertRegexp('plugindownloader install SpiderDave Pastebin', 'convert') self.assertNotError('load Pastebin') if not network: class PluginDownloaderTestCase(PluginTestCase): pass # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: ������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Praise/�����������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016231� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Praise/__init__.py������������������������������������������������������0000644�0001750�0001750�00000004664�13634634532�020346� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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. ### """ Hand out praise to IRC denizens with this plugin. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.strike __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������limnoria-2020.03.17/plugins/Praise/config.py��������������������������������������������������������0000644�0001750�0001750�00000005133�13634634532�020044� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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 from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Praise') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Praise', True) Praise = conf.registerPlugin('Praise') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Praise, 'someConfigVariableName', # registry.Boolean(False, _("""Help for someConfigVariableName."""))) conf.registerChannelValue(Praise, 'showIds', registry.Boolean(False, _("""Determines whether the bot will show the ids of a praise when the praise is given."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Praise/locales/���������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017653� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Praise/locales/fi.po����������������������������������������������������0000644�0001750�0001750�00000004451�13634634532�020607� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: \n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-07-24 15:36+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\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" #: config.py:49 msgid "" "Determines whether the bot will show the ids of\n" " a praise when the praise is given." msgstr "" "Määrittää näyttääkö botti ylistysten id:t, kun\n" " ylistys annetaan." #: plugin.py:39 msgid "" "Praise is a plugin for ... well, praising things. Feel free to add\n" " your own flavor to it by customizing what praises it gives. Use \"praise\n" " add <text>\" to add new ones, making sure to include \"$who\" in <text> where\n" " you want to insert the thing being praised.\n" " " msgstr "" "Praise on lisäosa ... no, ylistämään asioita. Lisää vapaasti\n" " omaa suosiotasi muokkaamalla mitä ylistyksiä se antaa. Käytä komentoa \"praise\n" " add <teksti>\" lisätäksesi uusia ylistyksiä, varmistaen, että sisällytät \"$who\"n <tekstissä> mihin\n" " haluat laittaa ylistettävän asian.\n" " " #: plugin.py:53 msgid "Praises must contain $who." msgstr "Ylistyksien täytyy sisältää $who." #: plugin.py:57 msgid "" "[<channel>] [<id>] <who|what> [for <reason>]\n" "\n" " Praises <who|what> (for <reason>, if given). If <id> is given, uses\n" " that specific praise. <channel> is only necessary if the message isn't\n" " sent in the channel itself.\n" " " msgstr "" "[<kanava>] [<id>] <kuka|mikä> [for <syystä>]\n" "\n" " Ylistää <ketä|mitä> (<syystä>, jos annettu). Jos <id> on annettu, käyttää\n" " juuri sitä tiettyä ylistystä. <Kanava> on vaadittu vain jos viestiä ei lähetetä\n" " kanavalla itsellään.\n" " " #: plugin.py:73 msgid "There is no praise with id #%i." msgstr "Ylistystä id:llä #%i ei löydy." #: plugin.py:78 msgid "There are no praises in my database for %s." msgstr "Minun tietokannassani ei ole ylistyksiä %s:lle." #: plugin.py:86 msgid " for " msgstr "varten" �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Praise/locales/fr.po����������������������������������������������������0000644�0001750�0001750�00000004261�13634634532�020617� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2010-10-17 18:33+CEST\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\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" #: config.py:49 msgid "" "Determines whether the bot will show the ids of\n" " a praise when the praise is given." msgstr "Détermine si le bot affichera les ids des louanges lorsqu'une louange est donnée." #: plugin.py:40 msgid "" "Praise is a plugin for ... well, praising things. Feel free to add\n" " your own flavor to it by customizing what praises it gives. Use \"praise\n" " add <text>\" to add new ones, making sure to include \"$who\" in <text> where\n" " you want to insert the thing being praised.\n" " " msgstr "Praise est un plugin pour... mmh... louer des gens et des choses. Sentez-vous libre de personnaliser à votre goût pourquoi vous louez ceci. Utilisez \"praise add\" pour ajouter les vôtres, et assurez-vous d'ajouter \"$who\" dans le <texte> là où vous voulez insérer le nom de la chose à louer." #: plugin.py:54 msgid "Praises must contain $who." msgstr "Les louanges doivent contenir $who" #: plugin.py:58 msgid "" "[<channel>] [<id>] <who|what> [for <reason>]\n" "\n" " Praises <who|what> (for <reason>, if given). If <id> is given, uses\n" " that specific praise. <channel> is only necessary if the message isn't\n" " sent in the channel itself.\n" " " msgstr "" "[<canal>] [<id>] <qui|quoi> [for <raison>]\n" "\n" "Loue <qui|quoi> (pour la <raison>, si elle est donnée). Si l'<id> est donné, utilise une louange spécifique. <canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:74 msgid "There is no praise with id #%i." msgstr "Il n'y a pas de louange d'id #%i" #: plugin.py:79 msgid "There are no praises in my database for %s." msgstr "Il n'y a pas de louange dans ma base de données pour %s." #: plugin.py:87 msgid " for " msgstr " pour " �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Praise/locales/it.po����������������������������������������������������0000644�0001750�0001750�00000004221�13634634532�020620� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-15 09:54+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:49 msgid "" "Determines whether the bot will show the ids of\n" " a praise when the praise is given." msgstr "" "Determina se il bot mostrerà gli id\n" " di un elogio quando questo viene dato." #: plugin.py:39 #, docstring msgid "" "Praise is a plugin for ... well, praising things. Feel free to add\n" " your own flavor to it by customizing what praises it gives. Use \"praise\n" " add <text>\" to add new ones, making sure to include \"$who\" in <text> where\n" " you want to insert the thing being praised.\n" " " msgstr "" "Praise è un plugin per ... beh, elogiare cose. Sentiti libero/a di personalizzarlo\n" " modificando gli elogi; utilizzando \"praise\" add <testo>\" per aggiungerne\n" " di nuovi e accertandoti di includere \"$who\" alla posizione del <testo> in cui\n" " desideri che il soggetto venga elogiato.\n" " " #: plugin.py:53 msgid "Praises must contain $who." msgstr "Gli elogi devono contenere $who." #: plugin.py:57 #, docstring msgid "" "[<channel>] [<id>] <who|what> [for <reason>]\n" "\n" " Praises <who|what> (for <reason>, if given). If <id> is given, uses\n" " that specific praise. <channel> is only necessary if the message isn't\n" " sent in the channel itself.\n" " " msgstr "" "[<canale>] [<id>] <chi|cosa> [per <motivo>]\n" "\n" " Elogia <chi|cosa> (per il <motivo>, se fornito). Se <id> viene dato, usa\n" " quello specifico elogio. <canale> è necessario solo se il messaggio non\n" " viene inviato nel canale stesso.\n" " " #: plugin.py:73 msgid "There is no praise with id #%i." msgstr "Non c'è nessun elogio con l'id #%i." #: plugin.py:78 msgid "There are no praises in my database for %s." msgstr "Non ci sono elogi per %s nel mio database." #: plugin.py:86 msgid " for " msgstr " per" �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Praise/plugin.py��������������������������������������������������������0000644�0001750�0001750�00000010126�13634634532�020073� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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 re from supybot.commands import * import supybot.plugins as plugins import supybot.ircutils as ircutils from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Praise') class Praise(plugins.ChannelIdDatabasePlugin): """Praise is a plugin for ... well, praising things. Feel free to add your own flavor to it by customizing what praises it gives. Use "praise add <text>" to add new ones, making sure to include "$who" in <text> where you want to insert the thing being praised. """ _meRe = re.compile(r'\bme\b', re.I) _myRe = re.compile(r'\bmy\b', re.I) def _replaceFirstPerson(self, s, nick): s = self._meRe.sub(nick, s) s = self._myRe.sub('%s\'s' % nick, s) return s def addValidator(self, irc, text): if '$who' not in text: irc.error(_('Praises must contain $who.'), Raise=True) @internationalizeDocstring def praise(self, irc, msg, args, channel, id, text): """[<channel>] [<id>] <who|what> [for <reason>] Praises <who|what> (for <reason>, if given). If <id> is given, uses that specific praise. <channel> is only necessary if the message isn't sent in the channel itself. """ if ' for ' in text: (target, reason) = list(map(str.strip, text.split(' for ', 1))) else: (target, reason) = (text, '') if ircutils.strEqual(target, irc.nick): target = 'itself' if id is not None: try: praise = self.db.get(channel, id) except KeyError: irc.error(format(_('There is no praise with id #%i.'), id)) return else: praise = self.db.random(channel) if not praise: irc.error(format(_('There are no praises in my database ' \ 'for %s.'), channel)) return text = self._replaceFirstPerson(praise.text, msg.nick) reason = self._replaceFirstPerson(reason, msg.nick) target = self._replaceFirstPerson(target, msg.nick) text = text.replace('$who', target) if reason: text += _(' for ') + reason if self.registryValue('showIds', channel, irc.network): text += format(' (#%i)', praise.id) irc.reply(text, action=True) praise = wrap(praise, ['channeldb', optional('id'), 'text']) Praise = internationalizeDocstring(Praise) Class = Praise # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Praise/test.py����������������������������������������������������������0000644�0001750�0001750�00000003565�13634634532�017565� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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 PraiseTestCase(ChannelPluginTestCase): plugins = ('Praise',) def testAdd(self): self.assertError('praise add foo') # needs $who def testPraise(self): self.assertError('praise foo') # no praises! # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Protector/��������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016767� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Protector/__init__.py���������������������������������������������������0000644�0001750�0001750�00000004732�13634634532�021100� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Defends a channel against actions by people who don't have the proper capabilities. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������limnoria-2020.03.17/plugins/Protector/config.py�����������������������������������������������������0000644�0001750�0001750�00000005427�13634634532�020610� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.ircutils as ircutils import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Protector') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Protector', True) Protector = conf.registerPlugin('Protector') conf.registerChannelValue(Protector, 'enable', registry.Boolean(False, _("""Determines whether this plugin is enabled in a given channel."""))) class ImmuneNicks(conf.ValidNicks): List = ircutils.IrcSet conf.registerChannelValue(Protector, 'immune', ImmuneNicks([], _("""Determines what nicks the bot will consider to be immune from enforcement. These nicks will not even have their actions watched by this plugin. In general, only the ChanServ for this network will be in this list."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Protector/locales/������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�020411� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Protector/locales/fi.po�������������������������������������������������0000644�0001750�0001750�00000003131�13634634532�021337� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. msgid "" msgstr "" "Project-Id-Version: Protector plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 11:59+EET\n" "PO-Revision-Date: 2014-12-20 12:09+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.10\n" #: config.py:47 msgid "" "Determines whether this plugin is enabled in a\n" " given channel." msgstr "" "Määrittää onko tämä lisäosa käytössä\n" " annetulla kanavalla." #: config.py:54 msgid "" "Determines what nicks the bot will consider to\n" " be immune from enforcement. These nicks will not even have their " "actions\n" " watched by this plugin. In general, only the ChanServ for this network\n" " will be in this list." msgstr "" "Määrittää nimimerkit, jotka botti ajattelee\n" " immuuneiksi tästä pakotuksesta. Näiden nimimerkkien toiminnot jäävät\n" " tämän lisäosan valvonnan ulkopuolisiksi. Yleisesti, vain tämän verkon " "ChanServ tulee olemaan\n" " tässä listassa." #: plugin.py:39 msgid "" "Prevents users from doing things they are not supposed to do on a channel,\n" " even if they have +o or +h." msgstr "" "Estää käyttäjiä tekemästä asioita, joita heidän ei ole tarkoitus tehdä " "kanavalla,\n" " vaikka heillä on +o tai +h." ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Protector/locales/fr.po�������������������������������������������������0000644�0001750�0001750�00000002070�13634634532�021351� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2010-10-17 18:34+CEST\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\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" #: config.py:47 msgid "" "Determines whether this plugin is enabled in a\n" " given channel." msgstr "Détermine si le plugin est activé sur un canal donné." #: config.py:54 msgid "" "Determines what nicks the bot will consider to\n" " be immune from enforcement. These nicks will not even have their actions\n" " watched by this plugin. In general, only the ChanServ for this network\n" " will be in this list." msgstr "Détermine quels nicks le bot considérera comme imunisés. Les actions de ces nicks ne seront pas surveillés par ce plugin. En général, seul le ChanServ de ce réseau doit être dans cette liste." ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Protector/locales/it.po�������������������������������������������������0000644�0001750�0001750�00000001750�13634634532�021362� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2012-07-25 23:33+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:47 msgid "" "Determines whether this plugin is enabled in a\n" " given channel." msgstr "" "Determina se il plugin è abilitato in un dato canale." #: config.py:54 msgid "" "Determines what nicks the bot will consider to\n" " be immune from enforcement. These nicks will not even have their actions\n" " watched by this plugin. In general, only the ChanServ for this network\n" " will be in this list." msgstr "" "Determina quali nick il bot considererà immuni, le azioni di questi nick non\n" " verranno monitorate dal plugin. In linea di massima sarà presente nella lista\n" " solo il ChanServ della rete.\n" ������������������������limnoria-2020.03.17/plugins/Protector/plugin.py�����������������������������������������������������0000644�0001750�0001750�00000016602�13634634532�020636� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.utils as utils import supybot.ircdb as ircdb import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Protector') class Protector(callbacks.Plugin): """Prevents users from doing things they are not supposed to do on a channel, even if they have +o or +h.""" def isImmune(self, irc, msg): if not ircutils.isUserHostmask(msg.prefix): self.log.debug('%q is immune, it\'s a server.', msg) return True # It's a server prefix. if ircutils.strEqual(msg.nick, irc.nick): self.log.debug('%q is immune, it\'s me.', msg) return True # It's the bot itself. if msg.nick in self.registryValue('immune', msg.channel, irc.network): self.log.debug('%q is immune, it\'s configured to be immune.', msg) return True return False def isOp(self, irc, channel, hostmask): cap = ircdb.makeChannelCapability(channel, 'op') if ircdb.checkCapability(hostmask, cap): self.log.debug('%s is an op on %s, it has %s.', hostmask, channel, cap) return True if ircutils.strEqual(hostmask, irc.prefix): return True return False def isProtected(self, irc, channel, hostmask): cap = ircdb.makeChannelCapability(channel, 'protected') if ircdb.checkCapability(hostmask, cap): self.log.debug('%s is protected on %s, it has %s.', hostmask, channel, cap) return True if ircutils.strEqual(hostmask, irc.prefix): return True return False def demote(self, irc, channel, nick): irc.queueMsg(ircmsgs.deop(channel, nick)) def __call__(self, irc, msg): def ignore(reason): self.log.debug('Ignoring %q, %s.', msg, reason) if not msg.args: ignore('no msg.args') elif not msg.channel: ignore('not on a channel') elif not self.registryValue('enable', msg.channel, irc.network): ignore('supybot.plugins.Protector.enable is False.') elif msg.channel not in irc.state.channels: # One has to wonder how this would happen, but just in case... ignore('bot isn\'t in channel') elif irc.nick not in irc.state.channels[msg.channel].ops: ignore('bot is not opped') elif msg.nick not in irc.state.channels[msg.channel].users: ignore('sender is not in channel (ChanServ, maybe?)') elif msg.nick not in irc.state.channels[msg.channel].ops: ignore('sender is not an op in channel (IRCOP, maybe?)') elif self.isImmune(irc, msg): ignore('sender is immune') else: super(Protector, self).__call__(irc, msg) def doMode(self, irc, msg): channel = msg.channel chanOp = ircdb.makeChannelCapability(channel, 'op') chanVoice = ircdb.makeChannelCapability(channel, 'voice') chanHalfOp = ircdb.makeChannelCapability(channel, 'halfop') if not ircdb.checkCapability(msg.prefix, chanOp): irc.sendMsg(ircmsgs.deop(channel, msg.nick)) for (mode, value) in ircutils.separateModes(msg.args[1:]): if not value: continue if ircutils.strEqual(value, msg.nick): # We allow someone to mode themselves to oblivion. continue if irc.isNick(value): hostmask = irc.state.nickToHostmask(value) if mode == '+o': if not self.isOp(irc, channel, hostmask): irc.queueMsg(ircmsgs.deop(channel, value)) elif mode == '+h': if not ircdb.checkCapability(hostmask, chanHalfOp): irc.queueMsg(ircmsgs.dehalfop(channel, value)) elif mode == '+v': if not ircdb.checkCapability(hostmask, chanVoice): irc.queueMsg(ircmsgs.devoice(channel, value)) elif mode == '-o': if ircdb.checkCapability(hostmask, chanOp): irc.queueMsg(ircmsgs.op(channel, value)) elif mode == '-h': if ircdb.checkCapability(hostmask, chanOp): irc.queueMsg(ircmsgs.halfop(channel, value)) elif mode == '-v': if ircdb.checkCapability(hostmask, chanOp): irc.queueMsg(ircmsgs.voice(channel, value)) else: assert ircutils.isUserHostmask(value) # Handle bans. def doKick(self, irc, msg): channel = msg.channel kicked = msg.args[1].split(',') protected = [] for nick in kicked: if ircutils.strEqual(nick, irc.nick): return # Channel will handle the rejoin. for nick in kicked: hostmask = irc.state.nickToHostmask(nick) if self.isProtected(irc, channel, hostmask): self.log.info('%s was kicked from %s and is protected; ' 'inviting back.', hostmask, channel) hostmask = '%s!%s' % (nick, irc.state.nickToHostmask(nick)) protected.append(nick) bans = [] for banmask in irc.state.channels[channel].bans: if ircutils.hostmaskPatternEqual(banmask, hostmask): bans.append(banmask) irc.queueMsg(ircmsgs.unbans(channel, bans)) irc.queueMsg(ircmsgs.invite(nick, channel)) if not self.isOp(irc, channel, msg.prefix): self.demote(irc, channel, msg.nick) Class = Protector # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Protector/test.py�������������������������������������������������������0000644�0001750�0001750�00000003321�13634634532�020311� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * class ProtectorTestCase(PluginTestCase): plugins = ('Protector',) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Quote/������������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016103� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Quote/__init__.py�������������������������������������������������������0000644�0001750�0001750�00000004660�13634634532�020214� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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. ### """ Maintains a Quotes database for each channel. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.strike __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Quote/config.py���������������������������������������������������������0000644�0001750�0001750�00000004653�13634634532�017724� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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 from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Quote') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Quote', True) Quote = conf.registerPlugin('Quote') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Quote, 'someConfigVariableName', # registry.Boolean(False, _("""Help for someConfigVariableName."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Quote/locales/����������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017525� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Quote/locales/fi.po�����������������������������������������������������0000644�0001750�0001750�00000002424�13634634532�020457� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: Quote plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 11:59+EET\n" "PO-Revision-Date: 2014-12-20 13:07+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.10\n" #: plugin.py:36 msgid "This plugin allows you to add quotes to the database for a channel." msgstr "Tämä plugin sallii lainausten lisäämisen kanavan tietokantaan." #: plugin.py:39 msgid "" "[<channel>]\n" "\n" " Returns a random quote from <channel>. <channel> is only necessary " "if\n" " the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>]\n" "\n" " Palauttaa satunnaisen lainauksen <kanavalta>. <Kanava> on vaadittu " "vain, jos\n" " viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:48 msgid "I have no quotes in my database for %s." msgstr "Minun tietokannassani ei ole lainauksia kanavalla %s." ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Quote/locales/fr.po�����������������������������������������������������0000644�0001750�0001750�00000001645�13634634532�020474� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2010-10-17 18:34+CEST\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\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:38 msgid "" "[<channel>]\n" "\n" " Returns a random quote from <channel>. <channel> is only necessary if\n" " the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>]\n" "\n" "Retourne une citation aléatoire du <canal>. <canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:47 msgid "I have no quotes in my database for %s." msgstr "Je n'ai pas de citation dans ma base de données pour %s." �������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Quote/locales/it.po�����������������������������������������������������0000644�0001750�0001750�00000001551�13634634532�020475� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-12 18:39+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugin.py:38 #, docstring msgid "" "[<channel>]\n" "\n" " Returns a random quote from <channel>. <channel> is only necessary if\n" " the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>]\n" "\n" " Restituisce una citazione casuale da <canale>. <canale> è necessario solo\n" " se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:47 msgid "I have no quotes in my database for %s." msgstr "Non ho citazioni per %s nel mio database." �������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Quote/plugin.py���������������������������������������������������������0000644�0001750�0001750�00000005766�13634634532�017763� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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.commands import * import supybot.plugins as plugins from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Quote') class Quote(plugins.ChannelIdDatabasePlugin): """This plugin allows you to add quotes to the database for a channel.""" @internationalizeDocstring def random(self, irc, msg, args, channel): """[<channel>] Returns a random quote from <channel>. <channel> is only necessary if the message isn't sent in the channel itself. """ quote = self.db.random(channel) if quote: irc.reply(self.showRecord(quote)) else: irc.error(_('I have no quotes in my database for %s.') % channel) random = wrap(random, ['channeldb']) def replace(self, irc, msg, args, user, channel, id, text): """[<channel>] <id> <text> Replace quote <id> with <text>. <channel> is only necessary if the message isn't sent in the channel itself. """ try: record = self.db.get(channel, id) self.checkChangeAllowed(irc, msg, channel, user, record) record.text = text self.db.set(channel, id, record) irc.replySuccess() except KeyError: self.noSuchRecord(irc, channel, id) replace = wrap(replace, ['user', 'channeldb', 'id', 'text']) Class = Quote # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������limnoria-2020.03.17/plugins/Quote/test.py�����������������������������������������������������������0000644�0001750�0001750�00000004701�13634634532�017430� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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 QuoteTestCase(ChannelPluginTestCase): plugins = ('Quote', 'User') def testReplace(self): self.feedMsg('register testuser moo', to=self.nick, frm=self.prefix) _ = self.getMsg(' ') self.assertNotError("quote add hello") self.assertNotError("quote replace 1 goodbye") self.assertRegexp("quote get 1", "goodbye") self.assertError("quote replace 5 afsdafas") # non-existant def testUnauthenticatedAdd(self): # This should fail because the user isn't registered self.assertError('quote add hello world!') with conf.supybot.databases.plugins.requireRegistration.context(False): self.assertNotError('quote add hello world!') self.assertRegexp('quote get 1', 'hello') self.assertNotError('quote remove 1') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������limnoria-2020.03.17/plugins/QuoteGrabs/�������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017062� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/QuoteGrabs/__init__.py��������������������������������������������������0000644�0001750�0001750�00000005107�13634634532�021170� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Daniel DiPaolo # 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. ### """ Quotegrabs are like IRC sound bites. When someone says something funny, incriminating, stupid, outrageous, ... anything that might be worth remembering, you can grab that quote for that person. With this plugin, you can store many quotes per person and display their most recent quote, as well as see who "grabbed" the quote in the first place. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.strike __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload reload(plugin) # In case we're being reloaded. if world.testing: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/QuoteGrabs/config.py����������������������������������������������������0000644�0001750�0001750�00000007052�13634634532�020677� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Daniel DiPaolo # 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 from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('QuoteGrabs') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('QuoteGrabs', True) QuoteGrabs = conf.registerPlugin('QuoteGrabs') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(QuoteGrabs, 'someConfigVariableName', # registry.Boolean(False, _("""Help for someConfigVariableName."""))) conf.registerChannelValue(conf.supybot.plugins.QuoteGrabs, 'randomGrabber', registry.Boolean(False, _("""Determines whether the bot will randomly grab possibly-suitable quotes on occasion. The suitability of a given message is determined by ..."""))) conf.registerChannelValue(conf.supybot.plugins.QuoteGrabs.randomGrabber, 'averageTimeBetweenGrabs', registry.PositiveInteger(864000, _("""Determines about how many seconds, on average, should elapse between random grabs. This is only an average value; grabs can happen from any time after half this time until never, although that's unlikely to occur."""))) conf.registerChannelValue(conf.supybot.plugins.QuoteGrabs.randomGrabber, 'minimumWords', registry.PositiveInteger(3, _("""Determines the minimum number of words in a message for it to be considered for random grabbing."""))) conf.registerChannelValue(conf.supybot.plugins.QuoteGrabs.randomGrabber, 'minimumCharacters', registry.PositiveInteger(8, _("""Determines the minimum number of characters in a message for it to be considered for random grabbing."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/QuoteGrabs/locales/�����������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�020504� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/QuoteGrabs/locales/fi.po������������������������������������������������0000755�0001750�0001750�00000016214�13634634532�021443� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: QuoteGrabs plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 11:59+EET\n" "PO-Revision-Date: 2014-12-20 12:08+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.10\n" #: config.py:49 msgid "" "Determines whether the bot will randomly grab\n" " possibly-suitable quotes on occasion. The suitability of a given " "message\n" " is determined by ..." msgstr "" "Määrittää kaappaako botti sattumanvaraisesti mahdollisesti sopivia " "lainauksia. Annetun viestin\n" " sopivuuden määrittää ..." #: config.py:54 msgid "" "Determines about how many seconds, on\n" " average, should elapse between random grabs. This is only an average\n" " value; grabs can happen from any time after half this time until never,\n" " although that's unlikely to occur." msgstr "" "Määrittää kuinka monta sekuntia keskiarvona,\n" " pitäisi kulua kaappausten välissä. Tämä on vain keskiarvo;\n" " kaappaukset voivat tapahtua milloin tahansa, kun puolet tästä ajasta on " "kulunut ikuisuuteen asti, mutta\n" " sitä tuskin tapahtuu." #: config.py:59 msgid "" "Determines the minimum\n" " number of words in a message for it to be considered for random\n" " grabbing." msgstr "" "Määrittää minimimäärän sanoja viestissä, joita pidetään kaappaukseen " "soveltuvina." #: config.py:63 msgid "" "Determines the\n" " minimum number of characters in a message for it to be considered for\n" " random grabbing." msgstr "" "Määrittää\n" " minimimäärän merkkejä ennen kuin sitä harkitaan sopivaksi\n" " satunnaiselle kaappaamiselle." #: plugin.py:64 msgid "%s (Said by: %s; grabbed by %s at %t)" msgstr "%s (Sanonut: %s; kaapannut %s kello %t)" #: plugin.py:226 msgid "" "Stores and displays quotes from channels. Quotes are stored randomly\n" " and/or on user request." msgstr "" "Tallentaa ja näyttää lainauksia, jotka on kaapattu kanavilta. Lainauksia " "tallennetaan sattumanvaraisesti\n" " ja/tai käyttäjän pyytäessä sitä." #: plugin.py:266 msgid "" "[<channel>] <nick>\n" "\n" " Grabs a quote from <channel> by <nick> for the quotegrabs table.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<kanava>] <nimimerkki>\n" "\n" " Kaappaa lainauksen, jonka <nimimerkki> on sanonut <kanavalla> " "lainaus kaappaus tietokantaan.\n" " <Kanava> on vaadittu vain jos viestiä ei lähetetä kanavalla " "itsellään.\n" " " #: plugin.py:279 msgid "You can't quote grab yourself." msgstr "Et voi kaapata lainausta itseltäsi." #: plugin.py:286 msgid "I couldn't find a proper message to grab." msgstr "En voinut löytää kelvollista viestiä kaapattavaksi." #: plugin.py:291 msgid "" "[<channel>] <number>\n" "\n" " Removes the grab <number> (the last by default) on <channel>.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<kanava>] <numero>\n" "\n" " Poistaa kaappauksen <numero> (oletuksena viimeinen) <kanavalta>.\n" " <Kanava> on vaadittu vain, jos viestiä ei lähetetä kanavalla " "itsellään.\n" " " #: plugin.py:302 msgid "Nothing to ungrab." msgstr "Ei mitään kaappausta poistettavaksi." #: plugin.py:304 msgid "Invalid grab number." msgstr "Epäkelvollinen kaappauksen numero." #: plugin.py:309 msgid "" "[<channel>] <nick>\n" "\n" " Returns <nick>'s latest quote grab in <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] <nimimerkki>\n" "\n" " Palauttaa <nimimerkin> viimeisimmän lainauksen kaappauksen " "<kanavalla>. <Kanava> on\n" " vaadittu vain jos viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:317 msgid "I couldn't find a matching quotegrab for %s." msgstr "En voinut löytää täsmäävää lainaus kaappausta %s:lle." #: plugin.py:323 msgid "" "[<channel>] <nick>\n" "\n" " Returns a list of shortened quotes that have been grabbed for " "<nick>\n" " as well as the id of each quote. These ids can be used to get the\n" " full quote. <channel> is only necessary if the message isn't sent " "in\n" " the channel itself.\n" " " msgstr "" "[<kanava>] <nimimerkki>\n" "\n" " Palauttaa listan lyhennetyistä lainauksista, jotka on kaapattu " "<nimimerkitä>,\n" " kuten myös jokaisen lainauksen id:een. Näitä id:eitä voidaan " "käyttää\n" " koko lainauksen saamiseen. <Kanava> on vaadittu vain, jos viestiä " "ei lähetetä\n" " kanavalla itsellään.\n" " " #: plugin.py:340 msgid "I couldn't find any quotegrabs for %s." msgstr "En voinut löytää yhtään kaapattuja lainauksia %s:ltä." #: plugin.py:346 msgid "" "[<channel>] [<nick>]\n" "\n" " Returns a randomly grabbed quote, optionally choosing only from " "those\n" " quotes grabbed for <nick>. <channel> is only necessary if the " "message\n" " isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] [<nimimerkki>]\n" "\n" " Palauttaa satunnaisen lainatun kaappauksen, vaihtoehtoisesti valiten " "vain\n" " lainauksista, jotka on kaapattu <nimimerkiltä>. <Kanava> on " "vaadittu vain, jos\n" " viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:356 msgid "Couldn't get a random quote for that nick." msgstr "Satunnaista lainausta tuolta nimimerkiltä ei voitu noutaa." #: plugin.py:358 msgid "" "Couldn't get a random quote. Are there any grabbed quotes in the database?" msgstr "" "Satunnaista lainausta ei voitu noutaa. Onko tietokannassa yhtään kaapattuja " "lainauksia?" #: plugin.py:364 msgid "" "[<channel>] <id>\n" "\n" " Return the quotegrab with the given <id>. <channel> is only " "necessary\n" " if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] <id>\n" "\n" " Palauttaa kaapatun lainauksen annetulla <id:llä>. <Kanava> on " "vaadittu vain\n" " jos viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:372 msgid "No quotegrab for id %s" msgstr "Ei kaapattuja lainauksia %s:lle." #: plugin.py:378 msgid "" "[<channel>] <text>\n" "\n" " Searches for <text> in a quote. <channel> is only necessary if the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] <teksti>\n" "\n" " Etsii <tekstiä> lainauksesta. <Kanava> on vaadittu vain, jos " "viestiä\n" " viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:393 msgid "No quotegrabs matching %s" msgstr "Ei %s:ään täsmääviä lainauksia" #~ msgid "Add the help for \"help QuoteGrabs\" here." #~ msgstr "Lisää ohje \"help QuoteGrabs\"ille tähän." ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/QuoteGrabs/locales/fr.po������������������������������������������������0000644�0001750�0001750�00000014301�13634634532�021444� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2010-10-17 18:36+CEST\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\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" #: config.py:49 msgid "" "Determines whether the bot will randomly grab\n" " possibly-suitable quotes on occasion. The suitability of a given message\n" " is determined by ..." msgstr "Détermine si le bot récupèrera automatiquement des quotes éligible à une quote. L'éligibilté est déterminée par..." #: config.py:54 msgid "" "Determines about how many seconds, on\n" " average, should elapse between random grabs. This is only an average\n" " value; grabs can happen from any time after half this time until never,\n" " although that's unlikely to occur." msgstr "Détermine le nombre de secondes, en moyenne, entre deux récupération de citations. Il s'agit seulement d'une moyenne, et les prises de citation peuvent être faites n'importe quand après la moitié de ce temps, voire jamais, même si c'est improbable." #: config.py:59 msgid "" "Determines the minimum\n" " number of words in a message for it to be considered for random\n" " grabbing." msgstr "Détermine le nombre minimum de mots dans un message pour être éligible à la quotation aléatoire." #: config.py:63 msgid "" "Determines the\n" " minimum number of characters in a message for it to be considered for\n" " random grabbing." msgstr "Détermine le nombre minimum de caractères dans un message pour être éligible à la quotation aléatoire." #: plugin.py:57 msgid "%s (Said by: %s; grabbed by %s at %t)" msgstr "%s (Dit par %s ; récupéré par %s à %t)" #: plugin.py:210 msgid "Add the help for \"help QuoteGrabs\" here." msgstr "" #: plugin.py:249 msgid "" "[<channel>] <nick>\n" "\n" " Grabs a quote from <channel> by <nick> for the quotegrabs table.\n" " <channel> is only necessary if the message isn't sent in the channel\n" " itself.\n" " " msgstr "" "[<canal>] <nick>\n" "\n" "Récupère une quote du <canal> par le <nick> dans la table des citations. <canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:262 msgid "You can't quote grab yourself." msgstr "Vous ne pouvez récupérer des citations de vous-même." #: plugin.py:269 msgid "I couldn't find a proper message to grab." msgstr "Je ne peux trouver de message à quoter." #: plugin.py:274 msgid "" "[<channel>] <number>\n" "\n" " Removes the grab <number> (the last by default) on <channel>.\n" " <channel> is only necessary if the message isn't sent in the channel\n" " itself.\n" " " msgstr "" "[<canal>] <nombre>\n" "\n" "Supprime la quote désignée par le <nombre> (la dernière par défaut) sur le <canal>. <canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:285 msgid "Nothing to ungrab." msgstr "Rien à dé-quoter" #: plugin.py:287 msgid "Invalid grab number." msgstr "Numéro de quote invalide." #: plugin.py:292 msgid "" "[<channel>] <nick>\n" "\n" " Returns <nick>'s latest quote grab in <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] <nick>\n" "\n" "Retourne le nick de la dernière personne citée sur le <canal>. <canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:300 msgid "I couldn't find a matching quotegrab for %s." msgstr "Je ne peux trouver de quote coresspondant à %s." #: plugin.py:306 msgid "" "[<channel>] <nick>\n" "\n" " Returns a list of shortened quotes that have been grabbed for <nick>\n" " as well as the id of each quote. These ids can be used to get the\n" " full quote. <channel> is only necessary if the message isn't sent in\n" " the channel itself.\n" " " msgstr "" "[<canal>] <nick>\n" "\n" "Retourne une liste de quotes raccourcies que ont été récupérées pour le <nick>, ainsi que l'id de chaque quote. Les ids peuvent être utilisés pour récupérer les quotes entières. <canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:323 msgid "I couldn't find any quotegrabs for %s." msgstr "Je ne peux trouver de citation pour %s" #: plugin.py:329 msgid "" "[<channel>] [<nick>]\n" "\n" " Returns a randomly grabbed quote, optionally choosing only from those\n" " quotes grabbed for <nick>. <channel> is only necessary if the message\n" " isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] [<nick>]\n" "\n" "Retourne une citation aléatoire, éventuellement parmis les citations récupérées de <nick>. <canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:339 msgid "Couldn't get a random quote for that nick." msgstr "Je ne peux récupérer une quote aléatoire pour ce nick." #: plugin.py:341 msgid "Couldn't get a random quote. Are there any grabbed quotes in the database?" msgstr "Je ne peux récupérer de quote aléatoire. Y a-t-il des quotes dans la base de données ?" #: plugin.py:347 msgid "" "[<channel>] <id>\n" "\n" " Return the quotegrab with the given <id>. <channel> is only necessary\n" " if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] <id>\n" "\n" "Retourne la quote d'<id> donné. <canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:355 msgid "No quotegrab for id %s" msgstr "Pas de quote d'id %s" #: plugin.py:361 msgid "" "[<channel>] <text>\n" "\n" " Searches for <text> in a quote. <channel> is only necessary if the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] <texte>\n" "\n" "Recherche le <texte> dans les citations. <canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:376 msgid "No quotegrabs matching %s" msgstr "Aucune quote ne correspond à %s" �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/QuoteGrabs/locales/it.po������������������������������������������������0000644�0001750�0001750�00000014653�13634634532�021463� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-07-07 11:07+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:49 msgid "" "Determines whether the bot will randomly grab\n" " possibly-suitable quotes on occasion. The suitability of a given message\n" " is determined by ..." msgstr "" "Determina se il bot pescherà in modo casuale citazioni adatte all'occasione.\n" " L'idoneità del messaggio è determinata da ..." #: config.py:54 msgid "" "Determines about how many seconds, on\n" " average, should elapse between random grabs. This is only an average\n" " value; grabs can happen from any time after half this time until never,\n" " although that's unlikely to occur." msgstr "" "Determina quanti secondi, in media, devono passare tra due recuperi casuali.\n" " Questa è solo un valore medio; il recupero delle citazioni può accadere da\n" " qualsiasi momento dopo la metà di questo tempo fino a mai, seppur improbabile." #: config.py:59 msgid "" "Determines the minimum\n" " number of words in a message for it to be considered for random\n" " grabbing." msgstr "" "Determina il numero minimo di parole in un messaggio per essere considerato\n" " un recupero casuale." #: config.py:63 msgid "" "Determines the\n" " minimum number of characters in a message for it to be considered for\n" " random grabbing." msgstr "" "Determina il numero minimo di caratteri in un messaggio per essere considerato\n" " un recupero casuale." #: plugin.py:66 msgid "%s (Said by: %s; grabbed by %s at %t)" msgstr "%s (Detto da: %s; pescato da %s il %t)" #: plugin.py:226 #, docstring msgid "Add the help for \"help QuoteGrabs\" here." msgstr "" #: plugin.py:265 #, docstring msgid "" "[<channel>] <nick>\n" "\n" " Grabs a quote from <channel> by <nick> for the quotegrabs table.\n" " <channel> is only necessary if the message isn't sent in the channel\n" " itself.\n" " " msgstr "" "[<canale>] <nick>\n" "\n" " Recupera una citazione di <nick> da <canale> per la tabella delle citazioni.\n" " <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:278 msgid "You can't quote grab yourself." msgstr "Non puoi recuperare citazioni da solo." #: plugin.py:285 msgid "I couldn't find a proper message to grab." msgstr "Impossibile trovare un messaggio da recuperare." #: plugin.py:290 #, docstring msgid "" "[<channel>] <number>\n" "\n" " Removes the grab <number> (the last by default) on <channel>.\n" " <channel> is only necessary if the message isn't sent in the channel\n" " itself.\n" " " msgstr "" "[<canale>] <numero>\n" "\n" " Rimuove la citazione <numero> (di default l'ultima) su <canale>.\n" " <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:301 msgid "Nothing to ungrab." msgstr "Niente da rimuovere." #: plugin.py:303 msgid "Invalid grab number." msgstr "Numero citazione non valido." #: plugin.py:308 #, docstring msgid "" "[<channel>] <nick>\n" "\n" " Returns <nick>'s latest quote grab in <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] <nick>\n" "\n" " Riporta il <nick> dell'ultima citazione catturata in <canale>. <canale>\n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:316 msgid "I couldn't find a matching quotegrab for %s." msgstr "Impossibile trovare una citazione corrispondente a %s." #: plugin.py:322 #, docstring msgid "" "[<channel>] <nick>\n" "\n" " Returns a list of shortened quotes that have been grabbed for <nick>\n" " as well as the id of each quote. These ids can be used to get the\n" " full quote. <channel> is only necessary if the message isn't sent in\n" " the channel itself.\n" " " msgstr "" "[<canale>] <nick>\n" "\n" " Riporta un elenco di citazioni in versione ridotta che sono state pescate per <nick>\n" " e l'ID di ognuna. Questi ID possono essere utilizzati per recuperare la citazione\n" " completa. <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:339 msgid "I couldn't find any quotegrabs for %s." msgstr "Impossibile trovare una citazione per %s." #: plugin.py:345 #, docstring msgid "" "[<channel>] [<nick>]\n" "\n" " Returns a randomly grabbed quote, optionally choosing only from those\n" " quotes grabbed for <nick>. <channel> is only necessary if the message\n" " isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] [<nick>]\n" "\n" " Restituisce una citazione pescata in modo casuale, eventualmente scegliendo solo quelle\n" " di <nick>. <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:355 msgid "Couldn't get a random quote for that nick." msgstr "Impossibile ottenere una citazione casuale per questo nick." #: plugin.py:357 msgid "Couldn't get a random quote. Are there any grabbed quotes in the database?" msgstr "Impossibile ottenere una citazione casuale. Ci sono delle citazioni nel database?" #: plugin.py:363 #, docstring msgid "" "[<channel>] <id>\n" "\n" " Return the quotegrab with the given <id>. <channel> is only necessary\n" " if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] <id>\n" "\n" " Restituisce la citazione con l'<id> specificato. <canale> è necessario\n" " solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:371 msgid "No quotegrab for id %s" msgstr "Nessuna citazione per l'id %s." #: plugin.py:377 #, docstring msgid "" "[<channel>] <text>\n" "\n" " Searches for <text> in a quote. <channel> is only necessary if the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] <testo>\n" "\n" " Cerca <testo> in una citazione. <canale> è necessario solo se il\n" " messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:392 msgid "No quotegrabs matching %s" msgstr "Nessuna citazione corrispondente a %s." �������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/QuoteGrabs/plugin.py����������������������������������������������������0000644�0001750�0001750�00000040700�13634634532�020725� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Daniel DiPaolo # Copyright (c) 2008-2010, James McCoy # 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 os import sys import time import random import supybot.dbi as dbi import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.utils.minisix as minisix import supybot.ircmsgs as ircmsgs import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('QuoteGrabs') import sqlite3 import traceback #sqlite3.register_converter('bool', bool) class QuoteGrabsRecord(dbi.Record): __fields__ = [ 'by', 'text', 'grabber', 'at', 'hostmask', ] def __str__(self): grabber = plugins.getUserName(self.grabber) if self.at: return format(_('%s (Said by: %s; grabbed by %s at %t)'), self.text, self.hostmask, grabber, self.at) else: return format('%s', self.text) class SqliteQuoteGrabsDB(object): def __init__(self, filename): self.dbs = ircutils.IrcDict() self.filename = filename def close(self): for db in self.dbs.values(): db.close() def _getDb(self, channel): filename = plugins.makeChannelFilename(self.filename, channel) def p(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): db = sqlite3.connect(filename) if minisix.PY2: db.text_factory = str db.create_function('nickeq', 2, p) self.dbs[filename] = db return db db = sqlite3.connect(filename) if minisix.PY2: db.text_factory = str db.create_function('nickeq', 2, p) self.dbs[filename] = db cursor = db.cursor() cursor.execute("""CREATE TABLE quotegrabs ( id INTEGER PRIMARY KEY, nick BLOB, hostmask TEXT, added_by TEXT, added_at TIMESTAMP, quote TEXT );""") db.commit() return db def get(self, channel, id, quoteonly = 0): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT id, nick, quote, hostmask, added_at, added_by FROM quotegrabs WHERE id = ?""", (id,)) results = cursor.fetchall() if len(results) == 0: raise dbi.NoRecordError (id, by, quote, hostmask, at, grabber) = results[0] if quoteonly == 0: return QuoteGrabsRecord(id, by=by, text=quote, hostmask=hostmask, at=int(at), grabber=grabber) else: return QuoteGrabsRecord(id, text=quote) def random(self, channel, nick): db = self._getDb(channel) cursor = db.cursor() if nick: cursor.execute("""SELECT quote FROM quotegrabs WHERE nickeq(nick, ?) ORDER BY random() LIMIT 1""", (nick,)) else: cursor.execute("""SELECT quote FROM quotegrabs ORDER BY random() LIMIT 1""") results = cursor.fetchall() if len(results) == 0: raise dbi.NoRecordError 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, ?) ORDER BY id DESC""", (nick,)) results = cursor.fetchall() if len(results) == 0: raise dbi.NoRecordError return [QuoteGrabsRecord(id, text=quote) 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, ?) ORDER BY id DESC LIMIT 1""", (nick,)) results = cursor.fetchall() if len(results) == 0: raise dbi.NoRecordError 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, ?) ORDER BY id DESC LIMIT 1""", (nick,)) results = cursor.fetchall() if len(results) == 0: raise dbi.NoRecordError return results[0][0] def add(self, channel, msg, by): db = self._getDb(channel) cursor = db.cursor() text = ircmsgs.prettyPrint(msg) # Check to see if the latest quotegrab is identical cursor.execute("""SELECT quote FROM quotegrabs 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, ?, ?, ?, ?, ?)""", (msg.nick, msg.prefix, by, int(time.time()), text,)) db.commit() def remove(self, channel, grab=None): db = self._getDb(channel) cursor = db.cursor() if grab is not None: # the testing if there actually *is* the to-be-deleted record is # 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 = ?""", (grab,)) results = cursor.fetchall() if len(results) == 0: raise dbi.NoRecordError cursor.execute("""DELETE FROM quotegrabs WHERE id = ?""", (grab,)) else: cursor.execute("""SELECT * FROM quotegrabs WHERE id = (SELECT MAX(id) FROM quotegrabs)""") results = cursor.fetchall() if len(results) == 0: raise dbi.NoRecordError cursor.execute("""DELETE FROM quotegrabs WHERE id = (SELECT MAX(id) FROM quotegrabs)""") db.commit() def search(self, channel, text): db = self._getDb(channel) cursor = db.cursor() text = '%' + text + '%' cursor.execute("""SELECT id, nick, quote FROM quotegrabs 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 results] QuoteGrabsDB = plugins.DB('QuoteGrabs', {'sqlite3': SqliteQuoteGrabsDB}) class QuoteGrabs(callbacks.Plugin): """Stores and displays quotes from channels. Quotes are stored randomly and/or on user request.""" def __init__(self, irc): self.__parent = super(QuoteGrabs, self) self.__parent.__init__(irc) self.db = QuoteGrabsDB() def doPrivmsg(self, irc, msg): if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg): return irc = callbacks.SimpleProxy(irc, msg) if msg.channel: payload = msg.args[1] words = self.registryValue('randomGrabber.minimumWords', msg.channel, irc.network) length = self.registryValue('randomGrabber.minimumCharacters', msg.channel, irc.network) grabTime = \ self.registryValue('randomGrabber.averageTimeBetweenGrabs', msg.channel, irc.network) channel = plugins.getChannel(msg.channel) if self.registryValue('randomGrabber', msg.channel, irc.network): if len(payload) > length and len(payload.split()) > words: try: last = int(self.db.select(channel, msg.nick)) except dbi.NoRecordError: self._grab(channel, irc, msg, irc.prefix) self._sendGrabMsg(irc, msg) else: elapsed = int(time.time()) - last if (random.random() * elapsed) > (grabTime / 2): self._grab(channel, irc, msg, irc.prefix) self._sendGrabMsg(irc, msg) def _grab(self, channel, irc, msg, addedBy): self.db.add(channel, msg, addedBy) def _sendGrabMsg(self, irc, msg): s = 'jots down a new quote for %s' % msg.nick irc.reply(s, action=True, prefixNick=False) @internationalizeDocstring def grab(self, irc, msg, args, channel, nick): """[<channel>] <nick> Grabs a quote from <channel> by <nick> for the quotegrabs table. <channel> is only necessary if the message isn't sent in the channel itself. """ # chan is used to make sure we know where to grab the quote from, as # opposed to channel which is used to determine which db to store the # quote in chan = msg.args[0] if chan is None or not irc.isChannel(chan): raise callbacks.ArgumentError if ircutils.nickEqual(nick, msg.nick): irc.error(_('You can\'t quote grab yourself.'), Raise=True) for m in reversed(irc.state.history): if m.command == 'PRIVMSG' and ircutils.nickEqual(m.nick, nick) \ and ircutils.strEqual(m.args[0], chan): # TODO: strip statusmsg prefix for comparison? Must be careful # abouk leaks, though. self._grab(channel, irc, m, msg.prefix) irc.replySuccess() return irc.error(_('I couldn\'t find a proper message to grab.')) grab = wrap(grab, ['channeldb', 'nick']) @internationalizeDocstring def ungrab(self, irc, msg, args, channel, grab): """[<channel>] <number> Removes the grab <number> (the last by default) on <channel>. <channel> is only necessary if the message isn't sent in the channel itself. """ try: self.db.remove(channel, grab) irc.replySuccess() except dbi.NoRecordError: if grab is None: irc.error(_('Nothing to ungrab.')) else: irc.error(_('Invalid grab number.')) ungrab = wrap(ungrab, ['channeldb', optional('id')]) @internationalizeDocstring def quote(self, irc, msg, args, channel, nick): """[<channel>] <nick> Returns <nick>'s latest quote grab in <channel>. <channel> is only necessary if the message isn't sent in the channel itself. """ try: irc.reply(self.db.getQuote(channel, nick)) except dbi.NoRecordError: irc.error(_('I couldn\'t find a matching quotegrab for %s.') % nick, Raise=True) quote = wrap(quote, ['channeldb', 'nick']) @internationalizeDocstring def list(self, irc, msg, args, channel, nick): """[<channel>] <nick> Returns a list of shortened quotes that have been grabbed for <nick> as well as the id of each quote. These ids can be used to get the full quote. <channel> is only necessary if the message isn't sent in the channel itself. """ try: records = self.db.list(channel, nick) L = [] for record in records: # strip the nick from the quote quote = record.text.replace('<%s> ' % nick, '', 1) item = utils.str.ellipsisify('#%s: %s' % (record.id, quote),50) L.append(item) irc.reply(utils.str.commaAndify(L)) except dbi.NoRecordError: irc.error(_('I couldn\'t find any quotegrabs for %s.') % nick, Raise=True) list = wrap(list, ['channeldb', 'nick']) @internationalizeDocstring def random(self, irc, msg, args, channel, nick): """[<channel>] [<nick>] Returns a randomly grabbed quote, optionally choosing only from those quotes grabbed for <nick>. <channel> is only necessary if the message isn't sent in the channel itself. """ try: irc.reply(self.db.random(channel, nick)) except dbi.NoRecordError: if nick: irc.error(_('Couldn\'t get a random quote for that nick.')) else: irc.error(_('Couldn\'t get a random quote. Are there any ' 'grabbed quotes in the database?')) random = wrap(random, ['channeldb', additional('nick')]) @internationalizeDocstring def say(self, irc, msg, args, channel, id): """[<channel>] <id> Return the quotegrab with the given <id>. <channel> is only necessary if the message isn't sent in the channel itself. """ try: irc.reply(self.db.get(channel, id, 1)) except dbi.NoRecordError: irc.error(_('No quotegrab for id %s') % utils.str.quoted(id), Raise=True) say = wrap(say, ['channeldb', 'id']) @internationalizeDocstring def get(self, irc, msg, args, channel, id): """[<channel>] <id> Return the quotegrab with the given <id>. <channel> is only necessary if the message isn't sent in the channel itself. """ try: irc.reply(self.db.get(channel, id)) except dbi.NoRecordError: irc.error(_('No quotegrab for id %s') % utils.str.quoted(id), Raise=True) get = wrap(get, ['channeldb', 'id']) @internationalizeDocstring def search(self, irc, msg, args, channel, text): """[<channel>] <text> Searches for <text> in a quote. <channel> is only necessary if the message isn't sent in the channel itself. """ try: records = self.db.search(channel, text) L = [] for record in records: # strip the nick from the quote quote = record.text.replace('<%s> ' % record.by, '', 1) item = utils.str.ellipsisify('#%s: %s' % (record.id, quote),50) L.append(item) irc.reply(utils.str.commaAndify(L)) except dbi.NoRecordError: irc.error(_('No quotegrabs matching %s') % utils.str.quoted(text), Raise=True) search = wrap(search, ['channeldb', 'text']) Class = QuoteGrabs # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������limnoria-2020.03.17/plugins/QuoteGrabs/test.py������������������������������������������������������0000644�0001750�0001750�00000015102�13634634532�020404� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Daniel DiPaolo # Copyright (c) 2008, James McCoy # 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 QuoteGrabsTestCase(ChannelPluginTestCase): plugins = ('QuoteGrabs',) def testQuoteGrab(self): testPrefix = 'foo!bar@baz' self.assertError('grab foo') # Test join/part/notice (shouldn't grab) self.irc.feedMsg(ircmsgs.join(self.channel, prefix=testPrefix)) self.assertError('grab foo') self.irc.feedMsg(ircmsgs.part(self.channel, prefix=testPrefix)) self.assertError('grab foo') # Test privmsgs self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'something', prefix=testPrefix)) self.assertNotError('grab foo') self.assertResponse('quote foo', '<foo> something') # Test actions self.irc.feedMsg(ircmsgs.action(self.channel, 'moos', prefix=testPrefix)) self.assertNotError('grab foo') self.assertResponse('quote foo', '* foo moos') def testUngrab(self): testPrefix = 'foo!bar@baz' # nothing yet self.assertError('ungrab') self.assertError('ungrab 2') self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'something', prefix=testPrefix)) # still not grabbed self.assertError('ungrab') self.assertError('ungrab 3') # grab and ungrab a quote self.assertNotError('grab foo') self.assertNotError('ungrab') self.assertNotError('grab foo') # this is not there... self.assertError('ungrab 8883') # ...unlike this... self.assertNotError('ungrab 1') # ...but not now anymore :-D self.assertError('ungrab') # grab two quotes and ungrab them by id self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'something', prefix=testPrefix)) self.assertNotError('grab foo') self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'another', prefix=testPrefix)) self.assertNotError('grab foo') self.assertNotError('ungrab 1') self.assertNotError('ungrab 2') self.assertError('ungrab') def testList(self): testPrefix = 'foo!bar@baz' self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'testList', prefix=testPrefix)) self.assertNotError('grab foo') self.assertResponse('quotegrabs list foo', '#1: testList') self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'a' * 80, prefix=testPrefix)) self.assertNotError('grab foo') self.assertResponse('quotegrabs list foo', '#2: %s... and #1: testList' %\ ('a'*43)) # 50 - length of "#2: ..." def testDuplicateGrabs(self): testPrefix = 'foo!bar@baz' self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'testDupe', prefix=testPrefix)) self.assertNotError('grab foo') self.assertNotError('grab foo') # note:NOTanerror,stillwon'tdupe self.assertResponse('quotegrabs list foo', '#1: testDupe') def testCaseInsensitivity(self): testPrefix = 'foo!bar@baz' self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'testCI', prefix=testPrefix)) self.assertNotError('grab FOO') self.assertNotError('quote foo') self.assertNotError('quote FoO') self.assertNotError('quote Foo') self.assertNotError('quotegrabs list FOO') self.assertNotError('quotegrabs list fOo') def testRandom(self): testPrefix = 'foo!bar@baz' self.assertError('random') self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'testRandom', prefix=testPrefix)) self.assertError('random') # still none in the db self.assertNotError('grab foo') self.assertResponse('random', '<foo> testRandom') self.assertResponse('random foo', '<foo> testRandom') self.assertResponse('random FOO', '<foo> testRandom') def testGet(self): testPrefix= 'foo!bar@baz' self.assertError('quotegrabs get asdf') self.assertError('quotegrabs get 1') self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'testGet', prefix=testPrefix)) self.assertNotError('grab foo') self.assertNotError('quotegrabs get 1') def testSearch(self): testPrefix= 'foo!bar@baz' self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'testSearch', prefix=testPrefix)) self.assertError('quotegrabs search test') # still none in db self.assertNotError('grab foo') self.assertNotError('quotegrabs search test') class QuoteGrabsNonChannelTestCase(QuoteGrabsTestCase): config = { 'databases.plugins.channelSpecific' : False } # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/RSS/��������������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�015455� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/RSS/__init__.py���������������������������������������������������������0000644�0001750�0001750�00000004677�13634634532�017576� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Provides basic functionality for handling RSS/RDF feeds. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������limnoria-2020.03.17/plugins/RSS/config.py�����������������������������������������������������������0000644�0001750�0001750�00000015110�13634634532�017264� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('RSS') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('RSS', True) class FeedNames(registry.SpaceSeparatedListOfStrings): List = callbacks.CanonicalNameSet class FeedItemSortOrder(registry.OnlySomeStrings): """Valid values include 'asInFeed', 'oldestFirst', 'newestFirst'.""" validStrings = ('asInFeed', 'oldestFirst', 'newestFirst', 'outdatedFirst', 'updatedFirst') RSS = conf.registerPlugin('RSS') conf.registerGlobalValue(RSS, 'feeds', FeedNames([], _("""Determines what feeds should be accessible as commands."""))) ######## # Format conf.registerChannelValue(RSS, 'headlineSeparator', registry.StringSurroundedBySpaces('|', _("""Determines what string is used to separate headlines in new feeds."""))) conf.registerChannelValue(RSS, 'format', registry.String(_('$date: $title <$link>'), _("""The format the bot will use for displaying headlines of a RSS feed that is triggered manually. In addition to fields defined by feedparser ($published (the entry date), $title, $link, $description, $id, etc.), the following variables can be used: $feed_name, $date (parsed date, as defined in supybot.reply.format.time)"""))) conf.registerChannelValue(RSS, 'announceFormat', registry.String(_('News from $feed_name: $title <$link>'), _("""The format the bot will use for displaying headlines of a RSS feed that is announced. See supybot.plugins.RSS.format for the available variables."""))) ########### # Announces conf.registerChannelValue(RSS, 'announce', registry.SpaceSeparatedSetOfStrings([], _("""Determines which RSS feeds should be announced in the channel; valid input is a list of strings (either registered RSS feeds or RSS feed URLs) separated by spaces."""))) conf.registerGlobalValue(RSS, 'waitPeriod', registry.PositiveInteger(1800, _("""Indicates how many seconds the bot will wait between retrieving RSS feeds; requests made within this period will return cached results."""))) conf.registerGlobalValue(RSS, 'sortFeedItems', FeedItemSortOrder('asInFeed', _("""Determines whether feed items should be sorted by their publication/update timestamp or kept in the same order as they appear in a feed."""))) conf.registerChannelValue(RSS, 'notice', registry.Boolean(False, _("""Determines whether announces will be sent as notices instead of privmsgs."""))) conf.registerChannelValue(RSS, 'maximumAnnounceHeadlines', registry.PositiveInteger(5, _("""Indicates how many new news entries may be sent at the same time. Extra entries will be discarded."""))) #################### # Headlines filtering conf.registerChannelValue(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.Integer(5, _("""Indicates how many headlines an rss feed will output when it is first added to announce for a channel."""))) conf.registerChannelValue(RSS, 'keywordWhitelist', registry.SpaceSeparatedSetOfStrings([], _("""Space separated list of strings, lets you filter headlines to those containing one or more items in this whitelist."""))) conf.registerChannelValue(RSS, 'keywordBlacklist', registry.SpaceSeparatedSetOfStrings([], _("""Space separated list of strings, lets you filter headlines to those not containing any items in this blacklist."""))) def register_feed_config(name, url=''): RSS.feeds().add(name) conf.registerGlobalValue(RSS.feeds, name, registry.String(url, _("""The URL for the feed %s. Note that because announced lines are cached, you may need to reload this plugin after changing this option.""" % name))) feed_group = conf.registerGroup(RSS.feeds, name) conf.registerChannelValue(feed_group, 'format', registry.String('', _("""Feed-specific format. Defaults to supybot.plugins.RSS.format if empty."""))) conf.registerChannelValue(feed_group, 'announceFormat', registry.String('', _("""Feed-specific announce format. Defaults to supybot.plugins.RSS.announceFormat if empty."""))) conf.registerGlobalValue(feed_group, 'waitPeriod', registry.NonNegativeInteger(0, _("""If set to a non-zero value, overrides supybot.plugins.RSS.waitPeriod for this particular feed."""))) for name in RSS.feeds(): register_feed_config(name) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/RSS/locales/������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017077� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/RSS/locales/de.po�������������������������������������������������������0000644�0001750�0001750�00000016141�13634634532�020024� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2011-10-29 16:08+CEST\n" "PO-Revision-Date: 2011-10-29 17:09+0100\n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: German <fbesser@gmail.com>\n" "Language: de\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" #: config.py:50 #, fuzzy msgid "" "Determines whether the bot will bold the title of the feed when\n" " it announces news." msgstr "Legt fest ob der Bot Titel fett schreibt, wenn er er Neuigkeiten verkündet." #: config.py:53 msgid "" "Determines what string is\n" " used to separate headlines in new feeds." msgstr "Gibt an welche Zeichenkette verwendet wird um Kopfzeilen neuer Feeds zu separieren." #: config.py:56 #, fuzzy msgid "" "Determines what\n" " prefix is prepended (if any) to the news item announcements made in the\n" " channel." msgstr "Legt fest welcher Prefix (falls überhaupt) den News vorangestellt wird, wenn neue Verkündungen in einem Kanal gemacht werden." #: config.py:56 #, fuzzy msgid "News from " msgstr "Neuigkeiten von " #: config.py:60 msgid "" "Determines which RSS feeds\n" " should be announced in the channel; valid input is a list of strings\n" " (either registered RSS feeds or RSS feed URLs) separated by spaces." msgstr "Legt fest welche RSS Feeds in einem Kanal verkündet werden. Zulässige eingaben sind Listen von Zeichenketten (entweder registrierte RSS Feed oder URLs zu RSS Feed), mit einem Leerzeichen getrennt" #: config.py:64 msgid "" "Indicates how many seconds the bot will\n" " wait between retrieving RSS feeds; requests made within this period will\n" " return cached results." msgstr "Zeigt wieviele Sekunden der Bot wartet, zwischen dem Empfangen von RSS Feeds; Anfragen die innerhalb dieser Zeit gemachten werden, führt zu zwischengespeicherten Ergebnissen." #: config.py:68 msgid "" "Determines what feeds should be accessible as\n" " commands." msgstr "Legt fest auf welche Feeds per Befehl zugegriffen werden darf." #: config.py:71 msgid "" "Determines whether the bot will list the link\n" " along with the title of the feed when the rss command is called.\n" " supybot.plugins.RSS.announce.showLinks affects whether links will be\n" " listed when a feed is automatically announced." msgstr "Legt fest ob der Bot den Link zusammen mit dem Titel des Feeds aufgelistet wird, wenn der RSS Befehl aufgerufen wird. supybot.plugins.RSS.announce.showLinks ist zuständig dafür falls der Feed automatisch verkündet wird." #: config.py:92 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 "Legt fest ob der Bot einen Link mit dem Feed Titel angeben soll wenn ein Feed automatisch verkündet wird." #: 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" " the \"add\" command to add feeds to this plugin, and use the \"announce\"\n" " command to determine what feeds should be announced in a given channel." msgstr "Dieses plugin wird verwendet um Updates eines RSS Feed in einem Kanal zu verkünden und um die Kopfzeilen des RSS Feeds über einen Befehl zu empfangen. Benutze \"add\" um Feeds zu diesem Plugin hinzuzufügen und benutze \"announce\" um festzulegen ob der Feed im gegeben Kanal verkündet werden soll." #: plugin.py:340 msgid "" "<name> <url>\n" "\n" " Adds a command to this plugin that will look up the RSS feed at the\n" " given URL.\n" " " msgstr "" "<Name> <URL>\n" "\n" "Fügt einen Befehl zu diesem Plugin hinzu, der den RSS Feed von angegebener URL abruft." #: plugin.py:351 msgid "" "<name>\n" "\n" " Removes the command for looking up RSS feeds at <name> from\n" " this plugin.\n" " " msgstr "" "<Name>\n" "\n" "Entfernt den Befehl um RSS Feeds von <Name> abzurufen aus diesem Plugin" #: plugin.py:357 msgid "That's not a valid RSS feed command name." msgstr "Das ist kein gültiger RSS Feed Befehl." #: plugin.py:368 msgid "" "[<channel>]\n" "\n" " Returns the list of feeds announced in <channel>. <channel> is\n" " only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<Kanal>] \n" "\n" "Gibt eine Liste der Feeds, die in einem <Kanal> verkündet werden, zurück. <Kanal> ist nur nötig falls der Befehl nicht direkt im Kanal abgegeben wird." #: plugin.py:375 msgid "I am currently not announcing any feeds." msgstr "Momentan kündige ich keine Feeds an." #: plugin.py:380 msgid "" "[<channel>] <name|url> [<name|url> ...]\n" "\n" " Adds the list of feeds to the current list of announced feeds in\n" " <channel>. Valid feeds include the names of registered feeds as\n" " well as URLs for RSS feeds. <channel> is only necessary if the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<Kanal>] <Name|URL> [<Name|URL> ...]\n" "\n" "Fügt die Liste der Feeds zur momentanen Liste der verkündeten Feeds eines <Kanal> hinzu. Zulässige Feeds umfassen die Namen von registrierten Feed als auch URLs für RSS Feeds. <Kanal> ist nur nötig falls die Nachricht nicht im Kanal gesendet wird." #: plugin.py:398 msgid "" "[<channel>] <name|url> [<name|url> ...]\n" "\n" " Removes the list of feeds from the current list of announced feeds\n" " in <channel>. Valid feeds include the names of registered feeds as\n" " well as URLs for RSS feeds. <channel> is only necessary if the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<Kanal>] <Name|URL> [<Name|URL> ...]\n" "\n" "Entfernt die Liste der Feeds von der momentanen Liste der verkündeten Feeds eines <Kanal>. Zulässige Feeds umfassen die Namen von registrierten Feed als auch URLs für RSS Feeds. <Kanal> ist nur nötig falls die Nachricht nicht im Kanal gesendet wird." #: plugin.py:416 msgid "" "<url> [<number of headlines>]\n" "\n" " Gets the title components of the given RSS feed.\n" " If <number of headlines> is given, return only that many headlines.\n" " " msgstr "" "<URL> [<Anzahl der Kopfzeilen>]\n" "\n" "Empfängt die Titel des angegeben RSS Feeds. Falls <Anzahl der Kopfzeilen> angegeben wurde, werden nur soviele Kopfzeilen ausgegeben." #: plugin.py:429 msgid "Couldn't get RSS feed." msgstr "Konnte den RSS Feed nicht bekommen." #: plugin.py:444 msgid "" "<url|feed>\n" "\n" " Returns information from the given RSS feed, namely the title,\n" " URL, description, and last update date, if available.\n" " " msgstr "" "<URL|Feed>\n" "\n" "Gibt Informationen zum angegeben RSS Feed aus. Den Titel, URL, Beschreibung und das Datum des letzten Updates, wenn verfügbar." #: plugin.py:457 msgid "I couldn't retrieve that RSS feed." msgstr "Ich konnte den RSS Feed nicht empfangen." #: plugin.py:470 msgid "Title: %s; URL: %u; Description: %s; Last updated: %s." msgstr "Titel: %s; URL: %u; Beschreibung: %s; Zuletzt aktualisiert: %s." �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/RSS/locales/fi.po�������������������������������������������������������0000755�0001750�0001750�00000031751�13634634532�020041� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# RSS plugin in Limnoria. # Copyright (C) Limnoria 2011-2014 # Mikaela Suomalainen <mkaysi@outlook.com>, 2011-2014. # msgid "" msgstr "" "Project-Id-Version: RSS plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 11:59+EET\n" "PO-Revision-Date: 2014-12-20 12:12+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: Finnish <>\n" "Language: fi\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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.10\n" #: config.py:49 msgid "Valid values include 'asInFeed', 'oldestFirst', 'newestFirst'." msgstr "" "Kelvolliset arvot ovat 'asInFeed' (kuten syötteessä), 'oldestFirst' (vanhin " "ensin), 'newestFirst' (uusin ensin)." #: config.py:55 msgid "" "Determines what feeds should be accessible as\n" " commands." msgstr "" "Määrittää mitten syötteiden pitäisi olla käytettävissä\n" " komentoina." #: config.py:62 msgid "" "Determines what string is\n" " used to separate headlines in new feeds." msgstr "" "Määrittää mitä merkkiketjua käytetään\n" " erottamaan otsikot uusissa syötteissä." # This looks like something that shouldn't be translated. #: config.py:65 msgid "$date: $title <$link>" msgstr "$date: $title <$link>" #: config.py:65 #, fuzzy msgid "" "The format the bot\n" " will use for displaying headlines of a RSS feed that is triggered\n" " manually. In addition to fields defined by feedparser ($published\n" " (the entry date), $title, $link, $description, $id, etc.), the " "following\n" " variables can be used: $feed_name, $date (parsed date, as defined in\n" " supybot.reply.format.time)" msgstr "" "Muoto, jota käytetään manuaalisesti pyydetyn RSS-syltteen otsikoissa.\n" " Feedparserin määrittämien kenttien ($published (julkaisuaika), $title, " "$link\n" " $description, $id jne.) lisäksi voidaan käyttää seuraavia muuttujia: " "$feed_name,\n" " $date (parsittu päiväys, jonka määrittää asetus supybot.reply.format.time)" #: config.py:72 msgid "News from $feed_name: $title <$link>" msgstr "Uutisia lähteestä $feed_name: $title <$link>" #: config.py:73 msgid "" "The format the bot will use for displaying headlines of a RSS feed\n" " that is announced. See supybot.plugins.RSS.format for the available\n" " variables." msgstr "" "Muoto, jotaa botti käyttää kuulutettavan RSS-syötteen otsikoiden " "näyttämiseen.\n" " Kelvolliset muuttujat näkee asetuksesta supybot.plugins.RSS.format." #: config.py:81 msgid "" "Determines which RSS feeds\n" " should be announced in the channel; valid input is a list of strings\n" " (either registered RSS feeds or RSS feed URLs) separated by spaces." msgstr "" "Määrittää kanavalla kuulutettavat RSS-syötteet. Kelvollinen asetusarvo\n" " on merkkiketju luettelo (joka sisältää rekisteröityjä RSS-syötteitä tai " "syöte\n" "-osoitteita) välilyönneillä erotettuina." #: config.py:85 msgid "" "Indicates how many seconds the bot will\n" " wait between retrieving RSS feeds; requests made within this period " "will\n" " return cached results." msgstr "" "Määrittää kuinka monta sekuntia botti odottaa\n" " RSS syötteiden päivittämisten välillä; pyynnöt, jotka tehdän tällä " "ajalla\n" " palauttavat välimuistissa olevia tuloksia." #: config.py:89 msgid "" "Determines whether feed items should be\n" " sorted by their update timestamp or kept in the same order as they " "appear\n" " in a feed." msgstr "" "Määrittää pitäisikö syötteiden kohteiden olla järjestettyinä\n" " päivitysaikaleiman mukaan vai pitää samassa järjestyksessä, kuin ne " "ilmestyvät\n" " syötteessä." #: config.py:96 msgid "" "Indicates how many headlines an rss feed\n" " will output by default, if no number is provided." msgstr "" "Ilmaisee kuinka monta otsikkoa RSS-syötteen ulostulossa on oletuksena,\n" " jos määrää ei määritetä komennossa." #: config.py:99 msgid "" "Indicates how many headlines an rss feed\n" " will output when it is first added to announce for a channel." msgstr "" "Ilmaisee kuinka monta otsikkoa RSS-syötteen ulostulossa on ensimmäisellä " "kerralla, kun sen kuuluttaminen\n" " lisätään kanavalle." #: config.py:102 msgid "" "Space separated list of \n" " strings, lets you filter headlines to those containing one or more " "items\n" " in this whitelist." msgstr "" "Välilyönneillä erotettu lista merkkiketjuista, sallii otsikoiden " "suodattamisen\n" " yhteen tai useampaan kohteeseen, jotka ovat tällä valkoisella listalla." #: config.py:106 msgid "" "Space separated list of \n" " strings, lets you filter headlines to those not containing any items\n" " in this blacklist." msgstr "" "Välilyönneillä erotettu lista merkkiketjuista,\n" " tämä sallii kohteiden, jotka ovat tällä mustalla listalla, suodattamisen " "pois." #: plugin.py:107 msgid "" "[<number of headlines>]\n" "\n" " Reports the titles for %s at the RSS feed %u. If\n" " <number of headlines> is given, returns only that many headlines.\n" " RSS feeds are only looked up every supybot.plugins.RSS.waitPeriod\n" " seconds, which defaults to 1800 (30 minutes) since that's what most\n" " websites prefer." msgstr "" "[<otsikoiden määrä>]\n" "\n" " Raportoi otsikot kohteelle %s RSS-syötteessä %u. Jos <otsikoiden määrä> on\n" " annettu, tämä palauttaa vain sen määrän otsikoita. RSS-syötteitä " "tarkistetaan\n" " asetuksen supybot.plugins.RSS.waitPeriod sekuntejen mukaan, joka on\n" " oletuksena 1800 (30 minuuttia), koska se on useimpien verkkosivujen suosima " "aika." #: plugin.py:122 msgid "Return feed items, sorted according to sortFeedItems." msgstr "Palauttaa syötteen kohteet järjestettynä sortFeedItems :in mukaan." #: plugin.py:145 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" " the \"add\" command to add feeds to this plugin, and use the \"announce" "\"\n" " command to determine what feeds should be announced in a given channel." msgstr "" "Tämä lisäosa on hyödyllinen kumpaankin, RSS syötteiden päivitysten " "kuuluttamiseen\n" " kanavalla, ja hakemaan RSS syötteen uusimmat otsikot komennon kautta. " "Käytä\n" " \"add\" komentoa lisätäksesi syötteitä tähän lisäosaan ja \"announce\"\n" " komentoa määrittämään mitkä syötteet pitäisi kuuluttaa annetulla " "kanavalla." #: plugin.py:190 msgid "I already have a command in this plugin named %s." msgstr "Minulla on jo komento %s tässä lisä-osassa." #: plugin.py:196 msgid "I already have a feed with that URL named %s." msgstr "Minulla on jo syöte nimeltä %s." #: plugin.py:206 msgid "" "Feed-specific format. Defaults to\n" " supybot.plugins.RSS.format if empty." msgstr "Syötekohtainen muoto. Oletuksena supybot.plugins.RSS.format." #: plugin.py:209 msgid "" "Feed-specific announce format.\n" " Defaults to supybot.plugins.RSS.announceFormat if empty." msgstr "" "Syötekohtainen kuulutusmuoto. Oletuksena supybot.plugins.RSS.announceFormat." #: plugin.py:382 msgid "" "<name> <url>\n" "\n" " Adds a command to this plugin that will look up the RSS feed at the\n" " given URL.\n" " " msgstr "" "<nimi> <url>\n" "\n" " Lisää tähän lisäosaan komennon, joka hakee RSS syötteen annetusta\n" " URL osoitteesta..\n" " " #: plugin.py:395 msgid "" "<name>\n" "\n" " Removes the command for looking up RSS feeds at <name> from\n" " this plugin.\n" " " msgstr "" "<nimi>\n" "\n" " Poistaa komennon, joka hakee RSS syötteet <nimestä>,\n" " lisäosasta.\n" " " #: plugin.py:402 msgid "That's not a valid RSS feed command name." msgstr "Tuo ei ole kelvollinen RSS sylte komento nimi." #: plugin.py:411 msgid "" "[<channel>]\n" "\n" " Returns the list of feeds announced in <channel>. <channel> is\n" " only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>]\n" "\n" " Palauttaa listan RSS syötteistä, joita kuulutetaan <kanavalla>. " "<Kanava> on\n" " vaadittu vain jos viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:418 msgid "I am currently not announcing any feeds." msgstr "Minä en tällä hetkellä kuuluta yhtään syötettä." #: plugin.py:423 msgid "" "[<channel>] <name|url> [<name|url> ...]\n" "\n" " Adds the list of feeds to the current list of announced feeds " "in\n" " <channel>. Valid feeds include the names of registered feeds " "as\n" " well as URLs for RSS feeds. <channel> is only necessary if the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] <nimi|url> [<nimi|url> ...]\n" "\n" " Lisää listan syötteistä tällä hetkellä\n" " <kanavalla> kuulutettaviin syötteisiin. Kelvolliset syötteet " "sisältävät rekisteröityjen syötteiden nimet,\n" " kuten myös RSS syötteiden URL-osoitteet. <Kanava> on vaadittu " "vain, jos\n" " viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:433 #, fuzzy msgid "These feeds are unknown: %L" msgstr "Nämä syötteet ovat tuntemattomia: %L" #: plugin.py:449 msgid "" "[<channel>] <name|url> [<name|url> ...]\n" "\n" " Removes the list of feeds from the current list of announced " "feeds\n" " in <channel>. Valid feeds include the names of registered feeds " "as\n" " well as URLs for RSS feeds. <channel> is only necessary if the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] <nimi|url> [<nimi|url> ...]\n" "\n" " Poistaa listan RSS syötteistä botin tällä hetkellä <kanavalla> " "kuuluttamista\n" " syötteistä. Kelvolliset syötteen nimet sisältävät\n" " URL osoitteet, kuten myös rekisteröidyt RSS syötteet. <Kanava> " "on vaadittu vain, jos viestiä\n" " ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:467 #, fuzzy msgid "" "<name|url> [<number of headlines>]\n" "\n" " Gets the title components of the given RSS feed.\n" " If <number of headlines> is given, return only that many headlines.\n" " " msgstr "" "<url> [<otsikoiden lukumäärä>]\n" "\n" " Hakee annetun RSS syötteen otsikko komponentit.\n" " Jos <otsikoiden lukumäärä> on annettu, palauttaa vain niin monta " "otsikkoa.\n" " " #: plugin.py:483 msgid "Couldn't get RSS feed." msgstr "RSS syötettä ei pystytty hakemaan." #: plugin.py:497 msgid "" "<url|feed>\n" "\n" " Returns information from the given RSS feed, namely the title,\n" " URL, description, and last update date, if available.\n" " " msgstr "" "<url|syöte>\n" "\n" " Palauttaa tietoja annetusta RSS syötteestä, nimellisesti otsikon,\n" " URL, kuvauksen, ja viimeisen päivityksen, jos saatavilla.\n" " " #: plugin.py:512 msgid "I couldn't retrieve that RSS feed." msgstr "En voinut noutaa tuota RSS syötettä." #: plugin.py:520 #, fuzzy msgid "time unavailable" msgstr "aika ei ole saatavilla" #: plugin.py:521 plugin.py:522 plugin.py:523 msgid "unavailable" msgstr "ei saatavilla" #: plugin.py:525 msgid "Title: %s; URL: %u; Description: %s; Last updated: %s." msgstr "Otsikko: %s; URL: %u; Kuvaus: %s; Viimeeksi päivitetty: %s." #~ msgid "" #~ "Determines whether the bot will bold the title of the feed when\n" #~ " it announces news." #~ msgstr "Määrittää korostetaanko syötteen otsikko uutisia kuuluttaessa." #~ msgid "" #~ "Determines what\n" #~ " prefix is prepended (if any) to the news item announcements made in " #~ "the\n" #~ " channel." #~ msgstr "" #~ "Määrittää kanavalla tehtyihin uutiskuulutuksiin liitettävän etuliitteen " #~ "(jos mikään)." #~ msgid "News from " #~ msgstr "Uutisia kohteesta " #~ msgid ": " #~ msgstr ": " #~ msgid "" #~ "Determines what\n" #~ " suffix is appended to the feed name in a news item." #~ msgstr "Määrittää uutissyötteen nimeen liitettävän jälkiliitteen." #~ msgid "" #~ "Determines whether the bot will list the link\n" #~ " along with the title of the feed when the rss command is called.\n" #~ " supybot.plugins.RSS.announce.showLinks affects whether links will be\n" #~ " listed when a feed is automatically announced." #~ msgstr "" #~ "Määrittää luetteleeko botti linkin\n" #~ " syötteen otsikon kanssa, kun rss komentoa kutsutaan.\n" #~ " supybot.plugins.RSS.announce.showLinks vaikuttaa luetellaanko\n" #~ " linkit, kun syöte on kuulutettu automaattisesti." #~ 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 "" #~ "Määrittää luetteleeko botti linkin\n" #~ " syötteen otsikon kautta, kun syöte kuulutetaan\n" #~ " automaattisesti." �����������������������limnoria-2020.03.17/plugins/RSS/locales/fr.po�������������������������������������������������������0000644�0001750�0001750�00000021677�13634634532�020055� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-01-22 07:53+CET\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: Limnoria <progval@gmail.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" "Language: fr\n" #: config.py:49 msgid "Valid values include 'asInFeed', 'oldestFirst', 'newestFirst'." msgstr "Les valeurs valides sont 'asInFeed', 'oldestFirst', et 'newestFirst'." #: config.py:54 msgid "" "Determines whether the bot will bold the title of the feed when\n" " it announces news." msgstr "" "Détermine si le bot mettera en gras le titre des flux lorsqu'il annoncera " "des news." #: config.py:57 msgid "" "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:60 msgid "" "Determines what\n" " prefix is prepended (if any) to the 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." #: config.py:60 msgid "New news from " msgstr "Nouvelle(s) news de " #: config.py:64 msgid ": " msgstr " : " #: config.py:64 msgid "" "Determines what\n" " suffix is appended to the feed name in a news item." msgstr "" "Détermine quel suffixe est utilisé pour annoncer les news sur le canal." #: config.py:67 msgid "" "Determines which RSS feeds\n" " should be announced in the channel; valid input is a list of strings\n" " (either registered RSS feeds or RSS feed URLs) separated by spaces." msgstr "" "Détermine quels flux RSS seront annoncés sur le canal ; une entrée valide " "est une liste de chaînes (des flux enregistrés ou des URLs de flux RSS), " "séparées par des espaces." #: config.py:71 msgid "" "Indicates how many seconds the bot will\n" " wait between retrieving RSS feeds; requests made within this period " "will\n" " return cached results." msgstr "" "Détermine le temps (en secondes) entre deux rafraichissement des flux RSS. " "Durant cette période, les flux seront mis en cache." #: config.py:75 msgid "" "Determines whether feed items should be\n" " sorted by their update timestamp or kept in the same order as they " "appear\n" " in a feed." msgstr "" "Détermine si les éléments du flux doivent être triés selon la date de leur " "mise à jour ou si ils doivent être conservés dans l'ordre original du flux." #: config.py:83 msgid "" "Determines what feeds should be accessible as\n" " commands." msgstr "Détermine quels flux sont accessibles en tant que commande." #: config.py:86 msgid "" "Determines whether the bot will list the link\n" " along with the title of the feed when the rss command is called.\n" " supybot.plugins.RSS.announce.showLinks affects whether links will be\n" " 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:97 msgid "" "Indicates how many headlines an rss feed\n" " will output by default, if no number is provided." msgstr "" "Indique combien d'éléments un flux rss affichera par défaut, si aucun nombre " "n'est donné." #: config.py:100 msgid "" "Indicates how many headlines an rss feed\n" " will output when it is first added to announce for a channel." msgstr "" "Indique combien d'éléments un flux rss affichera lorsque qu'il vient d'être " "configuré pour être annoncé sur le salon." #: config.py:103 msgid "" "Space separated list of \n" " strings, lets you filter headlines to those containing one or more " "items\n" " in this whitelist." msgstr "" "Liste séparée par des espaces de chaînes, qui vous permet de filtrer les " "éléments par liste blanche." #: config.py:107 msgid "" "Space separated list of \n" " strings, lets you filter headlines to those not containing any items\n" " in this blacklist." msgstr "" "Liste séparée par des espaces de chaînes, qui vous permet de filtrer les " "éléments par liste noire." #: config.py:113 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:57 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" " the \"add\" command to add feeds to this plugin, and use the \"announce" "\"\n" " 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:325 msgid "Return feed items, sorted according to sortFeedItems." msgstr "." #: plugin.py:378 msgid "" "<name> <url>\n" "\n" " Adds a command to this plugin that will look up the RSS feed at the\n" " given URL.\n" " " msgstr "" "<nom> <url>\n" "\n" "Ajoute un commande à ce plugin qui permet de regarder le flux situé à " "l'<url>." #: plugin.py:389 msgid "" "<name>\n" "\n" " Removes the command for looking up RSS feeds at <name> from\n" " this plugin.\n" " " msgstr "" "<nom>\n" "\n" "Supprime le flux des flux qui peuvent être lus grâce à une commande." #: plugin.py:395 msgid "That's not a valid RSS feed command name." msgstr "Ce n'est pas une commande de flux RSS valide" #: plugin.py:406 msgid "" "[<channel>]\n" "\n" " Returns the list of feeds announced in <channel>. <channel> is\n" " only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>]\n" "\n" "Retourne la liste des flux annoncés sur le <canal>. <canal> n'est nécessaire " "que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:413 msgid "I am currently not announcing any feeds." msgstr "Je n'annonce actuellement aucun flux." #: plugin.py:418 msgid "" "[<channel>] <name|url> [<name|url> ...]\n" "\n" " Adds the list of feeds to the current list of announced feeds " "in\n" " <channel>. Valid feeds include the names of registered feeds " "as\n" " well as URLs for RSS feeds. <channel> is only necessary if the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] <nom|url> [<nom|url> ...]\n" "\n" "Ajoute la liste de flux à la liste actuelle des flux annoncés sur le " "<canal>. Vous devez indiquer le <nom> du flux si il est déjà enregistré, ou " "l'<url> dans le cas contraire. <canal> n'est nécessaire que si le message " "n'est pas envoyé sur le canal lui-même." #: plugin.py:436 msgid "" "[<channel>] <name|url> [<name|url> ...]\n" "\n" " Removes the list of feeds from the current list of announced " "feeds\n" " in <channel>. Valid feeds include the names of registered feeds " "as\n" " well as URLs for RSS feeds. <channel> is only necessary if the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] <nom|url> [<nom|url> ...]\n" "\n" "Supprime la liste de flux de la liste actuelle des flux annoncés sur le " "<canal>. Vous devez indiquer le <nom> du flux si il est déjà enregistré, ou " "l'<url> dans le cas contraire. <canal> n'est nécessaire que si le message " "n'est pas envoyé sur le canal lui-même." #: plugin.py:454 msgid "" "<url> [<number of headlines>]\n" "\n" " Gets the title components of the given RSS feed.\n" " If <number of headlines> is given, return only that many headlines.\n" " " msgstr "" "<url> [<nombre de lignes>]\n" "\n" "Récupère le titre des éléments du flux RSS donné. si le <nombre de lignes> " "est donné, ne retourne que ce nombre de lignes d'en-tête." #: plugin.py:467 msgid "Couldn't get RSS feed." msgstr "Ne peut récupérer le flux RSS." #: plugin.py:482 msgid "" "<url|feed>\n" "\n" " Returns information from the given RSS feed, namely the title,\n" " URL, description, and last update date, if available.\n" " " msgstr "" "<url|flux>\n" "\n" "Retourne des informations sur le flux RSS donné : le titre, l'URL, la " "description, et la dernière mise à jour." #: plugin.py:495 msgid "I couldn't retrieve that RSS feed." msgstr "Je ne peux récupérer ce flux RSS." #: plugin.py:508 msgid "Title: %s; URL: %u; Description: %s; Last updated: %s." msgstr "Titre : %s , URL : %u ; description : %s ; dernière mise à jour : %s." �����������������������������������������������������������������limnoria-2020.03.17/plugins/RSS/locales/hu.po�������������������������������������������������������0000644�0001750�0001750�00000017326�13634634532�020056� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Limnoria RSS plugin. # Copyright (C) YEAR ORGANIZATION # nyuszika7h <nyuszika7h@outlook.com>, 2011-2014. # msgid "" msgstr "" "Project-Id-Version: Limnoria RSS\n" "POT-Creation-Date: 2011-10-29 16:08+CEST\n" "PO-Revision-Date: 2014-05-10 23:46+0200\n" "Last-Translator: nyuszika7h <nyuszika7h@outlook.com>\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" #: config.py:50 msgid "" "Determines whether the bot will bold the title of the feed when\n" " it announces news." msgstr "Meghatározza, hogy a bot félkövér betűvel írja-e ki a hírcsatorna címét, amikor bejelenti a híreket." #: config.py:53 msgid "" "Determines what string is\n" " used to separate headlines in new feeds." msgstr "Meghatározza, hogy milyen karakterlánc használt a főcímek elválasztására az új hírcsatornákban." #: config.py:56 msgid "" "Determines what\n" " prefix is prepended (if any) to the news item announcements made in the\n" " channel." msgstr "Meghatározza, hogy milyen előtag (ha van ilyen) legyen a csatornában bejelentett hírek előtt." #: config.py:56 msgid "News from " msgstr "Hírek innen: " #: config.py:60 msgid "" "Determines which RSS feeds\n" " should be announced in the channel; valid input is a list of strings\n" " (either registered RSS feeds or RSS feed URLs) separated by spaces." msgstr "Meghatározza, hogy melyik RSS hírcsatornák legyenek bejelentve a csatornában; az érvényes bemenet: karakterláncok (regisztrált RSS hírcsatornák vagy RSS hírcsatorna hivatkozások) szóközzel elválasztott listája." #: config.py:64 msgid "" "Indicates how many seconds the bot will\n" " wait between retrieving RSS feeds; requests made within this period will\n" " return cached results." msgstr "Azt jelzi, hány másodpercet vár a bot az RSS hírcsatornák letöltése között; az ebben az időszakban végrehajtott kérések gyorsítótárazott eredményeket adnak vissza." #: config.py:68 msgid "" "Determines what feeds should be accessible as\n" " commands." msgstr "Meghatározza, hogy melyik hírcsatornák legyenek elérhetők parancsokként." #: config.py:71 msgid "" "Determines whether the bot will list the link\n" " along with the title of the feed when the rss command is called.\n" " supybot.plugins.RSS.announce.showLinks affects whether links will be\n" " listed when a feed is automatically announced." msgstr "Meghatározza, hogy a bot kiírja-e a hírcsatorna hivatkozását a címmel együtt az rss command meghívásakor. A supybot.plugins.RSS.announce.showLinks befolyásolja, hogy a linkek ki legyenek-e írva, amikor egy hírcsatorna automatikusan be van jelentve." #: config.py:92 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 "Meghatározza, hogy a bot kiírja-e a hírcsatorna hivatkozását a címmel együtt amikor egy hírcsatorna automatikusan be van jelentve." #: plugin.py:62 #, docstring 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" " the \"add\" command to add feeds to this plugin, and use the \"announce\"\n" " command to determine what feeds should be announced in a given channel." msgstr "Ez a bővítmény hasznos mind az RSS hírcsatornák frissítéseinek bejelentésére egy csatornában, és az RSS hírcsatornák főhivatkozásainek letöltésére egy parancssal. Használd az \"add\" parancsot hírcsatornák hozzáadásához, és használd az \"announce\" parancsot, hogy meghatározd, milyen hírcsatornák legyenek bejelentve a megadott csatornában." #: plugin.py:340 #, docstring msgid "" "<name> <url>\n" "\n" " Adds a command to this plugin that will look up the RSS feed at the\n" " given URL.\n" " " msgstr "" "<név> <cím>\n" "\n" "Hozzáad egy parancsot ehhez a bővítményhez, amely le fogja kérdezni az RSS hírcsatornát a megadott hivatkozáson." #: plugin.py:351 #, docstring msgid "" "<name>\n" "\n" " Removes the command for looking up RSS feeds at <name> from\n" " this plugin.\n" " " msgstr "" "<név>\n" "\n" "Eltávolítja a <név> RSS hírcsatornáinak lekérdezéséhez a bővítményből." #: plugin.py:357 msgid "That's not a valid RSS feed command name." msgstr "Ez nem egy érvényes RSS hírcsatorna parancsnév." #: plugin.py:368 #, docstring msgid "" "[<channel>]\n" "\n" " Returns the list of feeds announced in <channel>. <channel> is\n" " only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<csatorna>]\n" "\n" "Kiírja a <csatorna>-n bejelentett hírcsatornákat. <csatorna> csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:375 msgid "I am currently not announcing any feeds." msgstr "Jelenleg nem jelentek be semmilyen hírcsatornát." #: plugin.py:380 #, docstring msgid "" "[<channel>] <name|url> [<name|url> ...]\n" "\n" " Adds the list of feeds to the current list of announced feeds in\n" " <channel>. Valid feeds include the names of registered feeds as\n" " well as URLs for RSS feeds. <channel> is only necessary if the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<csatorna>] <név|hivatkozás> [<név|hivatkozás> ...]\n" "\n" "Hozzáadja a hírcsatornák listáját a bejelentett hírcsatornák listájához <csatorna>-ban. Érvényes hírcsatornák közé tartoznak a regisztrált hírcsatornák nevei és az RSS hírcsatornák hivatkozásai. <csatorna> csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:398 #, docstring msgid "" "[<channel>] <name|url> [<name|url> ...]\n" "\n" " Removes the list of feeds from the current list of announced feeds\n" " in <channel>. Valid feeds include the names of registered feeds as\n" " well as URLs for RSS feeds. <channel> is only necessary if the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<csatorna>] <név|hivatkozás> [<név|hivatkozás> ...]\n" "\n" "Eltávolítja a hírcsatornák listáját a jelenleg bejelentett hírcsatornák listájából <csatorna>-ban. Érvényes hírcsatornák közé tartoznak a regisztrált hírcsatornák nevei és az RSS hírcsatornák hivatkozásai. <csatorna> csak akkor szükséges, ha az üzenet nem a csatornában van elküldve." #: plugin.py:416 #, docstring msgid "" "<url> [<number of headlines>]\n" "\n" " Gets the title components of the given RSS feed.\n" " If <number of headlines> is given, return only that many headlines.\n" " " msgstr "" "<hivatkozás> [<főcímek száma>]\n" "\n" "Lekérdezi a főcímeket a megadott RSS hírcsatornából. Ha <főcímek száma> meg van adva, csak annyi főcímet ír ki." #: plugin.py:429 msgid "Couldn't get RSS feed." msgstr "Nem sikerült elérni az RSS hírcsatornát." #: plugin.py:444 #, docstring msgid "" "<url|feed>\n" "\n" " Returns information from the given RSS feed, namely the title,\n" " URL, description, and last update date, if available.\n" " " msgstr "" "<hivatkozás|hírcsatorna>\n" "\n" "Információt ír ki a megadott RSS hírcsatornáról, név szerint a címet, a hivatkozást, a leírást és a legutóbbi frissítés idejét, ha elérhető." #: plugin.py:457 msgid "I couldn't retrieve that RSS feed." msgstr "Nem tudtam lekérdezni a megadott RSS hírcsatornát." #: plugin.py:470 msgid "Title: %s; URL: %u; Description: %s; Last updated: %s." msgstr "Cím: %s; Hivatkozás: %u; Leírás: %s; Legutóbbi frissítés: %s." ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/RSS/locales/it.po�������������������������������������������������������0000644�0001750�0001750�00000017575�13634634532�020064� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2012-06-03 04:49+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:49 #, docstring msgid "Valid values include 'asInFeed', 'oldestFirst', 'newestFirst'." msgstr "I valori validi sono 'asInFeed', 'oldestFirst', 'newestFirst'." #: config.py:54 #, fuzzy msgid "" "Determines whether the bot will bold the title of the feed when\n" " it announces news." msgstr "" "Determina se il bot mostrerà il titolo del feed in grassetto quando annuncia nuove notizie." #: config.py:57 msgid "" "Determines what string is\n" " used to separate headlines in new feeds." msgstr "" "Determina quale stringa utilizzare per separare i titoli nei nuovi feed." #: config.py:60 #, fuzzy msgid "" "Determines what\n" " prefix is prepended (if any) to the news item announcements made in the\n" " channel." msgstr "" "Determina quale prefisso (eventuale) utilizzare per annunciare le notizie in canale." #: config.py:60 #, fuzzy msgid "News from " msgstr "Nuove notizie da " #: config.py:64 msgid "" "Determines which RSS feeds\n" " should be announced in the channel; valid input is a list of strings\n" " (either registered RSS feeds or RSS feed URLs) separated by spaces." msgstr "" "Determina quali feed RSS sono annunciati in canale; una voce valida è un elenco di stringhe\n" " (sia di feed registrati sia di URL di feed) separate da spazi." #: config.py:68 msgid "" "Indicates how many seconds the bot will\n" " wait between retrieving RSS feeds; requests made within this period will\n" " return cached results." msgstr "" "Indica ogni quanti secondi il bot recupererà i feed RSS; le richieste effettuate\n" " entro questo periodo verranno memorizzate nella cache." #: config.py:72 msgid "" "Determines whether feed items should be\n" " sorted by their update timestamp or kept in the same order as they appear\n" " in a feed." msgstr "" "Determina se gli elementi del feed debbano essere ordinati per data di aggiornamento\n" " o mantenuti nello stesso ordine con il quale appaiono." #: config.py:76 msgid "" "Determines what feeds should be accessible as\n" " commands." msgstr "" "Determina quali feed siano accessibili come comandi." #: config.py:79 msgid "" "Determines whether the bot will list the link\n" " along with the title of the feed when the rss command is called.\n" " supybot.plugins.RSS.announce.showLinks affects whether links will be\n" " listed when a feed is automatically announced." msgstr "" "Determina se il bot elencherà i link con il titolo del feed quando viene richiamato\n" " il comando \"rss\". La variabile supybot.plugins.RSS.announce.showLinks determina\n" " se i link saranno mostrati quando un feed è annunciato automaticamente." #: config.py:100 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 "" "Determina se il bot elencherà i link con il titolo del feed quando questo è annunciato automaticamente." #: plugin.py:62 #, docstring 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" " the \"add\" command to add feeds to this plugin, and use the \"announce\"\n" " command to determine what feeds should be announced in a given channel." msgstr "" "Questo plugin è utile sia per annunciare feed RSS in un canale, sia per recuperare\n" " i titoli dei feed tramite un comando. Utilizza il comando \"add\" per aggiungere\n" " feed e \"announce\" per determinare quali feed devono essere annunciati in un dato canale." #: plugin.py:304 #, docstring msgid "Return feed items, sorted according to sortFeedItems." msgstr "Riporta gli elementi del feed in base a sortFeedItems." #: plugin.py:358 #, docstring msgid "" "<name> <url>\n" "\n" " Adds a command to this plugin that will look up the RSS feed at the\n" " given URL.\n" " " msgstr "" "<nome> <url>\n" "\n" " Aggiunge un comando a questo plugin che cercherà i feed RSS all'URL specificato.\n" " " #: plugin.py:369 #, docstring msgid "" "<name>\n" "\n" " Removes the command for looking up RSS feeds at <name> from\n" " this plugin.\n" " " msgstr "" "<nome>\n" "\n" " Rimuove il comando per cercare feed RSS con <nome>.\n" " " #: plugin.py:375 msgid "That's not a valid RSS feed command name." msgstr "Questo non è un comando di feed RSS valido." #: plugin.py:386 #, docstring msgid "" "[<channel>]\n" "\n" " Returns the list of feeds announced in <channel>. <channel> is\n" " only necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>]\n" "\n" " Restituisce l'elenco dei feed annunciati in <canale>. <canale> è\n" " necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:393 msgid "I am currently not announcing any feeds." msgstr "Attualmente non sto annunciando alcun feed." #: plugin.py:398 #, docstring msgid "" "[<channel>] <name|url> [<name|url> ...]\n" "\n" " Adds the list of feeds to the current list of announced feeds in\n" " <channel>. Valid feeds include the names of registered feeds as\n" " well as URLs for RSS feeds. <channel> is only necessary if the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] <nome|url> [<nome|url> ...]\n" "\n" " Aggiunge l'elenco dei feed all'attuale elenco di quelli da annunciare in\n" " <canale>. Valori validi includono sia i nomi dei feed registrati sia i loro URL.\n" " <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:416 #, docstring msgid "" "[<channel>] <name|url> [<name|url> ...]\n" "\n" " Removes the list of feeds from the current list of announced feeds\n" " in <channel>. Valid feeds include the names of registered feeds as\n" " well as URLs for RSS feeds. <channel> is only necessary if the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] <nome|url> [<nome|url> ...]\n" "\n" " Rimuove l'elenco dei feed dall'attuale elenco dei feed da annunciare in.\n" " <canale>. Valori validi includono sia i nomi dei feed registrati sia i loro URL.\n" " <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:434 #, docstring msgid "" "<url> [<number of headlines>]\n" "\n" " Gets the title components of the given RSS feed.\n" " If <number of headlines> is given, return only that many headlines.\n" " " msgstr "" "<url> [<numero di titoli>]\n" "\n" " Recupera i titoli del feed RSS specificato.\n" " Se <numero di titoli> è fornito, restituisce solo quella quantità.\n" " " #: plugin.py:447 msgid "Couldn't get RSS feed." msgstr "Impossibile recuperare il feed RSS." #: plugin.py:462 #, docstring msgid "" "<url|feed>\n" "\n" " Returns information from the given RSS feed, namely the title,\n" " URL, description, and last update date, if available.\n" " " msgstr "" "<url|feed>\n" "\n" " Riporta informazioni sul feed RSS specificato: titolo,\n" " URL, descrizione e data dell'ultimo aggiornamento.\n" " " #: plugin.py:475 msgid "I couldn't retrieve that RSS feed." msgstr "Non riesco a recuperare questo feed RSS." #: plugin.py:488 msgid "Title: %s; URL: %u; Description: %s; Last updated: %s." msgstr "Titolo: %s; URL: %u; Descrizione: %s; Ultimo aggiornamento: %s." �����������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/RSS/plugin.py�����������������������������������������������������������0000644�0001750�0001750�00000060121�13634634532�017317� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2008-2010, James McCoy # Copyright (c) 2014, Valentin Lorentz # 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 re import os import sys import json import time import types import string import socket import threading import feedparser import supybot.conf as conf import supybot.utils as utils import supybot.world as world from supybot.commands import * import supybot.utils.minisix as minisix import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.registry as registry import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('RSS') if world.testing: INIT_DELAY = 1 else: INIT_DELAY = 10 if minisix.PY2: from urllib2 import ProxyHandler else: from urllib.request import ProxyHandler from .config import register_feed_config def get_feedName(irc, msg, args, state): if irc.isChannel(args[0]): state.errorInvalid('feed name', args[0], 'must not be channel names.') if not registry.isValidRegistryName(args[0]): state.errorInvalid('feed name', args[0], 'Feed names must not include spaces.') state.args.append(callbacks.canonicalName(args.pop(0))) addConverter('feedName', get_feedName) announced_headlines_filename = \ conf.supybot.directories.data.dirize('RSS_announced.flat') def only_one_at_once(f): lock = [False] def newf(*args, **kwargs): if lock[0]: return lock[0] = True try: f(*args, **kwargs) finally: lock[0] = False return newf def get_entry_id(entry): # in order, try elements to use as unique identifier. # http://validator.w3.org/feed/docs/rss2.html#hrelementsOfLtitemgt id_elements = ('id', 'link', 'title', 'description') for id_element in id_elements: try: return getattr(entry, id_element) except AttributeError: pass raise ValueError('Feed entry is missing both title and description') class InvalidFeedUrl(ValueError): pass class Feed: __slots__ = ('url', 'name', 'data', 'last_update', 'entries', 'etag', 'modified', 'initial', 'lock', 'announced_entries', 'last_exception') def __init__(self, name, url, initial, plugin_is_loading=False, announced=None): assert name, name if not url: url = name if not utils.web.httpUrlRe.match(url): raise InvalidFeedUrl(url) self.name = name self.url = url self.initial = initial self.data = None # We don't want to fetch feeds right after the plugin is # loaded (the bot could be starting, and thus already busy) self.last_update = 0 self.entries = [] self.etag = None self.modified = None self.lock = threading.Lock() self.announced_entries = announced or \ utils.structures.TruncatableSet() self.last_exception = None def __repr__(self): return 'Feed(%r, %r, %r, <bool>, %r)' % \ (self.name, self.url, self.initial, self.announced_entries) def get_command(self, plugin): docstring = format(_("""[<number of headlines>] Reports the titles for %s at the RSS feed %u. If <number of headlines> is given, returns only that many headlines. RSS feeds are only looked up every supybot.plugins.RSS.waitPeriod seconds, which defaults to 1800 (30 minutes) since that's what most websites prefer."""), self.name, self.url) def f(self2, irc, msg, args): args.insert(0, self.name) self2.rss(irc, msg, args) f = utils.python.changeFunctionName(f, self.name, docstring) f = types.MethodType(f, plugin) return f _sort_parameters = { 'oldestFirst': (('published_parsed', 'updated_parsed'), False), 'newestFirst': (('published_parsed', 'updated_parsed'), True), 'outdatedFirst': (('updated_parsed', 'published_parsed'), False), 'updatedFirst': (('updated_parsed', 'published_parsed'), True), } def _sort_arguments(order): (fields, reverse) = _sort_parameters[order] def key(entry): for field in fields: if field in entry: return entry[field] raise KeyError('No date field in entry.') return (key, reverse) def sort_feed_items(items, order): """Return feed items, sorted according to sortFeedItems.""" if order == 'asInFeed': return items (key, reverse) = _sort_arguments(order) try: sitems = sorted(items, key=key, reverse=reverse) except KeyError: # feedparser normalizes required timestamp fields in ATOM and RSS # to the "published"/"updated" fields. Feeds missing it are unsortable by date. return items return sitems def load_announces_db(fd): return dict((name, utils.structures.TruncatableSet(entries)) for (name, entries) in json.load(fd).items()) def save_announces_db(db, fd): json.dump(dict((name, list(entries)) for (name, entries) in db), fd) class RSS(callbacks.Plugin): """This plugin is useful both for announcing updates to RSS feeds in a channel, and for retrieving the headlines of RSS feeds via command. Use the "add" command to add feeds to this plugin, and use the "announce" command to determine what feeds should be announced in a given channel.""" threaded = True def __init__(self, irc): self.__parent = super(RSS, self) self.__parent.__init__(irc) if world.starting: self._init_time = time.time() # To delay loading the feeds else: self._init_time = 0 # Scheme: {name: url} self.feed_names = callbacks.CanonicalNameDict() # Scheme: {url: feed} self.feeds = {} if os.path.isfile(announced_headlines_filename): with open(announced_headlines_filename) as fd: announced = load_announces_db(fd) else: announced = {} for name in self.registryValue('feeds'): try: url = self.registryValue(registry.join(['feeds', name])) except registry.NonExistentRegistryEntry: self.log.warning('%s is not a registered feed, removing.',name) continue try: self.register_feed(name, url, True, True, announced.get(name, [])) except InvalidFeedUrl: self.log.error('%s is not a valid feed, removing.', name) continue world.flushers.append(self._flush) def die(self): self._flush() world.flushers.remove(self._flush) self.__parent.die() def _flush(self): l = [(f.name, f.announced_entries) for f in self.feeds.values()] with utils.file.AtomicFile(announced_headlines_filename, 'w', backupDir='/dev/null') as fd: save_announces_db(l, fd) ################## # Feed registering def assert_feed_does_not_exist(self, name, url=None): if self.isCommandMethod(name): s = format(_('I already have a command in this plugin named %s.'), name) raise callbacks.Error(s) if url: feed = self.feeds.get(url) if feed and feed.name != feed.url: s = format(_('I already have a feed with that URL named %s.'), feed.name) raise callbacks.Error(s) def register_feed(self, name, url, initial, plugin_is_loading, announced=None): self.feed_names[name] = url self.feeds[url] = Feed(name, url, initial, plugin_is_loading, announced) def remove_feed(self, feed): del self.feed_names[feed.name] del self.feeds[feed.url] conf.supybot.plugins.RSS.feeds().remove(feed.name) conf.supybot.plugins.RSS.feeds.unregister(feed.name) ################## # Methods handling def isCommandMethod(self, name): if not self.__parent.isCommandMethod(name): return bool(self.get_feed(name)) else: return True def listCommands(self): return self.__parent.listCommands(self.feed_names.keys()) def getCommandMethod(self, command): try: return self.__parent.getCommandMethod(command) except AttributeError: return self.get_feed(command[0]).get_command(self) def __call__(self, irc, msg): self.__parent.__call__(irc, msg) threading.Thread(target=self.update_feeds).start() ################## # Status accessors def get_feed(self, name): return self.feeds.get(self.feed_names.get(name, name), None) def is_expired(self, feed): assert feed period = self.registryValue('waitPeriod') if feed.name != feed.url: # Named feed specific_period = self.registryValue('feeds.%s.waitPeriod' % feed.name) if specific_period: period = specific_period event_horizon = time.time() - period return feed.last_update < event_horizon ############### # Feed fetching def update_feed(self, feed): handlers = [] if utils.web.proxy(): handlers.append(ProxyHandler( {'http': utils.force(utils.web.proxy())})) handlers.append(ProxyHandler( {'https': utils.force(utils.web.proxy())})) with feed.lock: d = feedparser.parse(feed.url, etag=feed.etag, modified=feed.modified, handlers=handlers) if 'status' not in d or d.status != 304: # Not modified if 'etag' in d: feed.etag = d.etag if 'modified' in d: feed.modified = d.modified feed.data = d.feed feed.entries = d.entries feed.last_update = time.time() # feedparser will store soft errors in bozo_exception and set # the "bozo" bit to 1 on supported platforms: # https://pythonhosted.org/feedparser/bozo.html # If this error caused us to e.g. not get any entries at all, # it may be helpful to show it as well. if getattr(d, 'bozo', 0) and hasattr(d, 'bozo_exception'): feed.last_exception = d.bozo_exception else: feed.last_exception = None (initial, feed.initial) = (feed.initial, False) self.announce_feed(feed, initial) def update_feed_if_needed(self, feed): if self.is_expired(feed) and \ self._init_time + INIT_DELAY < time.time(): self.update_feed(feed) @only_one_at_once def update_feeds(self): announced_feeds = set() for irc in world.ircs: for channel in irc.state.channels: announced_feeds |= self.registryValue('announce', channel, irc.network) for name in announced_feeds: feed = self.get_feed(name) if not feed: self.log.warning('Feed %s is announced but does not exist.', name) continue self.update_feed_if_needed(feed) def get_new_entries(self, feed): with feed.lock: entries = feed.entries new_entries = [entry for entry in entries if get_entry_id(entry) not in feed.announced_entries] if not new_entries: return [] feed.announced_entries |= set(get_entry_id(entry) for entry in new_entries) # We keep a little more because we don't want to re-announce # oldest entries if one of the newest gets removed. feed.announced_entries.truncate(10*len(entries)) return new_entries def announce_feed(self, feed, initial): new_entries = self.get_new_entries(feed) order = self.registryValue('sortFeedItems') new_entries = sort_feed_items(new_entries, 'newestFirst') for irc in world.ircs: for channel in irc.state.channels: if feed.name not in self.registryValue('announce', channel, irc.network): continue if initial: max_entries = self.registryValue( 'initialAnnounceHeadlines', channel, irc.network) else: max_entries = self.registryValue( 'maximumAnnounceHeadlines', channel, irc.network) announced_entries = new_entries[0:max_entries] announced_entries = sort_feed_items(announced_entries, order) for entry in announced_entries: self.announce_entry(irc, channel, feed, entry) ################# # Entry rendering def should_send_entry(self, network, channel, entry): whitelist = self.registryValue('keywordWhitelist', channel, network) blacklist = self.registryValue('keywordBlacklist', channel, network) # fix shadowing by "from supybot.commands import *" try: all = __builtins__.all any = __builtins__.any except AttributeError: all = __builtins__['all'] any = __builtins__['any'] title = getattr(entry, 'title', '') description = getattr(entry, 'description', '') if whitelist: if all(kw not in title and kw not in description for kw in whitelist): return False if blacklist: if any(kw in title or kw in description for kw in blacklist): return False return True _normalize_entry = utils.str.multipleReplacer( {'\r': ' ', '\n': ' ', '\x00': ''}) def format_entry(self, network, channel, feed, entry, is_announce): key_name = 'announceFormat' if is_announce else 'format' if feed.name in self.registryValue('feeds'): specific_key_name = registry.join(['feeds', feed.name, key_name]) template = self.registryValue(specific_key_name, channel, network) or \ self.registryValue(key_name, channel, network) else: template = self.registryValue(key_name, channel, network) date = entry.get('published_parsed') date = utils.str.timestamp(date) s = string.Template(template).substitute( entry, feed_name=feed.name, date=date) return self._normalize_entry(s) def announce_entry(self, irc, channel, feed, entry): if self.should_send_entry(irc.network, channel, entry): s = self.format_entry(irc.network, channel, feed, entry, True) if self.registryValue('notice', channel, irc.network): m = ircmsgs.notice(channel, s) else: m = ircmsgs.privmsg(channel, s) irc.queueMsg(m) ########## # Commands @internationalizeDocstring def add(self, irc, msg, args, name, url): """<name> <url> Adds a command to this plugin that will look up the RSS feed at the given URL. """ self.assert_feed_does_not_exist(name, url) register_feed_config(name, url) self.register_feed(name, url, True, False) irc.replySuccess() add = wrap(add, ['feedName', 'url']) @internationalizeDocstring def remove(self, irc, msg, args, name): """<name> Removes the command for looking up RSS feeds at <name> from this plugin. """ feed = self.get_feed(name) if not feed: irc.error(_('That\'s not a valid RSS feed command name.')) return self.remove_feed(feed) irc.replySuccess() remove = wrap(remove, ['feedName']) class announce(callbacks.Commands): @internationalizeDocstring def list(self, irc, msg, args, channel): """[<channel>] Returns the list of feeds announced in <channel>. <channel> is only necessary if the message isn't sent in the channel itself. """ announce = conf.supybot.plugins.RSS.announce feeds = format('%L', list(announce.get(channel)())) irc.reply(feeds or _('I am currently not announcing any feeds.')) list = wrap(list, ['channel',]) @internationalizeDocstring def add(self, irc, msg, args, channel, feeds): """[<channel>] <name|url> [<name|url> ...] Adds the list of feeds to the current list of announced feeds in <channel>. Valid feeds include the names of registered feeds as well as URLs for RSS feeds. <channel> is only necessary if the message isn't sent in the channel itself. """ plugin = irc.getCallback('RSS') invalid_feeds = [x for x in feeds if not plugin.get_feed(x) and not utils.web.urlRe.match(x)] if invalid_feeds: irc.error(format(_('These feeds are unknown: %L'), invalid_feeds), Raise=True) announce = conf.supybot.plugins.RSS.announce S = announce.get(channel)() for name in feeds: S.add(name) announce.get(channel).setValue(S) irc.replySuccess() for name in feeds: feed = plugin.get_feed(name) if not feed: register_feed_config(name, name) plugin.register_feed(name, name, True, False) feed = plugin.get_feed(name) plugin.announce_feed(feed, True) add = wrap(add, [('checkChannelCapability', 'op'), many(first('url', 'feedName'))]) @internationalizeDocstring def remove(self, irc, msg, args, channel, feeds): """[<channel>] <name|url> [<name|url> ...] Removes the list of feeds from the current list of announced feeds in <channel>. Valid feeds include the names of registered feeds as well as URLs for RSS feeds. <channel> is only necessary if the message isn't sent in the channel itself. """ announce = conf.supybot.plugins.RSS.announce S = announce.get(channel)() for feed in feeds: S.discard(feed) announce.get(channel).setValue(S) irc.replySuccess() remove = wrap(remove, [('checkChannelCapability', 'op'), many(first('url', 'feedName'))]) @internationalizeDocstring def channels(self, irc, msg, args, feed): """<name|url> Returns a list of channels that the given feed name or URL is being announced to. """ plugin = irc.getCallback('RSS') if not plugin.get_feed(feed): irc.error(_("Unknown feed %s" % feed), Raise=True) channels = [] for ircnet in world.ircs: for channel in ircnet.state.channels: if feed in plugin.registryValue('announce', channel, ircnet.network): channels.append(ircnet.network + channel) if channels: irc.reply(format("%s is announced to %L.", feed, channels)) else: irc.reply("%s is not announced to any channels." % feed) channels = wrap(channels, ['feedName']) @internationalizeDocstring def rss(self, irc, msg, args, url, n): """<name|url> [<number of headlines>] Gets the title components of the given RSS feed. If <number of headlines> is given, return only that many headlines. """ self.log.debug('Fetching %u', url) try: feed = self.get_feed(url) if not feed: feed = Feed(url, url, True) except InvalidFeedUrl: irc.error('%s is not a valid feed URL or name.' % url, Raise=True) channel = msg.channel self.update_feed_if_needed(feed) entries = feed.entries if not entries: s = _('Couldn\'t get RSS feed.') # If we got a soft parsing exception on our last run, show the error. if feed.last_exception is not None: s += _(' Parser error: ') s += str(feed.last_exception) irc.error(s) return n = n or self.registryValue('defaultNumberOfHeadlines', channel, irc.network) entries = list(filter(lambda e:self.should_send_entry(irc.network, channel, e), feed.entries)) entries = entries[:n] headlines = map(lambda e:self.format_entry(irc.network, channel, feed, e, False), entries) sep = self.registryValue('headlineSeparator', channel, irc.network) irc.replies(headlines, joiner=sep) rss = wrap(rss, [first('url', 'feedName'), additional('int')]) @internationalizeDocstring def info(self, irc, msg, args, url): """<url|feed> Returns information from the given RSS feed, namely the title, URL, description, and last update date, if available. """ try: url = self.registryValue('feeds.%s' % url) except registry.NonExistentRegistryEntry: pass feed = self.get_feed(url) if not feed: feed = Feed(url, url, True) self.update_feed_if_needed(feed) info = feed.data if not info: irc.error(_('I couldn\'t retrieve that RSS feed.')) return # check the 'modified_parsed' key, if it's there, convert it here first if 'modified' in info: seconds = time.mktime(info['modified_parsed']) now = time.mktime(time.gmtime()) when = utils.timeElapsed(now - seconds) + ' ago' else: when = _('time unavailable') title = info.get('title', _('unavailable')) desc = info.get('description', _('unavailable')) link = info.get('link', _('unavailable')) # The rest of the entries are all available in the channel key response = format(_('Title: %s; URL: %u; ' 'Description: %s; Last updated: %s.'), title, link, desc, when) irc.reply(utils.str.normalizeWhitespace(response)) info = wrap(info, [first('url', 'feedName')]) RSS = internationalizeDocstring(RSS) Class = RSS # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/RSS/test.py�������������������������������������������������������������0000644�0001750�0001750�00000046267�13634634532�017017� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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 sys import feedparser from supybot.test import * import supybot.conf as conf import supybot.utils.minisix as minisix xkcd_old = """<?xml version="1.0" encoding="utf-8"?> <rss version="2.0"><channel><title>xkcd.comhttp://xkcd.com/xkcd.com: A webcomic of romance and math humor.enSnake Factshttp://xkcd.com/1398/<img src="http://imgs.xkcd.com/comics/snake_facts.png" title="Biologically speaking, what we call a 'snake' is actually a human digestive tract which has escaped from its host." alt="Biologically speaking, what we call a 'snake' is actually a human digestive tract which has escaped from its host." />Wed, 23 Jul 2014 04:00:00 -0000http://xkcd.com/1398/ """ xkcd_new = """ xkcd.comhttp://xkcd.com/xkcd.com: A webcomic of romance and math humor.enTelescopes: Refractor vs Reflectorhttp://xkcd.com/1791/<img src="http://imgs.xkcd.com/comics/telescopes_refractor_vs_reflector.png" title="On the other hand, the refractor's limited light-gathering means it's unable to make out shadow people or the dark god Chernabog." alt="On the other hand, the refractor's limited light-gathering means it's unable to make out shadow people or the dark god Chernabog." />Fri, 27 Jan 2017 05:00:00 -0000http://xkcd.com/1791/Chaoshttp://xkcd.com/1399/<img src="http://imgs.xkcd.com/comics/chaos.png" title="Although the oral exam for the doctorate was just 'can you do that weird laugh?'" alt="Although the oral exam for the doctorate was just 'can you do that weird laugh?'" />Fri, 25 Jul 2014 04:00:00 -0000http://xkcd.com/1399/Snake Factshttp://xkcd.com/1398/<img src="http://imgs.xkcd.com/comics/snake_facts.png" title="Biologically speaking, what we call a 'snake' is actually a human digestive tract which has escaped from its host." alt="Biologically speaking, what we call a 'snake' is actually a human digestive tract which has escaped from its host." />Wed, 23 Jul 2014 04:00:00 -0000http://xkcd.com/1398/ """ not_well_formed = """ this is missing a close tag <link>http://example.com/</link> <description>this dummy feed has no elements</description> <language>en</language> </channel> </rss> """ def constant(content): if minisix.PY3: content = content.encode() def f(*args, **kwargs): return minisix.io.BytesIO(content) return f url = 'http://www.advogato.org/rss/articles.xml' class RSSTestCase(ChannelPluginTestCase): plugins = ('RSS','Plugin') def testRssAddBadName(self): self.assertError('rss add "foo bar" %s' % url) def testCantAddFeedNamedRss(self): self.assertError('rss add rss %s' % url) def testCantRemoveMethodThatIsntFeed(self): self.assertError('rss remove rss') def testCantAddDuplicatedFeed(self): self.assertNotError('rss add xkcd http://xkcd.com/rss.xml') try: self.assertError('rss add xkcddup http://xkcd.com/rss.xml') finally: self.assertNotError('rss remove xkcd') def testInitialAnnounceNewest(self): old_open = feedparser._open_resource feedparser._open_resource = constant(xkcd_new) timeFastForward(1.1) try: with conf.supybot.plugins.RSS.sortFeedItems.context('newestFirst'): with conf.supybot.plugins.RSS.initialAnnounceHeadlines.context(1): self.assertNotError('rss add xkcd http://xkcd.com/rss.xml') self.assertNotError('rss announce add xkcd') self.assertRegexp(' ', 'Telescopes') finally: self._feedMsg('rss announce remove xkcd') self._feedMsg('rss remove xkcd') feedparser._open_resource = old_open def testInitialAnnounceOldest(self): old_open = feedparser._open_resource feedparser._open_resource = constant(xkcd_new) timeFastForward(1.1) try: with conf.supybot.plugins.RSS.initialAnnounceHeadlines.context(1): with conf.supybot.plugins.RSS.sortFeedItems.context('oldestFirst'): self.assertNotError('rss add xkcd http://xkcd.com/rss.xml') self.assertNotError('rss announce add xkcd') self.assertRegexp(' ', 'Telescopes') finally: self._feedMsg('rss announce remove xkcd') self._feedMsg('rss remove xkcd') feedparser._open_resource = old_open def testNoInitialAnnounce(self): old_open = feedparser._open_resource feedparser._open_resource = constant(xkcd_old) timeFastForward(1.1) try: with conf.supybot.plugins.RSS.initialAnnounceHeadlines.context(0): self.assertNotError('rss add xkcd http://xkcd.com/rss.xml') self.assertNotError('rss announce add xkcd') self.assertNoResponse(' ', timeout=0.1) finally: self._feedMsg('rss announce remove xkcd') self._feedMsg('rss remove xkcd') feedparser._open_resource = old_open def testAnnounce(self): old_open = feedparser._open_resource feedparser._open_resource = constant(xkcd_old) timeFastForward(1.1) try: self.assertError('rss announce add xkcd') self.assertNotError('rss add xkcd http://xkcd.com/rss.xml') self.assertNotError('rss announce add xkcd') self.assertNotError(' ') with conf.supybot.plugins.RSS.sortFeedItems.context('oldestFirst'): with conf.supybot.plugins.RSS.waitPeriod.context(1): timeFastForward(1.1) self.assertNoResponse(' ', timeout=0.1) feedparser._open_resource = constant(xkcd_new) self.assertNoResponse(' ', timeout=0.1) timeFastForward(1.1) self.assertRegexp(' ', 'Chaos') self.assertRegexp(' ', 'Telescopes') self.assertNoResponse(' ') finally: self._feedMsg('rss announce remove xkcd') self._feedMsg('rss remove xkcd') feedparser._open_resource = old_open def testMaxAnnounces(self): old_open = feedparser._open_resource feedparser._open_resource = constant(xkcd_old) timeFastForward(1.1) try: self.assertError('rss announce add xkcd') self.assertNotError('rss add xkcd http://xkcd.com/rss.xml') self.assertNotError('rss announce add xkcd') self.assertNotError(' ') with conf.supybot.plugins.RSS.sortFeedItems.context('oldestFirst'): with conf.supybot.plugins.RSS.waitPeriod.context(1): with conf.supybot.plugins.RSS.maximumAnnounceHeadlines.context(1): timeFastForward(1.1) self.assertNoResponse(' ', timeout=0.1) feedparser._open_resource = constant(xkcd_new) log.debug('set return value to: %r', xkcd_new) self.assertNoResponse(' ', timeout=0.1) timeFastForward(1.1) self.assertRegexp(' ', 'Telescopes') self.assertNoResponse(' ') finally: self._feedMsg('rss announce remove xkcd') self._feedMsg('rss remove xkcd') feedparser._open_resource = old_open def testAnnounceAnonymous(self): old_open = feedparser._open_resource feedparser._open_resource = constant(xkcd_old) timeFastForward(1.1) try: self.assertNotError('rss announce add http://xkcd.com/rss.xml') self.assertNotError(' ') with conf.supybot.plugins.RSS.waitPeriod.context(1): timeFastForward(1.1) self.assertNoResponse(' ', timeout=0.1) feedparser._open_resource = constant(xkcd_new) self.assertNoResponse(' ', timeout=0.1) timeFastForward(1.1) self.assertRegexp(' ', 'Telescopes') finally: self._feedMsg('rss announce remove http://xkcd.com/rss.xml') self._feedMsg('rss remove http://xkcd.com/rss.xml') feedparser._open_resource = old_open def testAnnounceReload(self): old_open = feedparser._open_resource feedparser._open_resource = constant(xkcd_old) timeFastForward(1.1) try: with conf.supybot.plugins.RSS.waitPeriod.context(1): self.assertNotError('rss add xkcd http://xkcd.com/rss.xml') self.assertNotError('rss announce add xkcd') self.assertNotError(' ', timeout=0.1) self.assertNotError('reload RSS') self.assertNoResponse(' ', timeout=0.1) timeFastForward(1.1) self.assertNoResponse(' ', timeout=0.1) finally: self._feedMsg('rss announce remove xkcd') self._feedMsg('rss remove xkcd') feedparser._open_resource = old_open def testReload(self): old_open = feedparser._open_resource feedparser._open_resource = constant(xkcd_old) timeFastForward(1.1) try: with conf.supybot.plugins.RSS.waitPeriod.context(1): self.assertNotError('rss add xkcd http://xkcd.com/rss.xml') self.assertNotError('rss announce add xkcd') self.assertRegexp(' ', 'Snake Facts') feedparser._open_resource = constant(xkcd_new) self.assertNotError('reload RSS') self.assertRegexp(' ', 'Telescopes') finally: self._feedMsg('rss announce remove xkcd') self._feedMsg('rss remove xkcd') feedparser._open_resource = old_open def testReloadNoDelay(self): # https://github.com/ProgVal/Limnoria/issues/922 old_open = feedparser._open_resource feedparser._open_resource = constant(xkcd_old) timeFastForward(1.1) try: with conf.supybot.plugins.RSS.waitPeriod.context(1): self.assertNotError('rss add xkcd http://xkcd.com/rss.xml') self.assertRegexp('xkcd', 'Snake Facts') self.assertNotError('reload RSS') self.assertRegexp('xkcd', 'Snake Facts') finally: self._feedMsg('rss announce remove xkcd') self._feedMsg('rss remove xkcd') feedparser._open_resource = old_open def testReannounce(self): old_open = feedparser._open_resource feedparser._open_resource = constant(xkcd_old) timeFastForward(1.1) try: self.assertError('rss announce add xkcd') self.assertNotError('rss add xkcd http://xkcd.com/rss.xml') self.assertNotError('rss announce add xkcd') self.assertRegexp(' ', 'Snake Facts') with conf.supybot.plugins.RSS.waitPeriod.context(1): with conf.supybot.plugins.RSS.initialAnnounceHeadlines.context(1): with conf.supybot.plugins.RSS.sortFeedItems.context('oldestFirst'): timeFastForward(1.1) self.assertNoResponse(' ', timeout=0.1) self._feedMsg('rss announce remove xkcd') feedparser._open_resource = constant(xkcd_new) timeFastForward(1.1) self.assertNoResponse(' ', timeout=0.1) self.assertNotError('rss announce add xkcd') timeFastForward(1.1) self.assertRegexp(' ', 'Chaos') self.assertRegexp(' ', 'Telescopes') self.assertNoResponse(' ') finally: self._feedMsg('rss announce remove xkcd') self._feedMsg('rss remove xkcd') feedparser._open_resource = old_open def testFeedSpecificFormat(self): old_open = feedparser._open_resource feedparser._open_resource = constant(xkcd_old) timeFastForward(1.1) try: self.assertNotError('rss add xkcd http://xkcd.com/rss.xml') self.assertNotError('rss add xkcdsec https://xkcd.com/rss.xml') self.assertNotError('config plugins.RSS.feeds.xkcd.format foo') self.assertRegexp('config plugins.RSS.feeds.xkcd.format', 'foo') self.assertRegexp('xkcd', 'foo') self.assertNotRegexp('xkcdsec', 'foo') finally: self._feedMsg('rss remove xkcd') self._feedMsg('rss remove xkcdsec') feedparser._open_resource = old_open def testFeedSpecificWaitPeriod(self): old_open = feedparser._open_resource feedparser._open_resource = constant(xkcd_old) timeFastForward(1.1) try: self.assertNotError('rss add xkcd1 http://xkcd.com/rss.xml') self.assertNotError('rss announce add xkcd1') self.assertNotError('rss add xkcd2 http://xkcd.com/rss.xml&foo') self.assertNotError('rss announce add xkcd2') self.assertNotError(' ') self.assertNotError(' ') with conf.supybot.plugins.RSS.sortFeedItems.context('oldestFirst'): with conf.supybot.plugins.RSS.feeds.xkcd1.waitPeriod.context(1): timeFastForward(1.1) self.assertNoResponse(' ', timeout=0.1) feedparser._open_resource = constant(xkcd_new) self.assertNoResponse(' ', timeout=0.1) timeFastForward(1.1) self.assertRegexp(' ', 'xkcd1.*Chaos') self.assertRegexp(' ', 'xkcd1.*Telescopes') self.assertNoResponse(' ') timeFastForward(1.1) self.assertNoResponse(' ', timeout=0.1) finally: self._feedMsg('rss announce remove xkcd1') self._feedMsg('rss remove xkcd1') self._feedMsg('rss announce remove xkcd2') self._feedMsg('rss remove xkcd2') feedparser._open_resource = old_open def testDescription(self): timeFastForward(1.1) with conf.supybot.plugins.RSS.format.context('$description'): old_open = feedparser._open_resource feedparser._open_resource = constant(xkcd_new) try: self.assertRegexp('rss http://xkcd.com/rss.xml', 'On the other hand, the refractor\'s') finally: feedparser._open_resource = old_open def testBadlyFormedFeedWithNoItems(self): # This combination will cause the RSS command to show the last parser # error. old_open = feedparser._open_resource timeFastForward(1.1) feedparser._open_resource = constant(not_well_formed) try: self.assertRegexp('rss http://example.com/', 'Parser error') finally: feedparser._open_resource = old_open if network: def testRssinfo(self): timeFastForward(1.1) self.assertNotError('rss info %s' % url) self.assertNotError('rss add advogato %s' % url) self.assertNotError('rss info advogato') self.assertNotError('rss info AdVogATo') self.assertNotError('rss remove advogato') def testRssinfoDoesTimeProperly(self): timeFastForward(1.1) self.assertNotRegexp('rss info http://slashdot.org/slashdot.rss', '-1 years') def testAnnounceAdd(self): timeFastForward(1.1) self.assertNotError('rss add advogato %s' % url) self.assertNotError('rss announce add advogato') self.assertRegexp('rss announce channels advogato', 'advogato is announced to.*%s%s' % (self.irc.network, self.channel)) self.assertNotRegexp('rss announce', r'ValueError') self.assertNotError('rss announce remove advogato') self.assertRegexp('rss announce channels advogato', 'advogato is not announced to any channels') self.assertNotError('rss remove advogato') self.assertRegexp('rss announce channels advogato', 'Unknown feed') def testRss(self): timeFastForward(1.1) self.assertNotError('rss %s' % url) m = self.assertNotError('rss %s 2' % url) self.assertTrue(m.args[1].count(' | ') == 1) def testRssAdd(self): timeFastForward(1.1) self.assertNotError('rss add advogato %s' % url) self.assertNotError('advogato') self.assertNotError('rss advogato') self.assertNotError('rss remove advogato') self.assertNotRegexp('list RSS', 'advogato') self.assertError('advogato') self.assertError('rss advogato') def testNonAsciiFeeds(self): timeFastForward(1.1) self.assertNotError('rss http://www.heise.de/newsticker/heise.rdf') self.assertNotError('rss info http://br-linux.org/main/index.xml') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Relay/������������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016062� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Relay/__init__.py�������������������������������������������������������0000644�0001750�0001750�00000004651�13634634532�020173� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Handles relaying between networks. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Relay/config.py���������������������������������������������������������0000644�0001750�0001750�00000011070�13634634532�017672� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.ircutils as ircutils import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Relay') def configure(advanced): from supybot.questions import output, expect, anything, something, yn conf.registerPlugin('Relay', True) if yn(_('Would you like to relay between any channels?')): channels = anything(_('What channels? Separate them by spaces.')) conf.supybot.plugins.Relay.channels.set(channels) if yn(_('Would you like to use color to distinguish between nicks?')): conf.supybot.plugins.Relay.color.setValue(True) output("""Right now there's no way to configure the actual connection to the server. What you'll need to do when the bot finishes starting up is use the 'start' command followed by the 'connect' command. Use the 'help' command to see how these two commands should be used.""") class Ignores(registry.SpaceSeparatedListOf): List = ircutils.IrcSet Value = conf.ValidHostmask class Networks(registry.SpaceSeparatedListOf): List = ircutils.IrcSet Value = registry.String Relay = conf.registerPlugin('Relay') conf.registerChannelValue(Relay, 'color', registry.Boolean(True, _("""Determines whether the bot will color relayed PRIVMSGs so as to make the messages easier to read."""))) conf.registerChannelValue(Relay, 'topicSync', registry.Boolean(True, _("""Determines whether the bot will synchronize topics between networks in the channels it relays."""))) conf.registerChannelValue(Relay, 'hostmasks', registry.Boolean(True, _("""Determines whether the bot will relay the hostmask of the person joining or parting the channel when they join or part."""))) conf.registerChannelValue(Relay, 'includeNetwork', registry.Boolean(True, _("""Determines whether the bot will include the network in relayed PRIVMSGs; if you're only relaying between two networks, it's somewhat redundant, and you may wish to save the space."""))) conf.registerChannelValue(Relay, 'punishOtherRelayBots', registry.Boolean(True, _("""Determines whether the bot will detect other bots relaying and respond by kickbanning them."""))) conf.registerGlobalValue(Relay, 'channels', conf.SpaceSeparatedSetOfChannels([], _("""Determines which channels the bot will relay in."""))) conf.registerChannelValue(Relay.channels, 'joinOnAllNetworks', registry.Boolean(False, _("""Determines whether the bot will always join the channel(s) it relays when connecting to any network. """))) conf.registerChannelValue(Relay, 'ignores', Ignores([], _("""Determines what hostmasks will not be relayed on a channel."""))) conf.registerChannelValue(Relay, 'noticeNonPrivmsgs', registry.Boolean(False, _("""Determines whether the bot will used NOTICEs rather than PRIVMSGs for non-PRIVMSG relay messages (i.e., joins, parts, nicks, quits, modes, etc.)"""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Relay/locales/����������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017504� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Relay/locales/fi.po�����������������������������������������������������0000644�0001750�0001750�00000017236�13634634532�020445� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: Relay plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 11:59+EET\n" "PO-Revision-Date: 2014-12-20 12:17+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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-Generator: Poedit 1.6.10\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: config.py:39 msgid "Would you like to relay between any channels?" msgstr "Haluasitko botin välittävän joidenkin kanavien välillä?" #: config.py:40 msgid "What channels? Separate them by spaces." msgstr "Minkä kanavien? Erota ne välilyönnillä." #: config.py:42 msgid "Would you like to use color to distinguish between nicks?" msgstr "Haluaisitko botin käyttävän värejä erottamaan nimimerkit toisistaan?" #: config.py:59 msgid "" "Determines whether the bot will color relayed\n" " PRIVMSGs so as to make the messages easier to read." msgstr "" "Määrittää värjääkö botti välitetyt\n" " PRIVMSG:eet, jotta viestit olisivat helpommin luettavia." #: config.py:62 msgid "" "Determines whether the bot will synchronize\n" " topics between networks in the channels it relays." msgstr "" "Määrittää synkronoiko botti\n" " aiheet kanavilla, joita se välittää kaikkiin verkkoihin." #: config.py:65 msgid "" "Determines whether the bot will relay the\n" " hostmask of the person joining or parting the channel when they join\n" " or part." msgstr "" "Määrittää välittääkö botti\n" " liittyvän tai poistuvan henkilön hostmaskin, hänen liittyessään/" "poistuessaan." #: config.py:69 msgid "" "Determines whether the bot will include the\n" " network in relayed PRIVMSGs; if you're only relaying between two " "networks,\n" " it's somewhat redundant, and you may wish to save the space." msgstr "" "Määrittää sisällyttääkö botti\n" " verkon välitetyissä PRIVMSG:issä; jos välität vain kahta verkkoa,\n" " se on aika tarpeeton, ja saatat tahtoa säästää tilaa." #: config.py:73 msgid "" "Determines whether the bot will detect other\n" " bots relaying and respond by kickbanning them." msgstr "" "Määrittää havaitseeko botti toiset\n" " välittävät botit ja vastaa potkimalla ne ja antamalla niille " "porttikiellon." #: config.py:76 msgid "" "Determines which channels the bot\n" " will relay in." msgstr "" "Määrittää mitä kanavia botti\n" " välittää." #: config.py:79 msgid "" "Determines whether the bot\n" " will always join the channel(s) it relays for on all networks the bot " "is\n" " connected to." msgstr "" "Määrittää liittyykö botti aina\n" " kanavalle( tai kanaville), joita se välittää, kaikissa verkoissa, " "joihin\n" " botti on yhdistänyt." #: config.py:83 msgid "" "Determines what hostmasks will not be relayed on a\n" " channel." msgstr "" "Määrittää mitä hostmaskeja ei välitetä\n" " kanavalla." #: config.py:86 msgid "" "Determines whether the bot will used NOTICEs\n" " rather than PRIVMSGs for non-PRIVMSG relay messages (i.e., joins, " "parts,\n" " nicks, quits, modes, etc.)" msgstr "" "Määrittää käyttääkö botti huomautuksia (NOTICE)\n" " mielummin kuin PRIVMSG:ssiä muissa kuin PRIVMSG välitysviesteissä (esim. " "liittymiset, poistumiset\n" " nimimerkit, lopetukset, tilat, jne.)" #: plugin.py:46 msgid "This plugin allows you to setup a relay between networks." msgstr "Tämä plugin sallii välityksen asettamisen verkkojen välille." #: plugin.py:100 msgid "" "[<channel>]\n" "\n" " Starts relaying between the channel <channel> on all networks. If " "on a\n" " network the bot isn't in <channel>, it'll join. This commands is\n" " required even if the bot is in the channel on both networks; it " "won't\n" " relay between those channels unless it's told to join both\n" " channels. If <channel> is not given, starts relaying on the " "channel\n" " the message was sent in.\n" " " msgstr "" "[<kanava>]\n" "\n" " Alkaa välittämään <kanavaa> kaikissa verkoissa. Jos botti ei ole\n" " <kanavalla> jossain verkossa, se liittyy. Tämä komento on vaadittu, " "jopa jos\n" " botti on kanavalla molemmissa verkoissa; se ei välitä\n" " noiden kanavien välillä, ennen kuin se on käsketty liittymään " "molemmille\n" " kanaville. Jos <kanava> ei ole annettu, botti alkaa välittämään " "kanavaa, jolla\n" " viesti lähetettiin.\n" " " #: plugin.py:119 msgid "" "<channel>\n" "\n" " Ceases relaying between the channel <channel> on all networks. The " "bot\n" " will part from the channel on all networks in which it is on the\n" " channel.\n" " " msgstr "" "<kanava>\n" "\n" " Pysäyttää <kanavan> välittämisen kaikissa verkoissa. Botti\n" " poistuu kanavalta kaikissa verkoissa, joilla se on\n" " kanavalla.\n" " " #: plugin.py:134 msgid "" "[<channel>]\n" "\n" " Returns the nicks of the people in the channel on the various " "networks\n" " the bot is connected to. <channel> is only necessary if the " "message\n" " isn't sent on the channel itself.\n" " " msgstr "" "[<kanava>]\n" "\n" " Palauttaa kanavalla olevien ihmisten nimimerkit niissä verkoissa,\n" " joihin botti on yhdistänyt. <Kanava> on vaadittu vain. jos viestiä\n" " ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:224 msgid "is an op on %L" msgstr "on operaattori kanavalla %L" #: plugin.py:226 msgid "is a halfop on %L" msgstr "on puolioperaattori kanavalla %L" #: plugin.py:228 msgid "is voiced on %L" msgstr "omaa äänen kanavalla %L" #: plugin.py:231 msgid "is also on %L" msgstr "on myös kanavilla %L" #: plugin.py:233 msgid "is on %L" msgstr "on myös kanavalla %L" #: plugin.py:235 msgid "isn't on any non-secret channels" msgstr "ei ole yhdelläkään ei salaisella kanavalla" #: plugin.py:242 plugin.py:243 plugin.py:247 msgid "<unknown>" msgstr "<tuntematon>" #: plugin.py:249 msgid " %s is away: %s." msgstr " %s on poissa: %s." #: plugin.py:254 msgid " identified" msgstr "tunnistautunut" #: plugin.py:259 msgid "%s (%s) has been%s on server %s since %s (idle for %s) and %s.%s" msgstr "%s (%s) on ollut %s palvelimella %s %s lähtien (idlannut %s) ja %s.%s" #: plugin.py:274 msgid "There is no %s on %s." msgstr "verkossa %s ei ole %s:ää." #: plugin.py:343 msgid "You seem to be relaying, punk." msgstr "Sinä näytät välittävän, punkki." #: plugin.py:396 msgid "%s%s has joined on %s" msgstr "%s%s on liittynyt verkossa %s" #: plugin.py:411 msgid "%s%s has left on %s (%s)" msgstr "%s%s on lähtenyt verkossa %s (%s)" #: plugin.py:414 msgid "%s%s has left on %s" msgstr "%s%s on lähtenyt verkossa %s" #: plugin.py:424 msgid "mode change by %s on %s: %s" msgstr "tilan muutos %s verkossa %s: %s" #: plugin.py:436 msgid "%s was kicked by %s on %s (%s)" msgstr "%s potkittiin, potkija %s verkossa %s (%s)" #: plugin.py:439 msgid "%s was kicked by %s on %s" msgstr "%s potkittiin, potkija %s verkossa %s" #: plugin.py:448 msgid "nick change by %s to %s on %s" msgstr "nimimerkin vaihto %s %s:ksi verkossa %s" #: plugin.py:478 msgid "topic change by %s on %s: %s" msgstr "aiheen vaihto, vaihtanut %s %s: %s:ksi." #: plugin.py:487 msgid "%s has quit %s (%s)" msgstr "%s on lopettanut %s verkossa (%s)" #: plugin.py:489 msgid "%s has quit %s." msgstr "%on lopettanut %s." #: plugin.py:499 msgid "disconnected from %s: %s" msgstr "yhteys katkaistu verkosta %s: %s" ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Relay/locales/fr.po�����������������������������������������������������0000644�0001750�0001750�00000015652�13634634532�020456� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-05-11 16:57+EEST\n" "PO-Revision-Date: \n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: Limnoria <progval@gmail.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" #: config.py:39 msgid "Would you like to relay between any channels?" msgstr "Voulez-vous relayer entre des canaux ?" #: config.py:40 msgid "What channels? Separate them by spaces." msgstr "Quels canaux ? Séparez-les par des espaces." #: config.py:42 msgid "Would you like to use color to distinguish between nicks?" msgstr "Voulez-vous utiliser de la couleur pour distinguer les nicks ?" #: config.py:59 msgid "" "Determines whether the bot will color relayed\n" " PRIVMSGs so as to make the messages easier to read." msgstr "" "Détermine si le bot colorera les PRIVMSGs relayez, pour rendre les messages " "plus faciles à lire." #: config.py:62 msgid "" "Determines whether the bot will synchronize\n" " topics between networks in the channels it relays." msgstr "" "Détermine si le bot synchronisera les topics entre les réseaux sur les " "canaux qu'il relaye." #: config.py:65 #, fuzzy msgid "" "Determines whether the bot will relay the\n" " hostmask of the person joining or parting the channel when they join\n" " or part." msgstr "" "Détermine si le bot relayera le masque d'hôte d'une personne joignant ou " "partant d'un canal." #: config.py:69 msgid "" "Determines whether the bot will include the\n" " network in relayed PRIVMSGs; if you're only relaying between two " "networks,\n" " it's somewhat redundant, and you may wish to save the space." msgstr "" "Détermine si le bot inclurera le réseau dans les PRIVMSG relayés ; si vous " "ne relayez qu'entre deux réseaux, ce sera quelque chose de redondant que " "vous pouvez supprimer pour gagner de la place." #: config.py:73 msgid "" "Determines whether the bot will detect other\n" " bots relaying and respond by kickbanning them." msgstr "" "Détermine si le bot détectera d'autres bots relayant et y répondra en les " "kickbannissant." #: config.py:76 msgid "" "Determines which channels the bot\n" " will relay in." msgstr "Détermine sur quels canaux le bot relayera." #: config.py:79 msgid "" "Determines whether the bot\n" " will always join the channel(s) it relays for on all networks the bot " "is\n" " connected to." msgstr "" "Détermine si le bot rejoindra toujours le(s) canal(aux) qu'il relaye sur " "tous les réseaux auxquels il est connecté." #: config.py:83 msgid "" "Determines what hostmasks will not be relayed on a\n" " channel." msgstr "Détermine quels masques d'hôte ne seront pas relayés sur un canal." #: config.py:86 msgid "" "Determines whether the bot will used NOTICEs\n" " rather than PRIVMSGs for non-PRIVMSG relay messages (i.e., joins, " "parts,\n" " nicks, quits, modes, etc.)" msgstr "" "Détermine si le bot utilisera des NOTICEs plutôt que des PRIVMSG pour les " "messages relayés qui ne concernent pas un PRIVMSG (arrivée, départ, " "changement de nick, quits, modes, etc.)" #: plugin.py:99 msgid "" "[<channel>]\n" "\n" " Starts relaying between the channel <channel> on all networks. If " "on a\n" " network the bot isn't in <channel>, it'll join. This commands is\n" " required even if the bot is in the channel on both networks; it " "won't\n" " relay between those channels unless it's told to join both\n" " channels. If <channel> is not given, starts relaying on the " "channel\n" " the message was sent in.\n" " " msgstr "" "[<canal>]\n" "\n" "Commence à relayer le canal <canal> sur tous les réseaux. Si il y a un " "réseau sur lequel le bot n'est pas sur <canal>, il y entrera. Cette commande " "est requise même si le bot est sur le canal sur tous les réseaux ; il ne " "relayera pas tant qu'on ne lui a pas dit de rejoindre les canaux sur tous " "les réseaux. Si <canal> n'est pas donné, il commencera à relayer sur le " "canal où a été envoyé le message." #: plugin.py:118 msgid "" "<channel>\n" "\n" " Ceases relaying between the channel <channel> on all networks. The " "bot\n" " will part from the channel on all networks in which it is on the\n" " channel.\n" " " msgstr "" "<canal>\n" "\n" "Cesse de relayer entre les canaux <canal> sur tous les réseaux. Le bot " "partira de ces canaux sur tous les réseaux si il y est." #: plugin.py:133 msgid "" "[<channel>]\n" "\n" " Returns the nicks of the people in the channel on the various " "networks\n" " the bot is connected to. <channel> is only necessary if the " "message\n" " isn't sent on the channel itself.\n" " " msgstr "" "[<canal>]\n" "\n" "Retourne les nicks des personnes sur le canal sur les différents réseaux sur " "lesquels est connecté le bot. <canal> n'est nécessaire que si l'on n'est pas " "sur le canal lui-même." #: plugin.py:223 msgid "is an op on %L" msgstr "est op sur %L" #: plugin.py:225 msgid "is a halfop on %L" msgstr "est halfop sur %L" #: plugin.py:227 msgid "is voiced on %L" msgstr "est voice sur %L" #: plugin.py:230 msgid "is also on %L" msgstr "est aussi sur %L" #: plugin.py:232 msgid "is on %L" msgstr "est sur %L" #: plugin.py:234 msgid "isn't on any non-secret channels" msgstr "n'est sur aucun canal non-secret" #: plugin.py:241 plugin.py:242 plugin.py:246 msgid "<unknown>" msgstr "<inconnu>" #: plugin.py:248 msgid " %s is away: %s." msgstr "%s est away : %s" #: plugin.py:253 msgid " identified" msgstr "identifié" #: plugin.py:258 msgid "%s (%s) has been%s on server %s since %s (idle for %s) and %s.%s" msgstr "" "%s (%s) a été vu%s sur le serveur %s depuis %s (idle depuis %s) et %s.%s" #: plugin.py:273 msgid "There is no %s on %s." msgstr "Il n'y a pas de %s sur %s." #: plugin.py:342 msgid "You seem to be relaying, punk." msgstr "Tu sembles relayer, enfoiré" #: plugin.py:395 msgid "%s%s has joined on %s" msgstr "%s%s est arrivé sur %s" #: plugin.py:410 msgid "%s%s has left on %s (%s)" msgstr "%s%s est parti de %s (%s)" #: plugin.py:413 msgid "%s%s has left on %s" msgstr "%s%s est parti de %s" #: plugin.py:423 msgid "mode change by %s on %s: %s" msgstr "changement de mode par %s sur %s : %s" #: plugin.py:435 msgid "%s was kicked by %s on %s (%s)" msgstr "%s a été kické par %s sur %s (%s)" #: plugin.py:438 msgid "%s was kicked by %s on %s" msgstr "%s a été kické par %s sur %s" #: plugin.py:447 msgid "nick change by %s to %s on %s" msgstr "changement de nick : %s -> %s sur %s" #: plugin.py:477 msgid "topic change by %s on %s: %s" msgstr "changement de topic par %s sur %s : %s" #: plugin.py:486 msgid "%s has quit %s (%s)" msgstr "%s a quitté %s (%s)" #: plugin.py:488 msgid "%s has quit %s." msgstr "%s a quitté %s." #: plugin.py:498 msgid "disconnected from %s: %s" msgstr "déconnecté de %s : %s" ��������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Relay/locales/it.po�����������������������������������������������������0000644�0001750�0001750�00000015607�13634634532�020463� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-05-11 16:57+EEST\n" "PO-Revision-Date: 2014-05-11 17:50+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 1.5.4\n" #: config.py:39 msgid "Would you like to relay between any channels?" msgstr "Vuoi trasmettere messaggi tra canali diversi?" #: config.py:40 msgid "What channels? Separate them by spaces." msgstr "Quali canali? Separali con spazi." #: config.py:42 msgid "Would you like to use color to distinguish between nicks?" msgstr "Vuoi usare colori per distinguere i nick?" #: config.py:59 msgid "" "Determines whether the bot will color relayed\n" " PRIVMSGs so as to make the messages easier to read." msgstr "" "Determina se il bot colorerà i PRIVMSG inoltrati per renderne più semplice " "la lettura." #: config.py:62 msgid "" "Determines whether the bot will synchronize\n" " topics between networks in the channels it relays." msgstr "" "Determina se il bot sincronizzerà i topic tra le reti nei canali in cui " "inoltra." #: config.py:65 #, fuzzy msgid "" "Determines whether the bot will relay the\n" " hostmask of the person joining or parting the channel when they join\n" " or part." msgstr "" "Determina se il bot inoltrerà l'hostmask dell'utente che entra o esce dal " "canale." #: config.py:69 msgid "" "Determines whether the bot will include the\n" " network in relayed PRIVMSGs; if you're only relaying between two " "networks,\n" " it's somewhat redundant, and you may wish to save the space." msgstr "" "Determina se il bot includerà la rete nei PRIVMSG inoltrati; se si " "trasmette\n" " tra due sole reti è alquanto ridondante e si preferirà risparmiare " "spazio." #: config.py:73 msgid "" "Determines whether the bot will detect other\n" " bots relaying and respond by kickbanning them." msgstr "" "Determina se il bot rileverà altri bot che stanno inoltrando rispondendo con " "un kickban." #: config.py:76 msgid "" "Determines which channels the bot\n" " will relay in." msgstr "Determina in quale canale il bot inoltrerà i messaggi." #: config.py:79 msgid "" "Determines whether the bot\n" " will always join the channel(s) it relays for on all networks the bot " "is\n" " connected to." msgstr "" "Determina se il bot entrerà sempre nei canali in cui trasmette per tutte le " "reti a cui è connesso." #: config.py:83 msgid "" "Determines what hostmasks will not be relayed on a\n" " channel." msgstr "Determina quale hostmask non verrà inoltrata su un canale." #: config.py:86 msgid "" "Determines whether the bot will used NOTICEs\n" " rather than PRIVMSGs for non-PRIVMSG relay messages (i.e., joins, " "parts,\n" " nicks, quits, modes, etc.)" msgstr "" "Determina se il bot userà i NOTICE piuttosto che i PRIVMSG per messaggi che " "non\n" " riguardano le comunicazioni tra utenti (join, part, nick, quit, mode, " "ecc.)" #: plugin.py:99 msgid "" "[<channel>]\n" "\n" " Starts relaying between the channel <channel> on all networks. If " "on a\n" " network the bot isn't in <channel>, it'll join. This commands is\n" " required even if the bot is in the channel on both networks; it " "won't\n" " relay between those channels unless it's told to join both\n" " channels. If <channel> is not given, starts relaying on the " "channel\n" " the message was sent in.\n" " " msgstr "" "[<canale>]\n" "\n" " Inizia l'inoltro dei messaggi di <canale> su tutte le reti. Se su " "una rete\n" " il bot non è in <canale>, ci entrerà. Questo comando è necessario " "anche se\n" " il bot è presente nel canale di entrambe le reti; non trasmetterà i " "messaggi\n" " a meno che gli sia stato detto di entrare su entrambi i canali. Se " "<canale>\n" " non è specificato, inizia l'inoltro sul canale in cui è stato " "inviato il messaggio.\n" " " #: plugin.py:118 msgid "" "<channel>\n" "\n" " Ceases relaying between the channel <channel> on all networks. The " "bot\n" " will part from the channel on all networks in which it is on the\n" " channel.\n" " " msgstr "" "<canale>\n" "\n" " Interrompe l'inoltro dei messaggi di <canale> su tutte le reti.\n" " Il bot uscirà dal canale su tutte le reti a cui è connesso.\n" " " #: plugin.py:133 msgid "" "[<channel>]\n" "\n" " Returns the nicks of the people in the channel on the various " "networks\n" " the bot is connected to. <channel> is only necessary if the " "message\n" " isn't sent on the channel itself.\n" " " msgstr "" "[<canale>]\n" "\n" " Riporta i nick degli utenti presenti nel canale sulle diverse reti " "alle quali è connesso\n" " il bot. <canale> è necessario solo se il messaggio non viene inviato " "nel canale stesso.\n" " " #: plugin.py:223 msgid "is an op on %L" msgstr "è un op su %L" #: plugin.py:225 msgid "is a halfop on %L" msgstr "è un halfop su %L" #: plugin.py:227 msgid "is voiced on %L" msgstr "ha il voice su %L" #: plugin.py:230 msgid "is also on %L" msgstr "è anche su %L" #: plugin.py:232 msgid "is on %L" msgstr "è su %L" #: plugin.py:234 msgid "isn't on any non-secret channels" msgstr "non è in alcun canale non segreto" #: plugin.py:241 plugin.py:242 plugin.py:246 msgid "<unknown>" msgstr "<sconosciuto>" #: plugin.py:248 msgid " %s is away: %s." msgstr " %s è away: %s." #: plugin.py:253 msgid " identified" msgstr " identificato" #: plugin.py:258 msgid "%s (%s) has been%s on server %s since %s (idle for %s) and %s.%s" msgstr "%s (%s) era%s sul server %s dalle %s (inattivo da %s) e %s.%s" #: plugin.py:273 msgid "There is no %s on %s." msgstr "Non c'è nessun %s su %s." #: plugin.py:342 msgid "You seem to be relaying, punk." msgstr "Sembra che tu stia inoltrando, giovane padawan." #: plugin.py:395 msgid "%s%s has joined on %s" msgstr "%s%s è entrato in %s" #: plugin.py:410 msgid "%s%s has left on %s (%s)" msgstr "%s%s ha lasciato %s (%s)" #: plugin.py:413 msgid "%s%s has left on %s" msgstr "%s%s ha lasciato %s" #: plugin.py:423 msgid "mode change by %s on %s: %s" msgstr "mode cambiato da %s su %s: %s" #: plugin.py:435 msgid "%s was kicked by %s on %s (%s)" msgstr "%s è stato cacciato da %s su %s (%s)" #: plugin.py:438 msgid "%s was kicked by %s on %s" msgstr "%s è stato cacciato da %s su %s" #: plugin.py:447 msgid "nick change by %s to %s on %s" msgstr "nick cambiato da %s a %s su %s" #: plugin.py:477 msgid "topic change by %s on %s: %s" msgstr "topic cambiato da %s su %s: %s" #: plugin.py:486 msgid "%s has quit %s (%s)" msgstr "%s è uscito da %s (%s)" #: plugin.py:488 msgid "%s has quit %s." msgstr "%s è uscito da %s." #: plugin.py:498 msgid "disconnected from %s: %s" msgstr "disconnesso da %s: %s" �������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Relay/plugin.py���������������������������������������������������������0000644�0001750�0001750�00000043142�13634634532�017730� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2010,2015 James McCoy # 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 time import supybot.conf as conf import supybot.utils as utils import supybot.world as world from supybot.commands import * import supybot.irclib as irclib import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.utils.structures import MultiSet, TimeoutQueue from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Relay') class Relay(callbacks.Plugin): """This plugin allows you to setup a relay between networks.""" noIgnore = True def __init__(self, irc): self.__parent = super(Relay, self) self.__parent.__init__(irc) self._whois = {} self.queuedTopics = MultiSet() self.lastRelayMsgs = ircutils.IrcDict() def do376(self, irc, msg): networkGroup = conf.supybot.networks.get(irc.network) for channel in self.registryValue('channels'): if self.registryValue('channels.joinOnAllNetworks', channel): if channel not in irc.state.channels: irc.queueMsg(networkGroup.channels.join(channel)) do377 = do422 = do376 def _getRealIrc(self, irc): if isinstance(irc, irclib.Irc): return irc else: return irc.getRealIrc() def _getIrcName(self, irc): # We should allow abbreviations at some point. return irc.network @internationalizeDocstring def join(self, irc, msg, args, channel): """[<channel>] Starts relaying between the channel <channel> on all networks. If on a network the bot isn't in <channel>, it'll join. This commands is required even if the bot is in the channel on both networks; it won't relay between those channels unless it's told to join both channels. If <channel> is not given, starts relaying on the channel the message was sent in. """ self.registryValue('channels').add(channel) for otherIrc in world.ircs: if channel not in otherIrc.state.channels: networkGroup = conf.supybot.networks.get(otherIrc.network) otherIrc.queueMsg(networkGroup.channels.join(channel)) irc.replySuccess() join = wrap(join, ['channel', 'admin']) @internationalizeDocstring def part(self, irc, msg, args, channel): """<channel> Ceases relaying between the channel <channel> on all networks. The bot will part from the channel on all networks in which it is on the channel. """ self.registryValue('channels').discard(channel) for otherIrc in world.ircs: if channel in otherIrc.state.channels: otherIrc.queueMsg(ircmsgs.part(channel)) irc.replySuccess() part = wrap(part, ['channel', 'admin']) @internationalizeDocstring def nicks(self, irc, msg, args, channel): """[<channel>] Returns the nicks of the people in the channel on the various networks the bot is connected to. <channel> is only necessary if the message isn't sent on the channel itself. """ realIrc = self._getRealIrc(irc) if channel not in self.registryValue('channels'): irc.error(format('I\'m not relaying in %s.', channel)) return users = [] for otherIrc in world.ircs: network = self._getIrcName(otherIrc) ops = [] halfops = [] voices = [] usersS = [] if network != self._getIrcName(realIrc): try: Channel = otherIrc.state.channels[channel] except KeyError: users.append(format('(not in %s on %s)',channel,network)) continue numUsers = 0 for s in Channel.users: s = s.strip() if not s: continue numUsers += 1 if s in Channel.ops: ops.append('@' + s) elif s in Channel.halfops: halfops.append('%' + s) elif s in Channel.voices: voices.append('+' + s) else: usersS.append(s) utils.sortBy(ircutils.toLower, ops) utils.sortBy(ircutils.toLower, voices) utils.sortBy(ircutils.toLower, halfops) utils.sortBy(ircutils.toLower, usersS) usersS = ', '.join(filter(None, list(map(', '.join, (ops,halfops,voices,usersS))))) users.append(format('%s (%i): %s', ircutils.bold(network), numUsers, usersS)) users.sort() irc.reply('; '.join(users)) nicks = wrap(nicks, ['channel']) def do311(self, irc, msg): irc = self._getRealIrc(irc) nick = ircutils.toLower(msg.args[1]) if (irc, nick) not in self._whois: return else: self._whois[(irc, nick)][-1][msg.command] = msg # These are all sent by a WHOIS response. do301 = do311 do312 = do311 do317 = do311 do319 = do311 do320 = do311 def do318(self, irc, msg): irc = self._getRealIrc(irc) nick = msg.args[1] loweredNick = ircutils.toLower(nick) if (irc, loweredNick) not in self._whois: return (replyIrc, replyMsg, d) = self._whois[(irc, loweredNick)] d['318'] = msg s = ircutils.formatWhois(irc, d, caller=replyMsg.nick, channel=replyMsg.args[0]) replyIrc.reply(s) del self._whois[(irc, loweredNick)] def do402(self, irc, msg): irc = self._getRealIrc(irc) nick = msg.args[1] loweredNick = ircutils.toLower(nick) if (irc, loweredNick) not in self._whois: return (replyIrc, replyMsg, d) = self._whois[(irc, loweredNick)] del self._whois[(irc, loweredNick)] s = format(_('There is no %s on %s.'), nick, self._getIrcName(irc)) replyIrc.reply(s) do401 = do402 def _formatPrivmsg(self, nick, network, msg): channel = msg.channel if self.registryValue('includeNetwork', channel): network = '@' + network else: network = '' # colorize nicks color = self.registryValue('color', channel) # Also used further down. if color: nick = ircutils.IrcString(nick) newnick = ircutils.mircColor(nick, *ircutils.canonicalColor(nick)) colors = ircutils.canonicalColor(nick, shift=4) nick = newnick if ircmsgs.isAction(msg): if color: t = ircutils.mircColor('*', *colors) else: t = '*' s = format('%s %s%s %s', t, nick, network, ircmsgs.unAction(msg)) else: if color: lt = ircutils.mircColor('<', *colors) gt = ircutils.mircColor('>', *colors) else: lt = '<' gt = '>' s = format('%s%s%s%s %s', lt, nick, network, gt, msg.args[1]) return s def _sendToOthers(self, irc, msg): assert msg.command in ('PRIVMSG', 'NOTICE', 'TOPIC') for otherIrc in world.ircs: if otherIrc != irc and not otherIrc.zombie: if msg.channel in otherIrc.state.channels: msg.tag('relayedMsg') otherIrc.queueMsg(msg) def _checkRelayMsg(self, msg): channel = msg.channel if channel in self.lastRelayMsgs: q = self.lastRelayMsgs[channel] unformatted = ircutils.stripFormatting(msg.args[1]) normalized = utils.str.normalizeWhitespace(unformatted) for s in q: if s in normalized: return True return False def _punishRelayers(self, msg): assert self._checkRelayMsg(msg), 'Punishing without checking.' who = msg.prefix channel = msg.channel def notPunishing(irc, s, *args): self.log.info('Not punishing %s in %s on %s: %s.', msg.prefix, channel, irc.network, s, *args) for irc in world.ircs: if channel in irc.state.channels: if irc.nick in irc.state.channels[channel].ops: if who in irc.state.channels[channel].bans: notPunishing(irc, 'already banned') else: self.log.info('Punishing %s in %s on %s for relaying.', who, channel, irc.network) irc.sendMsg(ircmsgs.ban(channel, who)) kmsg = _('You seem to be relaying, punk.') irc.sendMsg(ircmsgs.kick(channel, msg.nick, kmsg)) else: notPunishing(irc, 'not opped') def doPrivmsg(self, irc, msg): if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg): return text = msg.args[1] if msg.channel: irc = self._getRealIrc(irc) if msg.channel not in self.registryValue('channels'): return ignores = self.registryValue('ignores', msg.channel, irc.network) for ignore in ignores: if ircutils.hostmaskPatternEqual(ignore, msg.prefix): self.log.debug('Refusing to relay %s, ignored by %s.', msg.prefix, ignore) return # Let's try to detect other relay bots. if self._checkRelayMsg(msg): if self.registryValue('punishOtherRelayBots', msg.channel, irc.network): self._punishRelayers(msg) # Either way, we don't relay the message. else: self.log.warning('Refusing to relay message from %s, ' 'it appears to be a relay message.', msg.prefix) else: network = self._getIrcName(irc) s = self._formatPrivmsg(msg.nick, network, msg) m = self._msgmaker(msg.channel, network, s) self._sendToOthers(irc, m) def _msgmaker(self, target, network, s): msg = dynamic.msg if self.registryValue('noticeNonPrivmsgs', target) and \ msg.command != 'PRIVMSG': return ircmsgs.notice(target, s) else: return ircmsgs.privmsg(target, s) def doJoin(self, irc, msg): irc = self._getRealIrc(irc) channel = msg.args[0] if channel not in self.registryValue('channels'): return network = self._getIrcName(irc) if self.registryValue('hostmasks', channel): hostmask = format(' (%s)', msg.prefix.split('!')[1]) else: hostmask = '' s = format(_('%s%s has joined on %s'), msg.nick, hostmask, network) m = self._msgmaker(channel, s) self._sendToOthers(irc, m) def doPart(self, irc, msg): irc = self._getRealIrc(irc) channel = msg.args[0] if channel not in self.registryValue('channels'): return network = self._getIrcName(irc) if self.registryValue('hostmasks', channel): hostmask = format(' (%s)', msg.prefix.split('!')[1]) else: hostmask = '' if len(msg.args) > 1: s = format(_('%s%s has left on %s (%s)'), msg.nick, hostmask, network, msg.args[1]) else: s = format(_('%s%s has left on %s'), msg.nick, hostmask, network) m = self._msgmaker(channel, s) self._sendToOthers(irc, m) def doMode(self, irc, msg): irc = self._getRealIrc(irc) channel = msg.args[0] if channel not in self.registryValue('channels'): return network = self._getIrcName(irc) s = format(_('mode change by %s on %s: %s'), msg.nick, network, ' '.join(msg.args[1:])) m = self._msgmaker(channel, s) self._sendToOthers(irc, m) def doKick(self, irc, msg): irc = self._getRealIrc(irc) channel = msg.args[0] if channel not in self.registryValue('channels'): return network = self._getIrcName(irc) if len(msg.args) == 3: s = format(_('%s was kicked by %s on %s (%s)'), msg.args[1], msg.nick, network, msg.args[2]) else: s = format(_('%s was kicked by %s on %s'), msg.args[1], msg.nick, network) m = self._msgmaker(channel, s) self._sendToOthers(irc, m) def doNick(self, irc, msg): irc = self._getRealIrc(irc) newNick = msg.args[0] network = self._getIrcName(irc) s = format(_('nick change by %s to %s on %s'), msg.nick,newNick,network) for channel in self.registryValue('channels'): m = self._msgmaker(channel, s) self._sendToOthers(irc, m) def doTopic(self, irc, msg): irc = self._getRealIrc(irc) (channel, newTopic) = msg.args if channel not in self.registryValue('channels'): return network = self._getIrcName(irc) if self.registryValue('topicSync', channel): m = ircmsgs.topic(channel, newTopic) for otherIrc in world.ircs: if irc != otherIrc: try: if otherIrc.state.getTopic(channel) != newTopic: if (otherIrc, newTopic) not in self.queuedTopics: self.queuedTopics.add((otherIrc, newTopic)) otherIrc.queueMsg(m) else: self.queuedTopics.remove((otherIrc, newTopic)) except KeyError: self.log.warning('Not on %s on %s, ' 'can\'t sync topics.', channel, otherIrc.network) else: s = format(_('topic change by %s on %s: %s'), msg.nick, network, newTopic) m = self._msgmaker(channel, s) self._sendToOthers(irc, m) def doQuit(self, irc, msg): irc = self._getRealIrc(irc) network = self._getIrcName(irc) if msg.args: s = format(_('%s has quit %s (%s)'), msg.nick, network, msg.args[0]) else: s = format(_('%s has quit %s.'), msg.nick, network) for channel in self.registryValue('channels'): m = self._msgmaker(channel, s) self._sendToOthers(irc, m) def doError(self, irc, msg): irc = self._getRealIrc(irc) network = self._getIrcName(irc) s = format(_('disconnected from %s: %s'), network, msg.args[0]) for channel in self.registryValue('channels'): m = self._msgmaker(channel, s) self._sendToOthers(irc, m) def outFilter(self, irc, msg): irc = self._getRealIrc(irc) if msg.command == 'PRIVMSG': if msg.relayedMsg: self._addRelayMsg(msg) else: if msg.channel in self.registryValue('channels'): network = self._getIrcName(irc) s = self._formatPrivmsg(irc.nick, network, msg) relayMsg = self._msgmaker(msg.args[0], s) self._sendToOthers(irc, relayMsg) return msg def _addRelayMsg(self, msg): channel = msg.channel if channel in self.lastRelayMsgs: q = self.lastRelayMsgs[channel] else: q = TimeoutQueue(60) # XXX Make this configurable. self.lastRelayMsgs[channel] = q unformatted = ircutils.stripFormatting(msg.args[1]) normalized = utils.str.normalizeWhitespace(unformatted) q.enqueue(normalized) Class = Relay # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Relay/test.py�����������������������������������������������������������0000644�0001750�0001750�00000003317�13634634532�017411� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * class RelayTestCase(PluginTestCase): plugins = ('Relay',) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Reply/������������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016101� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Reply/__init__.py�������������������������������������������������������0000644�0001750�0001750�00000004740�13634634532�020211� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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. ### """ This plugin contains various commands which elicit certain types of responses from the bot. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.strike __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������limnoria-2020.03.17/plugins/Reply/config.py���������������������������������������������������������0000644�0001750�0001750�00000004653�13634634532�017722� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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 from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Reply') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Reply', True) Reply = conf.registerPlugin('Reply') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Reply, 'someConfigVariableName', # registry.Boolean(False, _("""Help for someConfigVariableName."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Reply/locales/����������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017523� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Reply/locales/de.po�����������������������������������������������������0000644�0001750�0001750�00000004431�13634634532�020447� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2011-08-10 11:27+CEST\n" "PO-Revision-Date: 2011-10-29 10:22+0100\n" "Last-Translator: Florian Besser <fbesser@gmail.com>\n" "Language-Team: German <fbesser@gmail.com>\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" #: plugin.py:37 msgid "" "This plugin 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" " can be easily abused.\n" " " msgstr "Dieses Plugin enthält einige befehl um verschiedene Antworten zu konstruieren. Einige Bot Besitzer sollten dieses Plugin nicht laden, weil es einfach missbraucht werden kann." #: plugin.py:43 msgid "" "<text>\n" "\n" " Replies with <text> in private. Use nested commands to your benefit\n" " here.\n" " " msgstr "" "<Text>\n" "\n" "Antwortet mit <Text> privat. Du kannst hier nutzen aus verschachtelte Befehle ziehen." #: plugin.py:53 msgid "" "<text>\n" "\n" " Replies with <text> as an action. Use nested commands to your benefit\n" " here.\n" " " msgstr "" "<Text>\n" "\n" "Antwortet mit <Text> als Aktion. Du kannst hier nutzen aus verschachtelte Befehle ziehen." #: plugin.py:66 msgid "" "<text>\n" "\n" " Replies with <text> in a notice. Use nested commands to your benefit\n" " here. If you want a private notice, nest the private command.\n" " " msgstr "" "<Text>\n" "\n" "Antwortet mit <Text> als Notiz.Du kannst hier nutzen aus verschachtelte Befehle ziehen. Falls du eine private Notiz nutzen willst niste den privaten Befehl ein." #: plugin.py:76 msgid "" "<text>\n" "\n" " Replies with <text>. Equivalent to the alias, 'echo $nick: $1'.\n" " " msgstr "" "<Text>\n" "\n" "Antworte mit <Text>. Gleich wie der Alias 'echo $nick: $1'." #: plugin.py:85 msgid "" "<str> [<str> ...]\n" "\n" " Replies with each of its arguments <str> in separate replies, depending\n" " the configuration of supybot.reply.oneToOne.\n" " " msgstr "" "<Zeichenkette> [<Zeichenkette> ...]\n" "\n" "Antwortet mit jedem <Zeichenketten> Argument in separaten Antworten, hängt von der Konfiguration von supybot.reply.oneToOne ab." ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Reply/locales/fi.po�����������������������������������������������������0000644�0001750�0001750�00000005044�13634634532�020456� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: \n" "POT-Creation-Date: 2011-08-10 11:27+CEST\n" "PO-Revision-Date: 2011-08-10 13:06+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: \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" #: plugin.py:37 msgid "" "This plugin 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" " can be easily abused.\n" " " msgstr "" "Tämä lisäosa sisältää muutamia erilaisia vastauksia.\n" " Joidenkin botin omistajien ei olisi viisasta ladata tätä lisäosaa, korka\n" " sitä voidaan väärinkäyttää hyvin helposti.\n" " " #: plugin.py:43 msgid "" "<text>\n" "\n" " Replies with <text> in private. Use nested commands to your benefit\n" " here.\n" " " msgstr "" "<teksti>\n" "\n" " Vastaa <tekstillä> yksityisviestissä. Käytä sisäkkäisiä komentoja eduksesi\n" " tässä.\n" " " #: plugin.py:53 msgid "" "<text>\n" "\n" " Replies with <text> as an action. Use nested commands to your benefit\n" " here.\n" " " msgstr "" "<teksti>\n" "\n" " Vastaa <tekstillä> toimintoja. Käytä sisäkkäisiä komentoja eduksesi\n" " tässä.\n" " " #: plugin.py:66 msgid "" "<text>\n" "\n" " Replies with <text> in a notice. Use nested commands to your benefit\n" " here. If you want a private notice, nest the private command.\n" " " msgstr "" "<teksti>\n" "\n" " Vastaa <tekstinä> huomautuksena. Käytä sisäkkäisiä komentoja eduksesi\n" " tässä. Jos haluat yksityisen huomautuksen, sisällytä komento \"private\".\n" " " #: plugin.py:76 msgid "" "<text>\n" "\n" " Replies with <text>. Equivalent to the alias, 'echo $nick: $1'.\n" " " msgstr "" "<teksti>\n" "\n" " Vastaa <tekstillä>. Vastaava aliakseen, 'echo $nick: $1'.\n" " " #: plugin.py:85 msgid "" "<str> [<str> ...]\n" "\n" " Replies with each of its arguments <str> in separate replies, depending\n" " the configuration of supybot.reply.oneToOne.\n" " " msgstr "" "<str> [<str> ...]\n" "\n" " Vastaa jokaisen parametrin <str> eri vastauksessa, riippuen\n" " asetuksesta supybot.reply.oneToOne.\n" " " ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Reply/locales/fr.po�����������������������������������������������������0000644�0001750�0001750�00000004373�13634634532�020473� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-08-10 11:27+CEST\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\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:37 msgid "" "This plugin 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" " can be easily abused.\n" " " 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:43 msgid "" "<text>\n" "\n" " Replies with <text> in private. Use nested commands to your benefit\n" " here.\n" " " msgstr "" "<texte>\n" "\n" "Répond avec le <texte> en privé. Utile pour les commandes imbriquées." #: plugin.py:53 #, fuzzy msgid "" "<text>\n" "\n" " Replies with <text> as an action. Use nested commands to your benefit\n" " here.\n" " " msgstr "" "<texte>\n" "\n" "Répond avec le <texte> comme une action. Utile pour les commandes imbriquées." #: plugin.py:66 msgid "" "<text>\n" "\n" " Replies with <text> in a notice. Use nested commands to your benefit\n" " here. If you want a private notice, nest the private command.\n" " " msgstr "" "<texte>\n" "\n" "Répond avec le <texte> en notice. Utile pour les commandes imbriquées." #: plugin.py:76 msgid "" "<text>\n" "\n" " Replies with <text>. Equivalent to the alias, 'echo $nick: $1'.\n" " " msgstr "" "<texte>\n" "\n" "Répond avec le <texte>. Équivalent à l'alias 'echo $nick: $i'." #: plugin.py:85 msgid "" "<str> [<str> ...]\n" "\n" " Replies with each of its arguments <str> in separate replies, depending\n" " the configuration of supybot.reply.oneToOne.\n" " " msgstr "" "<argument> [<argument> ...]\n" "\n" "Répond avec chacun des <argument>s dans des réponses séparées, en fonction de la configutation de supybot.reply.oneToOne." ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Reply/locales/hu.po�����������������������������������������������������0000644�0001750�0001750�00000005074�13634634532�020477� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # msgid "" msgstr "" "Project-Id-Version: Limnoria Reply\n" "POT-Creation-Date: 2011-08-10 11:27+CEST\n" "PO-Revision-Date: 2011-08-15 14:49+0200\n" "Last-Translator: nyuszika7h <litemininyuszika@gmail.com>\n" "Language-Team: \n" "Language: \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" #: plugin.py:37 msgid "" "This plugin 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" " can be easily abused.\n" " " msgstr "Ez a bővítmény néhány parancsot tartalmaz, amelyek különféle típusú válaszokat gyárt. Néhány bot tulajdonos bölcs lenne nem betölteni ezt a bővítményt, mert könnyen vissza lehet élni vele." #: plugin.py:43 msgid "" "<text>\n" "\n" " Replies with <text> in private. Use nested commands to your benefit\n" " here.\n" " " msgstr "" "<szöveg>\n" "\n" "Privát üzenetben válaszol <szöveg>-gel. Itt az előnyödre használhatpd a beágyazott parancsokat." #: plugin.py:53 msgid "" "<text>\n" "\n" " Replies with <text> as an action. Use nested commands to your benefit\n" " here.\n" " " msgstr "Ez a bővítmény néhány parancsot tartalmaz, amelyek különféle típusú válaszokat gyárt. Néhány bot tulajdonos bölcs lenne nem betölteni ezt a bővítményt, mert könnyen vissza lehet élni vele." #: plugin.py:66 msgid "" "<text>\n" "\n" " Replies with <text> in a notice. Use nested commands to your benefit\n" " here. If you want a private notice, nest the private command.\n" " " msgstr "" "<szöveg>\n" "\n" "Egy közleményben válaszol <szöveg>-gel. Itt az előnyödre használhatod a beágyazott parancsokat. Ha egy privát közleményt szeretnál, ágyazd be a private parancsot." #: plugin.py:76 msgid "" "<text>\n" "\n" " Replies with <text>. Equivalent to the alias, 'echo $nick: $1'.\n" " " msgstr "" "<szöveg>\n" "\n" "Válaszol <szöveg>-gel. Ugyanaz, mint az álnév, 'echo $nick: $1'." #: plugin.py:85 msgid "" "<str> [<str> ...]\n" "\n" " Replies with each of its arguments <str> in separate replies, depending\n" " the configuration of supybot.reply.oneToOne.\n" " " msgstr "" "<karakterlánc> [<karakterlánc> ...]\n" "\n" "Az összes <karakterlánc> paraméterével külön üzenetben válaszol, a supybot.reply.oneToOne konfigurációjától függően." ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Reply/locales/it.po�����������������������������������������������������0000644�0001750�0001750�00000004670�13634634532�020500� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-15 12:49+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugin.py:37 #, docstring msgid "" "This plugin 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" " can be easily abused.\n" " " msgstr "" "Questo plugin contiene alcuni comandi per creare vari tipi di risposte.\n" " Alcuni proprietari avranno l'accortezza di non caricarlo in quanto\n" " se ne può abusare facilmente.\n" " " #: plugin.py:43 #, docstring msgid "" "<text>\n" "\n" " Replies with <text> in private. Use nested commands to your benefit\n" " here.\n" " " msgstr "" "<testo>\n" "\n" " Risponde con <testo> in privato. Utilizza i comandi nidificati a tuo vantaggio." " " #: plugin.py:53 #, docstring msgid "" "<text>\n" "\n" " Replies with <text> as an action. Use nested commands to your benefit\n" " here.\n" " " msgstr "" "<testo>\n" "\n" " Risponde con <testo> come azione. Utilizza i comandi nidificati a tuo vantaggio.\n" " " #: plugin.py:66 #, docstring msgid "" "<text>\n" "\n" " Replies with <text> in a notice. Use nested commands to your benefit\n" " here. If you want a private notice, nest the private command.\n" " " msgstr "" "<testo>\n" "\n" " Risponde con <testo> in un notice. Utilizza i comandi nidificati a tuo vantaggio.\n" " Se desideri un notice privato, nidifica il comando \"private\".\n" " " #: plugin.py:76 #, docstring msgid "" "<text>\n" "\n" " Replies with <text>. Equivalent to the alias, 'echo $nick: $1'.\n" " " msgstr "" "<testo>\n" "\n" " Risponde con <testo>. Equivale all'alias \"echo $nick: $1\".\n" " " #: plugin.py:85 #, docstring msgid "" "<str> [<str> ...]\n" "\n" " Replies with each of its arguments <str> in separate replies, depending\n" " the configuration of supybot.reply.oneToOne.\n" " " msgstr "" "<stringa> [<stringa> ...]\n" "\n" " Risponde con ognuno degli argomenti <stringa> in risposte separate, in base\n" " alla configurazione di supybot.reply.oneToOne.\n" " " ������������������������������������������������������������������������limnoria-2020.03.17/plugins/Reply/plugin.py���������������������������������������������������������0000644�0001750�0001750�00000007102�13634634532�017743� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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.commands import * import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Reply') class Reply(callbacks.Plugin): """This plugin contains a few commands that construct various types of replies. Some bot owners would be wise to not load this plugin because it can be easily abused. """ @internationalizeDocstring def private(self, irc, msg, args, text): """<text> Replies with <text> in private. Use nested commands to your benefit here. """ irc.reply(text, private=True) private = wrap(private, ['text']) @internationalizeDocstring def action(self, irc, msg, args, text): """<text> Replies with <text> as an action. Use nested commands to your benefit here. """ if text: irc.reply(text, action=True) else: raise callbacks.ArgumentError action = wrap(action, ['text']) @internationalizeDocstring def notice(self, irc, msg, args, text): """<text> Replies with <text> in a notice. Use nested commands to your benefit here. If you want a private notice, nest the private command. """ irc.reply(text, notice=True) notice = wrap(notice, ['text']) @internationalizeDocstring def reply(self, irc, msg, args, text): """<text> Replies with <text>. Equivalent to the alias, 'echo $nick: $1'. """ irc.reply(text, prefixNick=True) reply = wrap(reply, ['text']) @internationalizeDocstring def replies(self, irc, msg, args, strings): """<str> [<str> ...] Replies with each of its arguments <str> in separate replies, depending the configuration of supybot.reply.oneToOne. """ irc.replies(strings) replies = wrap(replies, [many('something')]) Reply = internationalizeDocstring(Reply) Class = Reply # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Reply/test.py�����������������������������������������������������������0000644�0001750�0001750�00000005676�13634634532�017442� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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 * import supybot.ircutils as ircutils class ReplyTestCase(ChannelPluginTestCase): plugins = ('Reply',) def testPrivate(self): m = self.getMsg('private [list]') self.assertFalse(self.irc.isChannel(m.args[0])) def testNotice(self): m = self.getMsg('notice [list]') self.assertEqual(m.command, 'NOTICE') def testNoticePrivate(self): m = self.assertNotError('notice [private [list]]') self.assertEqual(m.command, 'NOTICE') self.assertEqual(m.args[0], self.nick) m = self.assertNotError('private [notice [list]]') self.assertEqual(m.command, 'NOTICE') self.assertEqual(m.args[0], self.nick) def testReplies(self): self.assertResponse('replies a b c', 'a, b, and c') self.assertNotError('config channel supybot.reply.oneToOne False') self.assertResponse('replies a b c', 'a') self.assertResponse(' ', 'b') self.assertResponse(' ', 'c') self.assertNotError('config channel supybot.reply.oneToOne True') class ReplyNonChannelTestCase(PluginTestCase): plugins = ('Reply',) def testAction(self): self.prefix = 'something!else@somewhere.else' self.nick = 'something' m = self.assertAction('action foo', 'foo') self.assertFalse(m.args[0] == self.irc.nick) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ������������������������������������������������������������������limnoria-2020.03.17/plugins/Scheduler/��������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016724� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Scheduler/__init__.py���������������������������������������������������0000644�0001750�0001750�00000005000�13634634532�021022� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Gives the user the ability to schedule commands to run at a particular time, or repeatedly run at a particular interval. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Scheduler/config.py�����������������������������������������������������0000644�0001750�0001750�00000004701�13634634532�020537� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Scheduler') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Scheduler', True) Scheduler = conf.registerPlugin('Scheduler') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Scheduler, 'someConfigVariableName', # registry.Boolean(False, _("""Help for someConfigVariableName."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������limnoria-2020.03.17/plugins/Scheduler/locales/������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�020346� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Scheduler/locales/fi.po�������������������������������������������������0000644�0001750�0001750�00000006514�13634634532�021304� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: Scheduler plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 13:29+EET\n" "PO-Revision-Date: 2014-12-20 13:38+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.10\n" #: plugin.py:49 msgid "This plugin allows you to schedule commands to execute at a later time." msgstr "" "Tämä laajennus sallii komentojen ajastuksen myöhäisemmäksi ajankohdaksi." #: plugin.py:112 msgid "Makes a function suitable for scheduling from command." msgstr "Tekee toiminnon sopivaksi komennosta ajastamiseen." #: plugin.py:132 msgid "" "<seconds> <command>\n" "\n" " Schedules the command string <command> to run <seconds> seconds in " "the\n" " future. For example, 'scheduler add [seconds 30m] \"echo [cpu]\"' " "will\n" " schedule the command \"cpu\" to be sent to the channel the schedule " "add\n" " command was given in (with no prefixed nick, a consequence of using\n" " echo). Do pay attention to the quotes in that example.\n" " " msgstr "" "<sekuntit> <komento>\n" "\n" " Ajastaa <komennon> suoriutumaan <sekunteja> sekunteja " "tulevaisuuteen\n" " Esimerkiksi, 'scheduler add [seconds 30m] \"echo [cpu]\"'\n" " ajastaa komennon \"cpu\" lähetyksen kanavalle jolla,\n" " komento annettiin (ilman nimimerkki etuliitettä, johtuen\n" " \"echo\" komennon käytöstä). Kiinnitä huomiota lainausmerkkeihin " "tuossa esimerkissä.\n" " " #: plugin.py:142 msgid "Event #%i added." msgstr "Tapahtuma #%i lisätty." #: plugin.py:147 msgid "" "<id>\n" "\n" " Removes the event scheduled with id <id> from the schedule.\n" " " msgstr "" "<id>\n" "\n" " Poistaa ajastetun komennon <id> ajastuksesta.\n" " " #: plugin.py:161 plugin.py:163 msgid "Invalid event id." msgstr "Viallinen tapahtuma id." #: plugin.py:177 msgid "" "<name> <seconds> <command>\n" "\n" " Schedules the command <command> to run every <seconds> seconds,\n" " starting now (i.e., the command runs now, and every <seconds> " "seconds\n" " thereafter). <name> is a name by which the command can be\n" " unscheduled.\n" " " msgstr "" "<nimi> <sekunteja> <komento>\n" "\n" " Ajastaa <komennon> suoritumaan, joka <sekunti>,\n" " alkaen nyt (esim., komento suoriutuu nyt, ja joka <sekunti>\n" " sen jälkeen). <Nimi> on nimi, jolla komennon ajastus voidaan\n" " poistaa.\n" " " #: plugin.py:186 msgid "There is already an event with that name, please choose another name." msgstr "" "On jo olemassa tapahtuma tuolla nimellä, ole hyvä ja käytä toista nimeä." #: plugin.py:196 msgid "" "takes no arguments\n" "\n" " Lists the currently scheduled events.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Luettelee tällä hetkellä ajastetut komennot.\n" " " #: plugin.py:207 msgid "There are currently no scheduled commands." msgstr "Tällä hetkellä ei ole ajastettuja komentoja." ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Scheduler/locales/fr.po�������������������������������������������������0000644�0001750�0001750�00000005533�13634634532�021315� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2010-10-19 19:28+CEST\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\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:47 msgid "Makes a function suitable for scheduling from command." msgstr "Crée une fonction utilisable pour la programmation à partir d'une commande." #: plugin.py:57 msgid "" "<seconds> <command>\n" "\n" " Schedules the command string <command> to run <seconds> seconds in the\n" " future. For example, 'scheduler add [seconds 30m] \"echo [cpu]\"' will\n" " schedule the command \"cpu\" to be sent to the channel the schedule add\n" " command was given in (with no prefixed nick, a consequence of using\n" " echo). Do pay attention to the quotes in that example.\n" " " msgstr "" "<secondes> <commande>\n" "\n" "Exécute la <commande> dans un certain nombre de <secondes>. Par exemple, 'scheduler add [seconds 30m] \"echo [cpu]\"' programmera la commande 'cpu' pour être envoyée sur le canal. Faites attention à l'utilisateur des guillemets dans cet exemple." #: plugin.py:69 msgid "Event #%i added." msgstr "Évènement #%i ajouté." #: plugin.py:74 msgid "" "<id>\n" "\n" " Removes the event scheduled with id <id> from the schedule.\n" " " msgstr "" "<id>\n" "\n" "Déprogramme l'évènement programmé d'<id> donné." #: plugin.py:88 #: plugin.py:90 msgid "Invalid event id." msgstr "Id d'évènement invalide." #: plugin.py:95 msgid "" "<name> <seconds> <command>\n" "\n" " Schedules the command <command> to run every <seconds> seconds,\n" " starting now (i.e., the command runs now, and every <seconds> seconds\n" " thereafter). <name> is a name by which the command can be\n" " unscheduled.\n" " " msgstr "" "<nom> <secondes> <commande>\n" "\n" "Programme la <commande> pour être lancée toutes les <secondes>, à partir de maintenant (c'est à dire que la commande est lancée maintenant, dans un certain nombres de <secondes>, puis dans deux fois ce temps, etc). Le <nom> est utilisé pour déprogrammer la commande." #: plugin.py:104 msgid "There is already an event with that name, please choose another name." msgstr "Il y a déjà un évènement avec ce nom, veuillez en choisir un autre." #: plugin.py:117 msgid "" "takes no arguments\n" "\n" " Lists the currently scheduled events.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Liste tous les évènement actuellement programmés" #: plugin.py:128 msgid "There are currently no scheduled commands." msgstr "Il n'y a actuellement aucune commande programmée." ���������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Scheduler/locales/it.po�������������������������������������������������0000644�0001750�0001750�00000005731�13634634532�021322� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-07-31 11:51+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugin.py:111 #, docstring msgid "Makes a function suitable for scheduling from command." msgstr "Rende disponibile una funzione per la programmazione a partire da un comando." #: plugin.py:131 #, docstring msgid "" "<seconds> <command>\n" "\n" " Schedules the command string <command> to run <seconds> seconds in the\n" " future. For example, 'scheduler add [seconds 30m] \"echo [cpu]\"' will\n" " schedule the command \"cpu\" to be sent to the channel the schedule add\n" " command was given in (with no prefixed nick, a consequence of using\n" " echo). Do pay attention to the quotes in that example.\n" " " msgstr "" "<secondi> <comando>\n" "\n" " Programma <comando> per essere eseguito entro un certo numero di <secondi>.\n" " Ad esempio, 'scheduler add [seconds 30m] \"echo [cpu]\"' programmerà il\n" " comando \"cpu\" per essere inviato in canale (senza il nick come prefisso,\n" " come utilizzare echo). Presta attenzione alle virgolette usate nell'esempio.\n" " " #: plugin.py:141 msgid "Event #%i added." msgstr "Aggiunto l'evento #%i." #: plugin.py:146 #, docstring msgid "" "<id>\n" "\n" " Removes the event scheduled with id <id> from the schedule.\n" " " msgstr "" "<id>\n" "\n" " Rimuove l'evento programmato tramite l'<id> fornito.\n" " " #: plugin.py:160 plugin.py:162 msgid "Invalid event id." msgstr "Id di evento non valido." #: plugin.py:176 #, docstring msgid "" "<name> <seconds> <command>\n" "\n" " Schedules the command <command> to run every <seconds> seconds,\n" " starting now (i.e., the command runs now, and every <seconds> seconds\n" " thereafter). <name> is a name by which the command can be\n" " unscheduled.\n" " " msgstr "" "<nome> <secondi> <comando>\n" "\n" " Programma il <comando> per essere eseguito ogni certo numero di <secondi>\n" " a partire da subito (il comando viene eseguito ora e successivamente ogni\n" " tot <secondi>). <nome> è il nome secondo il quale il comando può essere\n" " rimosso dalla programmazione.\n" " " #: plugin.py:185 msgid "There is already an event with that name, please choose another name." msgstr "C'è già un evento con quel nome, scegline un altro." #: plugin.py:195 #, docstring msgid "" "takes no arguments\n" "\n" " Lists the currently scheduled events.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Elenca gli eventi attualmente programmati.\n" " " #: plugin.py:209 msgid "There are currently no scheduled commands." msgstr "Al momento non ci sono comandi programmati." ���������������������������������������limnoria-2020.03.17/plugins/Scheduler/plugin.py�����������������������������������������������������0000644�0001750�0001750�00000020706�13634634532�020573� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import time import os import shutil import tempfile import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.schedule as schedule import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Scheduler') import supybot.world as world import supybot.utils.minisix as minisix pickle = minisix.pickle datadir = conf.supybot.directories.data() filename = conf.supybot.directories.data.dirize('Scheduler.pickle') class Scheduler(callbacks.Plugin): """This plugin allows you to schedule commands to execute at a later time.""" def __init__(self, irc): self.__parent = super(Scheduler, self) self.__parent.__init__(irc) self.events = {} self._restoreEvents(irc) world.flushers.append(self._flush) def _restoreEvents(self, irc): try: pkl = open(filename, 'rb') try: eventdict = pickle.load(pkl) except Exception as e: self.log.debug('Unable to load pickled data: %s', e) return finally: pkl.close() except IOError as e: self.log.debug('Unable to open pickle file: %s', e) return for name, event in eventdict.items(): 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'], n) elif event['type'] == 'repeat': # repeating event self._repeat(ircobj, event['msg'], name, event['time'], event['command'], False) except AssertionError as 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: pklfd, tempfn = tempfile.mkstemp(suffix='scheduler', dir=datadir) pkl = os.fdopen(pklfd, 'wb') try: pickle.dump(self.events, pkl) except Exception as e: self.log.warning('Unable to store pickled data: %s', e) pkl.close() shutil.move(tempfn, filename) except (IOError, shutil.Error) as e: self.log.warning('File error: %s', e) def die(self): self._flush() world.flushers.remove(self._flush) self.__parent.die() def _makeCommandFunction(self, irc, msg, command, remove=True): """Makes a function suitable for scheduling from command.""" tokens = callbacks.tokenize(command, channel=msg.channel, network=irc.network) def f(): if remove: del self.events[str(f.eventId)] self.Proxy(irc.irc, msg, tokens) return f def _add(self, irc, msg, t, command, name=None): f = self._makeCommandFunction(irc, msg, command) id = schedule.addEvent(f, t, name) 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): """<seconds> <command> Schedules the command string <command> to run <seconds> seconds in the future. For example, 'scheduler add [seconds 30m] "echo [cpu]"' will schedule the command "cpu" to be sent to the channel the schedule add command was given in (with no prefixed nick, a consequence of using echo). Do pay attention to the quotes in that example. """ t = time.time() + seconds id = self._add(irc, msg, t, command) irc.replySuccess(format(_('Event #%i added.'), id)) add = wrap(add, ['positiveInt', 'text']) @internationalizeDocstring def remove(self, irc, msg, args, id): """<id> Removes the event scheduled with id <id> from the schedule. """ if id in self.events: del self.events[id] try: id = int(id) except ValueError: pass try: schedule.removeEvent(id) irc.replySuccess() except KeyError: irc.error(_('Invalid event id.')) else: irc.error(_('Invalid event id.')) remove = wrap(remove, ['lowered']) def _repeat(self, irc, msg, name, seconds, command, now=True): f = self._makeCommandFunction(irc, msg, command, remove=False) id = schedule.addPeriodicEvent(f, seconds, name, now) assert id == name self.events[name] = {'command':command, 'msg':msg, 'time':seconds, 'type':'repeat'} @internationalizeDocstring def repeat(self, irc, msg, args, name, seconds, command): """<name> <seconds> <command> Schedules the command <command> to run every <seconds> seconds, starting now (i.e., the command runs now, and every <seconds> seconds thereafter). <name> is a name by which the command can be unscheduled. """ name = name.lower() if name in self.events: irc.error(_('There is already an event with that name, please ' 'choose another name.'), Raise=True) 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() repeat = wrap(repeat, ['nonInt', 'positiveInt', 'text']) @internationalizeDocstring def list(self, irc, msg, args): """takes no arguments Lists the currently scheduled events. """ L = list(self.events.items()) if L: L.sort() for (i, (name, command)) in enumerate(L): L[i] = format('%s: %q', name, command['command']) irc.reply(format('%L', L)) else: irc.reply(_('There are currently no scheduled commands.')) list = wrap(list) Class = Scheduler # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������limnoria-2020.03.17/plugins/Scheduler/test.py�������������������������������������������������������0000644�0001750�0001750�00000011034�13634634532�020246� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2008, James McCoy # 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 * import supybot.schedule as schedule class SchedulerTestCase(ChannelPluginTestCase): plugins = ('Scheduler', 'Utilities') def tearDown(self): schedule.schedule.reset() ChannelPluginTestCase.tearDown(self) def testAddRemove(self): self.assertRegexp('scheduler list', 'no.*commands') m = self.assertNotError('scheduler add 5 echo testAddRemove') self.assertRegexp('scheduler list', 'echo testAddRemove') timeFastForward(2) self.assertNoResponse(' ', timeout=1) timeFastForward(2) self.assertResponse(' ', 'testAddRemove') m = self.assertNotError('scheduler add 5 echo testAddRemove2') # Get id. id = None for s in m.args[1].split(): s = s.lstrip('#') if s.isdigit(): id = s break self.assertTrue(id, 'Couldn\'t find id in reply.') self.assertNotError('scheduler remove %s' % id) timeFastForward(5) self.assertNoResponse(' ', timeout=1) # Need this test to run first so it has id 0 for its event def test00RemoveZero(self): id = None m = self.assertNotError('scheduler add 5 echo testRemoveZero') for s in m.args[1].split(): s = s.lstrip('#') if s.isdigit(): id = s break self.assertNotError('scheduler remove %s' % id) timeFastForward(5) self.assertNoResponse(' ', timeout=1) def testRepeat(self): self.assertRegexp('scheduler repeat repeater 5 echo testRepeat', 'testRepeat') timeFastForward(5) self.assertResponse(' ', 'testRepeat') self.assertResponse('scheduler list', 'repeater: "echo testRepeat"') timeFastForward(3) self.assertNoResponse(' ', timeout=1) timeFastForward(2) self.assertResponse(' ', 'testRepeat') self.assertNotError('scheduler remove repeater') self.assertRegexp('scheduler list', 'no.*commands') timeFastForward(5) self.assertNoResponse(' ', timeout=1) def testRepeatWorksWithNestedCommands(self): self.assertRegexp('scheduler repeat foo 5 "echo foo [echo nested]"', 'foo nested') timeFastForward(5) self.assertResponse(' ', 'foo nested') timeFastForward(3) self.assertNoResponse(' ', timeout=1) timeFastForward(2) self.assertResponse(' ', 'foo nested') self.assertNotError('scheduler remove foo') timeFastForward(5) self.assertNoResponse(' ', timeout=1) def testRepeatDisallowsIntegerNames(self): self.assertError('scheduler repeat 1234 1234 "echo NoIntegerNames"') def testRepeatDisallowsDuplicateNames(self): self.assertNotError('scheduler repeat foo 5 "echo foo"') self.assertError('scheduler repeat foo 5 "echo another foo fails"') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/SedRegex/���������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016514� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/SedRegex/__init__.py����������������������������������������������������0000644�0001750�0001750�00000005077�13634634532�020630� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2015, Michael Daniel Telatynski <postmaster@webdevguru.co.uk> # Copyright (c) 2015-2020, James Lu <james@overdrivenetworks.com> # 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. ### """ History replacer using sed-style expressions. """ import supybot import supybot.world as world __version__ = supybot.version.version __author__ = supybot.Author("Michael Daniel Telatynski", "t3chguy", "postmaster@webdevguru.co.uk") __contributors__ = {supybot.authors.jlu: ["options bolding the replacement text", "misc. bug fixes and enhancements"], supybot.Author('nyuszika7h', 'nyuszika7h', 'nyuszika7h@openmailbox.org'): ["_unpack_sed method within plugin.py"] } __maintainer__ = supybot.authors.limnoria_core __url__ = 'https://github.com/ProgVal/Limnoria/tree/master/plugins/SedRegex' from . import config from . import plugin from importlib import reload reload(config) reload(plugin) if world.testing: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/SedRegex/config.py������������������������������������������������������0000644�0001750�0001750�00000006620�13634634532�020331� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2015, Michael Daniel Telatynski <postmaster@webdevguru.co.uk> # Copyright (c) 2015-2019, James Lu <james@overdrivenetworks.com> # 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 try: from supybot.i18n import PluginInternationalization _ = PluginInternationalization('SedRegex') except: _ = lambda x: x def configure(advanced): from supybot.questions import expect, anything, something, yn conf.registerPlugin('SedRegex', True) if advanced: output("""The SedRegex plugin allows you to make Perl/sed-style regex replacements to your chat history.""") SedRegex = conf.registerPlugin('SedRegex') conf.registerChannelValue(SedRegex, 'displayErrors', registry.Boolean(True, _("""Should errors be displayed?"""))) conf.registerChannelValue(SedRegex, 'boldReplacementText', registry.Boolean(True, _("""Should the replacement text be bolded?"""))) conf.registerChannelValue(SedRegex, 'enable', registry.Boolean(False, _("""Should Perl/sed-style regex replacing work in this channel?"""))) conf.registerChannelValue(SedRegex, 'ignoreRegex', registry.Boolean(True, _("""Should Perl/sed regex replacing ignore messages which look like valid regex?"""))) conf.registerGlobalValue(SedRegex, 'processTimeout', registry.PositiveFloat(0.5, _("""Sets the timeout when processing a single regexp. The default should be adequate unless you have a busy or low-powered system that cannot process regexps quickly enough. However, you will not want to set this value too high as that would make your bot vulnerable to ReDoS attacks."""))) # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/SedRegex/plugin.py������������������������������������������������������0000644�0001750�0001750�00000017406�13634634532�020366� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2015, Michael Daniel Telatynski <postmaster@webdevguru.co.uk> # Copyright (c) 2015-2019, James Lu <james@overdrivenetworks.com> # 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.commands import * from supybot.commands import ProcessTimeoutError import supybot.plugins as plugins import supybot.ircmsgs as ircmsgs import supybot.callbacks as callbacks import supybot.ircutils as ircutils import supybot.ircdb as ircdb import supybot.utils as utils import re import sys try: from supybot.i18n import PluginInternationalization _ = PluginInternationalization('SedRegex') except ImportError: _ = lambda x: x if sys.version_info[0] < 3: raise ImportError('This plugin requires Python 3. For a legacy version of this plugin that still ' 'supports Python 2, consult the python2-legacy branch at ' 'https://github.com/jlu5/SupyPlugins/tree/python2-legacy') SED_REGEX = re.compile(r"^(?:(?P<nick>.+?)[:,] )?s(?P<delim>[^\w\s])(?P<pattern>.*?)(?P=delim)" r"(?P<replacement>.*?)(?P=delim)(?P<flags>[a-z]*)$") # Replace newlines and friends with things like literal "\n" (backslash and "n") axe_spaces = utils.str.MultipleReplacer({'\n': '\\n', '\t': '\\t', '\r': '\\r'}) class SearchNotFound(Exception): pass class SedRegex(callbacks.PluginRegexp): """History replacer using sed-style regex syntax.""" threaded = True public = True unaddressedRegexps = ['replacer'] @staticmethod def _unpack_sed(expr): if '\0' in expr: raise ValueError('Expression can\'t contain NUL') delim = expr[1] escaped_expr = '' for (i, c) in enumerate(expr): if c == delim and i > 0: if expr[i - 1] == '\\': escaped_expr = escaped_expr[:-1] + '\0' continue escaped_expr += c match = SED_REGEX.search(escaped_expr) if not match: return groups = match.groupdict() pattern = groups['pattern'].replace('\0', delim) replacement = groups['replacement'].replace('\0', delim) if groups['flags']: raw_flags = set(groups['flags']) else: raw_flags = set() flags = 0 count = 1 for flag in raw_flags: if flag == 'g': count = 0 if flag == 'i': flags |= re.IGNORECASE pattern = re.compile(pattern, flags) return (pattern, replacement, count, raw_flags) def replacer(self, irc, msg, regex): if not self.registryValue('enable', msg.args[0]): return iterable = reversed(irc.state.history) msg.tag('Replacer') try: (pattern, replacement, count, flags) = self._unpack_sed(msg.args[1]) except Exception as e: self.log.warning(_("SedRegex error: %s"), e, exc_info=True) if self.registryValue('displayErrors', msg.args[0]): irc.error('%s.%s: %s' % (e.__class__.__module__, e.__class__.__name__, e)) return next(iterable) if 's' in flags: # Special 's' flag lets the bot only look at self messages target = msg.nick else: target = regex.group('nick') if not ircutils.isNick(str(target), strictRfc=True): return regex_timeout = self.registryValue('processTimeout') try: message = process(self._replacer_process, irc, msg, target, pattern, replacement, count, iterable, timeout=regex_timeout, pn=self.name(), cn='replacer') except ProcessTimeoutError: irc.error(_("Search timed out.")) except SearchNotFound: irc.error(_("Search not found in the last %i messages.") % len(irc.state.history)) except Exception as e: if self.registryValue('displayErrors', msg.args[0]): irc.error('%s.%s: %s' % (e.__class__.__module__, e.__class__.__name__, e)) else: irc.reply(message, prefixNick=False) def _replacer_process(self, irc, msg, target, pattern, replacement, count, messages): for m in messages: if m.command in ('PRIVMSG', 'NOTICE') and \ ircutils.strEqual(m.args[0], msg.args[0]) and m.tagged('receivedBy') == irc: if target and m.nick != target: continue # Don't snarf ignored users' messages unless specifically # told to. if ircdb.checkIgnored(m.prefix) and not target: continue # When running substitutions, ignore the "* nick" part of any actions. action = ircmsgs.isAction(m) if action: text = ircmsgs.unAction(m) else: text = m.args[1] if self.registryValue('ignoreRegex', msg.args[0]) and \ m.tagged('Replacer'): continue if m.nick == msg.nick: messageprefix = msg.nick else: messageprefix = '%s thinks %s' % (msg.nick, m.nick) try: replace_result = pattern.search(text) if replace_result: if self.registryValue('boldReplacementText', msg.args[0]): replacement = ircutils.bold(replacement) subst = pattern.sub(replacement, text, count) if action: # If the message was an ACTION, prepend the nick back. subst = '* %s %s' % (m.nick, subst) subst = axe_spaces(subst) return _("%s meant to say: %s") % \ (messageprefix, subst) except Exception as e: self.log.warning(_("SedRegex error: %s"), e, exc_info=True) raise self.log.debug(_("SedRegex: Search %r not found in the last %i messages of %s."), msg.args[1], len(irc.state.history), msg.args[0]) raise SearchNotFound() replacer.__doc__ = SED_REGEX.pattern Class = SedRegex # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/SedRegex/test.py��������������������������������������������������������0000644�0001750�0001750�00000017673�13634634532�020055� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2017-2020, James Lu <james@overdrivenetworks.com> # 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 __future__ import print_function import unittest from supybot.test import * class SedRegexTestCase(ChannelPluginTestCase): other = "blah!blah@someone.else" other2 = "ghost!ghost@spooky" plugins = ('SedRegex', 'Utilities') config = {'plugins.sedregex.enable': True, 'plugins.sedregex.boldReplacementText': False} # getMsg() stalls if no message is ever sent (i.e. if the plugin fails to respond to a request) # We should limit the timeout to prevent the tests from taking forever. timeout = 3 def testSimpleReplace(self): self.feedMsg('Abcd abcdefgh') self.feedMsg('s/abcd/test/') # Run an empty command so that messages from the previous trigger are caught. m = self.getMsg(' ') self.assertIn('Abcd testefgh', str(m)) def testCaseInsensitiveReplace(self): self.feedMsg('Aliens Are Invading, Help!') self.feedMsg('s/a/e/i') m = self.getMsg(' ') self.assertIn('eliens', str(m)) def testGlobalReplace(self): self.feedMsg('AAaa aaAa a b') self.feedMsg('s/a/e/g') m = self.getMsg(' ') self.assertIn('AAee eeAe e b', str(m)) def testGlobalCaseInsensitiveReplace(self): self.feedMsg('Abba') self.feedMsg('s/a/e/gi') m = self.getMsg(' ') self.assertIn('ebbe', str(m)) def testOnlySelfReplace(self): self.feedMsg('evil machines') self.feedMsg('evil tacocats', frm=self.__class__.other) self.feedMsg('s/evil/kind/s') m = self.getMsg(' ') self.assertIn('kind machines', str(m)) def testAllFlagsReplace(self): self.feedMsg('Terrible, terrible crimes') self.feedMsg('Terrible, terrible TV shows', frm=self.__class__.other) self.feedMsg('s/terr/horr/sgi') m = self.getMsg(' ') self.assertIn('horrible, horrible crimes', str(m)) def testOtherPersonReplace(self): self.feedMsg('yeah, right', frm=self.__class__.other) self.feedMsg('s/right/left/', frm=self.__class__.other2) m = self.getMsg(' ') # Note: using the bot prefix for the s/right/left/ part causes the first nick in "X thinks Y" # to be empty? It works fine in runtime though... self.assertIn('%s thinks %s meant to say' % (ircutils.nickFromHostmask(self.__class__.other2), ircutils.nickFromHostmask(self.__class__.other)), str(m)) def testExplicitOtherReplace(self): self.feedMsg('ouch', frm=self.__class__.other2) self.feedMsg('poof', frm=self.__class__.other) self.feedMsg('wow!') self.feedMsg('%s: s/^/p/' % ircutils.nickFromHostmask(self.__class__.other2)) m = self.getMsg(' ') self.assertIn('pouch', str(m)) @unittest.skipUnless(sys.version_info[0] >= 3, 'Test fails on Python 2.') def testBoldReplacement(self): with conf.supybot.plugins.sedregex.boldReplacementText.context(True): self.feedMsg('hahahaha', frm=self.__class__.other) # One replacement self.feedMsg('s/h/H/', frm=self.__class__.other2) m = self.getMsg(' ') self.assertIn('\x02H\x02aha', str(m)) # Replace all instances self.feedMsg('s/h/H/g', frm=self.__class__.other2) m = self.getMsg(' ') self.assertIn('\x02H\x02a\x02H\x02a', str(m)) # One whole word self.feedMsg('sweet dreams are made of this', frm=self.__class__.other) self.feedMsg('s/this/cheese/', frm=self.__class__.other2) m = self.getMsg(' ') self.assertIn('of \x02cheese\x02', str(m)) def testNonSlashSeparator(self): self.feedMsg('we are all decelopers on this blessed day') self.feedMsg('s.c.v.') m = self.getMsg(' ') self.assertIn('developers', str(m)) self.feedMsg('4 / 2 = 8') self.feedMsg('s@/@*@') m = self.getMsg(' ') self.assertIn('4 * 2 = 8', str(m)) def testWeirdSeparatorsFail(self): self.feedMsg("can't touch this", frm=self.__class__.other) # Only symbols are allowed as separators self.feedMsg('blah: s a b ') self.feedMsg('blah: sdadbd') m = self.getMsg('echo dummy message') # XXX: this is a total hack... for msg in self.irc.state.history: print("Message in history: %s" % msg, end='') self.assertNotIn("cbn't", str(msg)) def testActionReplace(self): self.feedMsg("\x01ACTION sleeps\x01") self.feedMsg('s/sleeps/wakes/') m = self.getMsg(' ') self.assertIn('meant to say: * %s wakes' % self.nick, str(m)) def testOtherPersonActionReplace(self): self.feedMsg("\x01ACTION sleeps\x01", frm=self.__class__.other) self.feedMsg('s/sleeps/wakes/') m = self.getMsg(' ') n = ircutils.nickFromHostmask(self.__class__.other) self.assertIn('thinks %s meant to say: * %s wakes' % (n, n), str(m)) # https://github.com/jlu5/SupyPlugins/commit/e19abe049888667c3d0a4eb4a2c3ae88b8bea511 # We want to make sure the bot treats channel names case-insensitively, if some client # writes to it using a differente case. def testCaseNormalizationInRead(self): assert self.channel != self.channel.title() # In case Limnoria's defaults change self.feedMsg("what a strange bug", to=self.channel.title()) self.feedMsg('s/strange/hilarious/', to=self.channel) m = self.getMsg(' ') self.assertIn('what a hilarious bug', str(m)) def testCaseNormalizationInReplace(self): assert self.channel != self.channel.title() # In case Limnoria's defaults change self.feedMsg("Segmentation fault", to=self.channel) self.feedMsg('s/$/ (core dumped)/', to=self.channel.title()) m = self.getMsg(' ') self.assertIn('Segmentation fault (core dumped)', str(m)) @unittest.skipIf(world.disableMultiprocessing, "Test requires multiprocessing to be enabled") def testReDoSTimeout(self): # From https://snyk.io/blog/redos-and-catastrophic-backtracking/ for idx in range(500): self.feedMsg("ACCCCCCCCCCCCCCCCCCCCCCCCCCCCX") self.feedMsg(r"s/A(B|C+)+D/this should abort/") m = self.getMsg(' ', timeout=1) self.assertIn('timed out', str(m)) # TODO: test ignores # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������limnoria-2020.03.17/plugins/Seen/�������������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�015700� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Seen/__init__.py��������������������������������������������������������0000644�0001750�0001750�00000004701�13634634532�020005� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Keeps track of the last time a user was seen on a channel. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������limnoria-2020.03.17/plugins/Seen/config.py����������������������������������������������������������0000644�0001750�0001750�00000005567�13634634532�017526� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Seen') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Seen', True) Seen = conf.registerPlugin('Seen') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Seen, 'someConfigVariableName', # registry.Boolean(False, _("""Help for someConfigVariableName."""))) conf.registerChannelValue(Seen, 'minimumNonWildcard', registry.NonNegativeInteger(2, _("""The minimum non-wildcard characters required to perform a 'seen' request. Of course, it only applies if there is a wildcard in the request."""))) conf.registerChannelValue(Seen, 'showLastMessage', registry.Boolean(True, _("""Determines whether the last message will be displayed with @seen. Useful for keeping messages from a channel private."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Seen/locales/�����������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017322� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Seen/locales/de.po������������������������������������������������������0000644�0001750�0001750�00000011170�13634634532�020244� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2012-03-11 20:58+UTC\n" "PO-Revision-Date: 2012-04-27 15:33+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: German <fbesser@gmail.com>\n" "Language: de\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" #: plugin.py:201 #: plugin.py:282 msgid "%s was last seen in %s %s ago: %s" msgstr "%s wurde zuletzt in %s vor %s gesehen: %s" #: plugin.py:208 msgid "%s (%s ago)" msgstr "%s (vor %s)" #: plugin.py:210 msgid "%s could be %L" msgstr "%s könnte %L sein" #: plugin.py:210 msgid "or" msgstr "oder" #: plugin.py:212 msgid "I haven't seen anyone matching %s." msgstr "Ich habe niemanden gesehen der auf %s passt." #: plugin.py:214 #: plugin.py:286 msgid "I have not seen %s." msgstr "Ich habe %s nicht gesehn." #: plugin.py:218 msgid "" "[<channel>] <nick>\n" "\n" " Returns the last time <nick> was seen and what <nick> was last seen\n" " saying. <channel> is only necessary if the message isn't sent on the\n" " channel itself. <nick> may contain * as a wildcard.\n" " " msgstr "" "[<Kanal>] <Nick>\n" "\n" "Gibt an wann <Nick> zum letzten Mal gesehen wurde und was dieser zuletzt sagte. <Kanal> ist nur nötig, falls die Nachricht nicht im Kanal selbst gesendet wurde. <Nick> kann * als Platzhalter beinhalten." #: plugin.py:229 msgid "" "[<channel>] [--user <name>] [<nick>]\n" "\n" " Returns the last time <nick> was seen and what <nick> was last seen\n" " doing. This includes any form of activity, instead of just PRIVMSGs.\n" " If <nick> isn't specified, returns the last activity seen in\n" " <channel>. If --user is specified, looks up name in the user database\n" " and returns the last time user was active in <channel>. <channel> is\n" " only necessary if the message isn't sent on the channel itself.\n" " " msgstr "" "[<Kanal>] [--user <Name>] [<Nick>]\n" "\n" "Gibt zurück wann <Nick> zuletzt gesehen wurde und was dieser Tat. Dies beinhaltet jede Art von Aktivität, anstatt nur PRIVMSGs. Falls <Nick> nicht angegen wurde, wird die letzte gesehene Aktivität im <Kanal> ausgegeben. Falls --user angegeben wird, wird in der Benutzerdatenbank nachgeschaut und die Zeit ausgegeben als der Benutzer zuletzte Aktiv war im <Kanal>. <Kanal> ist nur nötig, falls die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:259 msgid "Someone was last seen in %s %s ago: %s" msgstr "Jemand wurde zuletzt in %s gesehen, vor %s: %s" #: plugin.py:263 msgid "I have never seen anyone." msgstr "Ich habe noch niemals jemanden gesehen." #: plugin.py:267 msgid "" "[<channel>]\n" "\n" " Returns the last thing said in <channel>. <channel> is only necessary\n" " if the message isn't sent in the channel itself.\n" " " msgstr "" "[<Kanal>]\n" "\n" "Gibt das zuletzt gesagte im <Kanal> aus. <Kanal> ist nur nötig, falls die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:290 msgid "" "[<channel>] <name>\n" "\n" " Returns the last time <name> was seen and what <name> was last seen\n" " saying. This looks up <name> in the user seen database, which means\n" " that it could be any nick recognized as user <name> that was seen.\n" " <channel> is only necessary if the message isn't sent in the channel\n" " itself.\n" " " msgstr "" "[<Kanal>] <Name>\n" "\n" "Gibt an wann <Name> zum letzten mal gesehen wurde und was <Name> zuletzt sagte. Der <Name> wird in der Gesehen-Datenbank nachgeschaut, dies bedeutet, dass es jeder Nick sein kann der vom Benutzer <Name> gesehen wurde. <Kanal> ist nur nötig, falls die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:303 msgid "" "[<channel>] <nick>\n" "\n" " Returns the messages since <nick> last left the channel.\n" " " msgstr "" "[<Kanal>] <Nick>\n" "\n" "Gibt die Nachrichten zurück, die seit dem verlassen von <Nick> im Kanal gesendet wurden." #: plugin.py:310 #, fuzzy msgid "I am not in %s." msgstr "Ich habe %s nicht gesehn." #: plugin.py:313 #, fuzzy msgid "%s must be in %s to use this command." msgstr "Du musst in %s sein, um diesen Befehl zu benutzen." #: plugin.py:334 msgid "I couldn't find in my history of %s messages where %r last left %s" msgstr "Ich konnte in meiner Vergangenheit von %s Nachrichten nichts finden, wo %r zuletzt %s verlassen hat." #: plugin.py:343 msgid "Either %s didn't leave, or no messages were sent while %s was gone." msgstr "Entweder ist %s nicht gegangen, oder keine Nachrichten wurde gesendet während %s weg war." ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Seen/locales/fi.po������������������������������������������������������0000644�0001750�0001750�00000015226�13634634532�020260� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: Seen plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 11:59+EET\n" "PO-Revision-Date: 2014-12-20 13:06+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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-Generator: Poedit 1.6.10\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: config.py:49 msgid "" "The minimum non-wildcard characters\n" " required to perform a 'seen' request. Of course, it only applies if " "there\n" " is a wildcard in the request." msgstr "" "Vähimmäismäärä ei-jokerimerkkejä, joita vaaditaan 'seen'-pyynnön " "tekemiseen.\n" " Vaikuttaa tietysti vain, jos pyynnössä on jokerimerkki." #: config.py:53 msgid "" "Determines whether the last message will\n" " be displayed with @seen. Useful for keeping messages from a channel\n" " private." msgstr "" "Määrittää näytetäänkö viimeisin viesti @seen-komennolla. Hyödyllinen kanavan " "viestien\n" " yksityisenä pitämiseen." #: plugin.py:96 msgid "" "This plugin allows you to see when and what someone last said and\n" " what you missed since you left a channel." msgstr "" "Tämä plugin sallii jonkun viimeisen sanan näkemisen ja sen mitä sinulta jäi " "näkemättä edellisen\n" "kanavalta poistumisesi jälkeen." #: plugin.py:200 #, fuzzy msgid "Not enough non-wildcard characters." msgstr "Ei-jokerimerkki merkkejä ei ole annettu riittävästi." #: plugin.py:208 plugin.py:311 #, fuzzy msgid "%s was last seen in %s %s ago" msgstr "%s nähtiin viimeksi kanavalla %s %s sitten: %s" #: plugin.py:214 plugin.py:286 plugin.py:315 msgid "%s: %s" msgstr "%s: %s" #: plugin.py:220 msgid "%s (%s ago)" msgstr "%s (%s sitten)" #: plugin.py:222 msgid "%s could be %L" msgstr "%s voisi olla %L" #: plugin.py:222 msgid "or" msgstr "tai" #: plugin.py:224 msgid "I haven't seen anyone matching %s." msgstr "En ole nähnyt kenenkään täsmäävän %s." #: plugin.py:226 plugin.py:318 msgid "I have not seen %s." msgstr "En ole nähnyt %s:ää." #: plugin.py:230 msgid "" "[<channel>] <nick>\n" "\n" " Returns the last time <nick> was seen and what <nick> was last seen\n" " saying. <channel> is only necessary if the message isn't sent on " "the\n" " channel itself. <nick> may contain * as a wildcard.\n" " " msgstr "" "[<kanava>] <nimimerkki>\n" "\n" " Palauttaa viimeisen kerran, kun <nimimerkki> nähtiin ja mitä " "<nimimerkin> nähtiin sanovan viimeksi.\n" " <Kanava> on vaadittu vain jos viestiä ei lähetetä kanavalla\n" " itsellään.\n" " " #: plugin.py:237 plugin.py:257 msgid "You've found me!" msgstr "Löysit minut!" #: plugin.py:247 msgid "" "[<channel>] [--user <name>] [<nick>]\n" "\n" " Returns the last time <nick> was seen and what <nick> was last seen\n" " doing. This includes any form of activity, instead of just " "PRIVMSGs.\n" " If <nick> isn't specified, returns the last activity seen in\n" " <channel>. If --user is specified, looks up name in the user " "database\n" " and returns the last time user was active in <channel>. <channel> " "is\n" " only necessary if the message isn't sent on the channel itself.\n" " " msgstr "" "[<kanava>] [--user <nimimerkki>] [<nimimerkki>]\n" "\n" " Palauttaa viimeisen kerran, jolloin <nimimerkki> nähtiin viimeeksi " "ja mitä <nimimerkki> nähtiin viimeeksi\n" " tekemässä. Tämä sisältää kaikenlaisen aktiivisuuden, sen sijaan, " "että sisältäisi vain PRIVMSGitä.\n" " Jos <nimimerkki> ei ole määritetty, palauttaa viimeisen " "aktiivisuuden, joka tapahtui\n" " <kanavalla>. Jos --user on määritetty, etsii nimeä " "käyttäjätietokannasta\n" " ja palauttaa viimeisen kerran, kun käyttäjä oli aktiivinen " "<kanavalla>. <Kanava> on\n" " on vaadittu vain, jos viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:283 #, fuzzy msgid "Someone was last seen in %s %s ago" msgstr "Joku nähtiin viimeeksi kanavalla %s %s sitten: %s" #: plugin.py:289 msgid "I have never seen anyone." msgstr "Minä en ole nähnyt ketään." #: plugin.py:293 msgid "" "[<channel>]\n" "\n" " Returns the last thing said in <channel>. <channel> is only " "necessary\n" " if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>]\n" "\n" " Palauttaa viimeisen asian, joka sanottiin <kanavalla>. <Kanava> on " "vaadittu vain, jos\n" " viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:322 msgid "" "[<channel>] <name>\n" "\n" " Returns the last time <name> was seen and what <name> was last seen\n" " saying. This looks up <name> in the user seen database, which " "means\n" " that it could be any nick recognized as user <name> that was seen.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<kanava>] <nimi>\n" "\n" " Palauttaa viimeisen kerran, kun <nimi> nähtiin ja mitä <nimi> " "nähtiin viimeksi\n" " sanomassa. Tämä etsii <nimeä> käyttäjän näkemistietokannasta, mikä " "tarkoittaa,\n" " että se voi olla mikä tahansa nimimerkki. joka on tunnistettu " "käyttäjä<nimeksi> joka on nähty.\n" " <Kanava> on vaadittu vain, jos viestiä ei lähetetä kanavalla\n" " itsellään.\n" " " #: plugin.py:338 msgid "" "[<channel>] [<nick>]\n" "\n" " Returns the messages since <nick> last left the channel.\n" " If <nick> is not given, it defaults to the nickname of the person\n" " calling the command.\n" " " msgstr "" "[<kanava>] <nimimerkki>\n" "\n" " Palauttaa sen jälkeiset viestit, kun <nimimerkki> viimeksi poistui " "kanavalta.\n" "Ellei <nimimerkkiä> anneta, se on oletuksena komentoa pyytävän henkilön " "nimimerkki. " #: plugin.py:347 msgid "I am not in %s." msgstr "En ole nähnyt %s:ää." #: plugin.py:350 msgid "%s must be in %s to use this command." msgstr "Käyttäjän %s täytyy olla kanavalla %s käyttääkseen tätä komentoa." #: plugin.py:373 msgid "I couldn't find in my history of %s messages where %r last left %s" msgstr "" "En voinut löytää %s viestin historiasta milloin %r viimeksi lähti kanavalta " "%s" #: plugin.py:382 msgid "Either %s didn't leave, or no messages were sent while %s was gone." msgstr "" "Joko %s ei lähtenyt, tai yhtään viestiä ei lähetetty silloin, kun %s oli " "poissa." ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Seen/locales/fr.po������������������������������������������������������0000644�0001750�0001750�00000013401�13634634532�020262� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-01-21 22:33+CET\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: Limnoria <progval@gmail.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" "Language: fr\n" #: config.py:49 msgid "" "The minimum non-wildcard characters\n" " required to perform a 'seen' request. Of course, it only applies if " "there\n" " is a wildcard in the request." msgstr "" "Le nombre minimum de caractères non-joker nécessaires pour effectuer une " "requête \"seen\". Bien sûr, cela ne s'applique que s'il y a des jokers dans " "la requête." #: config.py:53 msgid "" "Determines whether the last message will\n" " be displayed with @seen. Useful for keeping messages from a channel\n" " private." msgstr "" "Détermine si le dernier message sera affiché avec @seen. Utile pour garder " "les messages d’un salon privés." #: plugin.py:198 msgid "Not enough non-wildcard characters." msgstr "Pas assez de caractères non-joker." #: plugin.py:206 plugin.py:303 msgid "%s was last seen in %s %s ago" msgstr "%s a été vu-e pour la dernière fois sur %s il y a %s" #: plugin.py:212 plugin.py:278 plugin.py:307 msgid "%s: %s" msgstr "%s : %s" #: plugin.py:218 msgid "%s (%s ago)" msgstr "%s (il y a %s)" #: plugin.py:220 msgid "%s could be %L" msgstr "%s doit être %L" #: plugin.py:220 msgid "or" msgstr "ou" #: plugin.py:222 msgid "I haven't seen anyone matching %s." msgstr "Je n'ai vu personne correspondant à %s." #: plugin.py:224 plugin.py:310 msgid "I have not seen %s." msgstr "Je n'ai pas vu %s." #: plugin.py:228 msgid "" "[<channel>] <nick>\n" "\n" " Returns the last time <nick> was seen and what <nick> was last seen\n" " saying. <channel> is only necessary if the message isn't sent on " "the\n" " channel itself. <nick> may contain * as a wildcard.\n" " " msgstr "" "[<canal>] <nick>\n" "\n" "Retourne la dernière fois que le <nick> a été vu-e et la dernière fois que " "<nick> a parlé. <canal> n'est nécessaire que si le message n'est pas envoyé " "sur le canal lui-même. <nick> peut contenir le joker *." #: plugin.py:242 msgid "" "[<channel>] [--user <name>] [<nick>]\n" "\n" " Returns the last time <nick> was seen and what <nick> was last seen\n" " doing. This includes any form of activity, instead of just " "PRIVMSGs.\n" " If <nick> isn't specified, returns the last activity seen in\n" " <channel>. If --user is specified, looks up name in the user " "database\n" " and returns the last time user was active in <channel>. <channel> " "is\n" " only necessary if the message isn't sent on the channel itself.\n" " " msgstr "" "[<canal>] [--user <nom>] [<nick>]\n" "\n" "Retourne la dernière fois que le <nick> a été vu-e et ce quand iel a fait " "quelque chose pour la première fois. Cela inclue toute forme d'activité, et " "pas uniquement envoyer des messages. Si le <nick> n'est pas donné, retourne " "la dernière activité vue sur le <canal>. Si --user est spécifié, recherche " "le nom dans la base de données des utilisateurs la dernière fois que celui " "en question a été vu-e actif-ve sur le <canal. <canal> n'est nécessaire que si le " "message n'est pas envoyé sur le canal lui-même." #: plugin.py:275 msgid "Someone was last seen in %s %s ago" msgstr "Quelqu'un a été vu-e pour la dernière fois sur %s il y a %s" #: plugin.py:281 msgid "I have never seen anyone." msgstr "Je n'ai jamais vu personne." #: plugin.py:285 msgid "" "[<channel>]\n" "\n" " Returns the last thing said in <channel>. <channel> is only " "necessary\n" " if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>]\n" "\n" "Retourne la dernière chose dite sur le <canal>. <canal> n'est nécessaire que " "si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:314 msgid "" "[<channel>] <name>\n" "\n" " Returns the last time <name> was seen and what <name> was last seen\n" " saying. This looks up <name> in the user seen database, which " "means\n" " that it could be any nick recognized as user <name> that was seen.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<canal>] <nom>\n" "\n" "Retourne la dernière fois que <nom> a été vu-e et la dernière fois que <nom> a " "été vu parler. Cela recherche <nom> dans la base de données des utilisateurs " "vus, ce qui signifie que si le nick n'était pas reconnu comme l'utilisateur " "<nom>, il n'est pas considéré comme vu. <canal> n'est nécessaire que si le " "message n'est pas envoyé sur le canal lui-même." #: plugin.py:330 msgid "" "[<channel>] [<nick>]\n" "\n" " Returns the messages since <nick> last left the channel.\n" " If <nick> is not given, it defaults to the nickname of the person\n" " calling the command.\n" " " msgstr "" "[<canal>] <nick>\n" "\n" "Retourne les messages depuis que <nick> est parti-e du canal.<nick> correspond " "par défaut au nick de la personne appelant la commande." #: plugin.py:339 msgid "I am not in %s." msgstr "Je ne suis pas dans %s." #: plugin.py:342 msgid "%s must be in %s to use this command." msgstr "%s doit être dans %s pour utiliser cette commande." #: plugin.py:365 msgid "I couldn't find in my history of %s messages where %r last left %s" msgstr "" "Je ne peux pas trouver dans mon historique de %s messages, où %r a quitté il " "y a %s" #: plugin.py:374 msgid "Either %s didn't leave, or no messages were sent while %s was gone." msgstr "" "Soit %s n'est jamais parti-e, soit aucun message n'a été envoyé depuis qu'iel " "est parti-e." ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Seen/locales/it.po������������������������������������������������������0000644�0001750�0001750�00000011344�13634634532�020273� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2012-03-16 00:09+0100\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugin.py:201 plugin.py:282 msgid "%s was last seen in %s %s ago: %s" msgstr "%s è stato visto per l'ultima volta in %s %s fa: %s" #: plugin.py:208 msgid "%s (%s ago)" msgstr "%s (%s fa)" #: plugin.py:210 msgid "%s could be %L" msgstr "%s potrebbe essere %L" #: plugin.py:210 msgid "or" msgstr "oppure" #: plugin.py:212 msgid "I haven't seen anyone matching %s." msgstr "Non ho visto nessuno che corrisponda a %s." #: plugin.py:214 plugin.py:286 msgid "I have not seen %s." msgstr "Non ho visto %s." #: plugin.py:218 #, docstring msgid "" "[<channel>] <nick>\n" "\n" " Returns the last time <nick> was seen and what <nick> was last seen\n" " saying. <channel> is only necessary if the message isn't sent on the\n" " channel itself. <nick> may contain * as a wildcard.\n" " " msgstr "" "[<canale>] <nick>\n" "\n" " Riporta l'ultima volta che <nick> è stato visto e cosa stava dicendo.\n" " <canale> è necessario solo se il messaggio non viene inviato nel canale\n" " stesso. <nick> può contenere * come wildcard.\n" " " #: plugin.py:229 #, docstring msgid "" "[<channel>] [--user <name>] [<nick>]\n" "\n" " Returns the last time <nick> was seen and what <nick> was last seen\n" " doing. This includes any form of activity, instead of just PRIVMSGs.\n" " If <nick> isn't specified, returns the last activity seen in\n" " <channel>. If --user is specified, looks up name in the user database\n" " and returns the last time user was active in <channel>. <channel> is\n" " only necessary if the message isn't sent on the channel itself.\n" " " msgstr "" "[<canale>] [--user <nome>] [<nick>]\n" "\n" " Riporta l'ultima volta che <nick> è stato visto e cosa stava facendo.\n" " Include qualsiasi forma di attività, non solo l'invio di messaggi.\n" " Se <nick> non è specificato, riporta l'ultima attività vista in <canale>.\n" " Se --user è specificato, cerca il nome nel database degli utenti e riporta\n" " l'ultima volta che l'utente era attivo in <canale>. <canale> è necessario\n" " solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:259 msgid "Someone was last seen in %s %s ago: %s" msgstr "Qualcuno è stato visto per l'ultima volta in %s %s fa: %s" #: plugin.py:263 msgid "I have never seen anyone." msgstr "Non ho mai visto nessuno." #: plugin.py:267 #, docstring msgid "" "[<channel>]\n" "\n" " Returns the last thing said in <channel>. <channel> is only necessary\n" " if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>]\n" "\n" " Riporta l'ultima cosa detta in <canale>. <canale> è necessario\n" " solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:290 #, docstring msgid "" "[<channel>] <name>\n" "\n" " Returns the last time <name> was seen and what <name> was last seen\n" " saying. This looks up <name> in the user seen database, which means\n" " that it could be any nick recognized as user <name> that was seen.\n" " <channel> is only necessary if the message isn't sent in the channel\n" " itself.\n" " " msgstr "" "[<canale>] <nome>\n" "\n" " Riporta l'ultima volta che <nome> è stato visto e cosa stava dicendo.\n" " Cerca <nome> nel database degli utenti visti, ovvero qualsiasi nick che\n" " venga riconosciuto con il <nome> dell'utente che è stato visto.\n" " <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:303 #, docstring msgid "" "[<channel>] <nick>\n" "\n" " Returns the messages since <nick> last left the channel.\n" " " msgstr "" "[<canale>] <nick>\n" "\n" " Riporta i messaggi da quando <nick> ha lasciato il canale l'ultima volta.\n" " " #: plugin.py:310 msgid "I am not in %s." msgstr "Non sono in %s." #: plugin.py:313 msgid "%s must be in %s to use this command." msgstr "Per usare questo comando %s deve essere in %s." #: plugin.py:334 msgid "I couldn't find in my history of %s messages where %r last left %s" msgstr "Non trovo nella cronologia dei messaggi di %s dove %r ha lasciato %s l'ultima volta." #: plugin.py:343 msgid "Either %s didn't leave, or no messages were sent while %s was gone." msgstr "%s non è uscito o non ha inviato alcun messaggio quando se n'é andato." ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Seen/plugin.py����������������������������������������������������������0000644�0001750�0001750�00000035573�13634634532�017557� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2010-2011, 2013, James McCoy # 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 re import sys import time import supybot.log as log import supybot.conf as conf import supybot.utils as utils import supybot.world as world import supybot.ircdb as ircdb from supybot.commands import * import supybot.irclib as irclib import supybot.utils.minisix as minisix import supybot.ircmsgs as ircmsgs import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Seen') class IrcStringAndIntDict(utils.InsensitivePreservingDict): def key(self, x): if isinstance(x, int): return x else: return ircutils.toLower(x) class SeenDB(plugins.ChannelUserDB): IdDict = IrcStringAndIntDict def serialize(self, v): return list(v) def deserialize(self, channel, id, L): (seen, saying) = L return (float(seen), saying) def update(self, channel, nickOrId, saying): seen = time.time() self[channel, nickOrId] = (seen, saying) self[channel, '<last>'] = (seen, saying) def seenWildcard(self, channel, nick): nicks = ircutils.IrcSet() nickRe = re.compile('^%s$' % '.*'.join(nick.split('*')), re.I) for (searchChan, searchNick) in self.keys(): #print 'chan: %s ... nick: %s' % (searchChan, searchNick) if isinstance(searchNick, int): # We need to skip the reponses that are keyed by id as they # apparently duplicate the responses for the same person that # are keyed by nick-string continue if ircutils.strEqual(searchChan, channel): if nickRe.search(searchNick) is not None: nicks.add(searchNick) L = [[nick, self.seen(channel, nick)] for nick in nicks] def negativeTime(x): return -x[1][0] utils.sortBy(negativeTime, L) return L def seen(self, channel, nickOrId): return self[channel, nickOrId] filename = conf.supybot.directories.data.dirize('Seen.db') anyfilename = conf.supybot.directories.data.dirize('Seen.any.db') class Seen(callbacks.Plugin): """This plugin allows you to see when and what someone last said and what you missed since you left a channel.""" noIgnore = True def __init__(self, irc): self.__parent = super(Seen, self) self.__parent.__init__(irc) self.db = SeenDB(filename) self.anydb = SeenDB(anyfilename) self.lastmsg = {} world.flushers.append(self.db.flush) world.flushers.append(self.anydb.flush) def die(self): if self.db.flush in world.flushers: world.flushers.remove(self.db.flush) else: self.log.debug('Odd, no flush in flushers: %r', world.flushers) self.db.close() if self.anydb.flush in world.flushers: world.flushers.remove(self.anydb.flush) else: self.log.debug('Odd, no flush in flushers: %r', world.flushers) self.anydb.close() self.__parent.die() def __call__(self, irc, msg): self.__parent.__call__(irc, msg) def doPrivmsg(self, irc, msg): if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg): return if msg.channel: channel = msg.channel said = ircmsgs.prettyPrint(msg) self.db.update(channel, msg.nick, said) self.anydb.update(channel, msg.nick, said) try: id = ircdb.users.getUserId(msg.prefix) self.db.update(channel, id, said) self.anydb.update(channel, id, said) except KeyError: pass # Not in the database. def doPart(self, irc, msg): channel = msg.args[0] said = ircmsgs.prettyPrint(msg) self.anydb.update(channel, msg.nick, said) try: id = ircdb.users.getUserId(msg.prefix) self.anydb.update(channel, id, said) except KeyError: pass # Not in the database. doJoin = doPart doKick = doPart def doQuit(self, irc, msg): said = ircmsgs.prettyPrint(msg) try: id = ircdb.users.getUserId(msg.prefix) except KeyError: id = None # Not in the database. for channel in msg.tagged('channels'): self.anydb.update(channel, msg.nick, said) if id is not None: self.anydb.update(channel, id, said) doNick = doQuit def doMode(self, irc, msg): # Filter out messages from network Services if msg.nick: try: id = ircdb.users.getUserId(msg.prefix) except KeyError: id = None # Not in the database. channel = msg.args[0] said = ircmsgs.prettyPrint(msg) self.anydb.update(channel, msg.nick, said) if id is not None: self.anydb.update(channel, id, said) doTopic = doMode def _seen(self, irc, channel, name, any=False): if any: db = self.anydb else: db = self.db try: results = [] if '*' in name: if (len(name.replace('*', '')) < self.registryValue('minimumNonWildcard', channel, irc.network)): irc.error(_('Not enough non-wildcard characters.'), Raise=True) results = db.seenWildcard(channel, name) else: results = [[name, db.seen(channel, name)]] if len(results) == 1: (nick, info) = results[0] (when, said) = info reply = format(_('%s was last seen in %s %s ago'), nick, channel, utils.timeElapsed(time.time()-when)) if self.registryValue('showLastMessage', channel, irc.network): if minisix.PY2: said = said.decode('utf8') reply = _('%s: %s') % (reply, said) irc.reply(reply) elif len(results) > 1: L = [] for (nick, info) in results: (when, said) = info L.append(format(_('%s (%s ago)'), nick, utils.timeElapsed(time.time()-when))) irc.reply(format(_('%s could be %L'), name, (L, _('or')))) else: irc.reply(format(_('I haven\'t seen anyone matching %s.'), name)) except KeyError: irc.reply(format(_('I have not seen %s.'), name)) def _checkChannelPresence(self, irc, channel, target, you): if channel not in irc.state.channels: irc.error(_("I'm not in %s." % channel), Raise=True) if target not in irc.state.channels[channel].users: if you: msg = format(_('You must be in %s to use this command.'), channel) else: msg = format(_('%s must be in %s to use this command.'), target, channel) irc.error(msg, Raise=True) @internationalizeDocstring def seen(self, irc, msg, args, channel, name): """[<channel>] <nick> Returns the last time <nick> was seen and what <nick> was last seen saying. <channel> is only necessary if the message isn't sent on the channel itself. <nick> may contain * as a wildcard. """ if name and ircutils.strEqual(name, irc.nick): irc.reply(_("You've found me!")) return self._checkChannelPresence(irc, channel, msg.nick, True) self._seen(irc, channel, name) seen = wrap(seen, ['channel', 'something']) @internationalizeDocstring def any(self, irc, msg, args, channel, optlist, name): """[<channel>] [--user <name>] [<nick>] Returns the last time <nick> was seen and what <nick> was last seen doing. This includes any form of activity, instead of just PRIVMSGs. If <nick> isn't specified, returns the last activity seen in <channel>. If --user is specified, looks up name in the user database and returns the last time user was active in <channel>. <channel> is only necessary if the message isn't sent on the channel itself. """ if name and ircutils.strEqual(name, irc.nick): irc.reply(_("You've found me!")) return self._checkChannelPresence(irc, channel, msg.nick, True) if name and optlist: raise callbacks.ArgumentError elif name: self._seen(irc, channel, name, any=True) elif optlist: for (option, arg) in optlist: if option == 'user': user = arg self._user(irc, channel, user, any=True) else: self._last(irc, channel, any=True) any = wrap(any, ['channel', getopts({'user': 'otherUser'}), additional('something')]) def _last(self, irc, channel, any=False): if any: db = self.anydb else: db = self.db try: (when, said) = db.seen(channel, '<last>') reply = format(_('Someone was last seen in %s %s ago'), channel, utils.timeElapsed(time.time()-when)) if self.registryValue('showLastMessage', channel, irc.network): reply = _('%s: %s') % (reply, said) irc.reply(reply) except KeyError: irc.reply(_('I have never seen anyone.')) @internationalizeDocstring def last(self, irc, msg, args, channel): """[<channel>] Returns the last thing said in <channel>. <channel> is only necessary if the message isn't sent in the channel itself. """ self._checkChannelPresence(irc, channel, msg.nick, True) self._last(irc, channel) last = wrap(last, ['channel']) def _user(self, irc, channel, user, any=False): if any: db = self.anydb else: db = self.db try: (when, said) = db.seen(channel, user.id) reply = format(_('%s was last seen in %s %s ago'), user.name, channel, utils.timeElapsed(time.time()-when)) if self.registryValue('showLastMessage', channel, irc.network): reply = _('%s: %s') % (reply, said) irc.reply(reply) except KeyError: irc.reply(format(_('I have not seen %s.'), user.name)) @internationalizeDocstring def user(self, irc, msg, args, channel, user): """[<channel>] <name> Returns the last time <name> was seen and what <name> was last seen saying. This looks up <name> in the user seen database, which means that it could be any nick recognized as user <name> that was seen. <channel> is only necessary if the message isn't sent in the channel itself. """ self._checkChannelPresence(irc, channel, msg.nick, True) self._user(irc, channel, user) user = wrap(user, ['channel', 'otherUser']) @internationalizeDocstring def since(self, irc, msg, args, channel, nick): """[<channel>] [<nick>] Returns the messages since <nick> last left the channel. If <nick> is not given, it defaults to the nickname of the person calling the command. """ if nick is None: nick = msg.nick you = True else: you = False self._checkChannelPresence(irc, channel, nick, you) if nick is None: nick = msg.nick end = None # By default, up until the most recent message. for (i, m) in utils.seq.renumerate(irc.state.history): if end is None and m.command == 'JOIN' and \ ircutils.strEqual(m.args[0], channel) and \ ircutils.strEqual(m.nick, nick): end = i if m.command == 'PART' and \ ircutils.strEqual(m.nick, nick) and \ ircutils.strEqual(m.args[0], channel): break elif m.command == 'QUIT' and ircutils.strEqual(m.nick, nick): # XXX We assume the person was in-channel at this point. break elif m.command == 'KICK' and \ ircutils.strEqual(m.args[1], nick) and \ ircutils.strEqual(m.args[0], channel): break else: # I never use this; it only kicks in when the for loop exited normally. irc.error(format(_('I couldn\'t find in my history of %s messages ' 'where %r last left %s'), len(irc.state.history), nick, channel)) return msgs = [m for m in irc.state.history[i:end] if m.command == 'PRIVMSG' and ircutils.strEqual(m.args[0], channel)] if msgs: irc.reply(format('%L', list(map(ircmsgs.prettyPrint, msgs)))) else: irc.reply(format(_('Either %s didn\'t leave, ' 'or no messages were sent while %s was gone.'), nick, nick)) since = wrap(since, ['channel', additional('nick')]) Class = Seen # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Seen/test.py������������������������������������������������������������0000644�0001750�0001750�00000012417�13634634532�017230� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2013, James McCoy # 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 * import supybot.ircdb as ircdb class ChannelDBTestCase(ChannelPluginTestCase): plugins = ('Seen', 'User') def setUp(self): ChannelPluginTestCase.setUp(self) self.prefix = 'foo!bar@baz' self.nick = 'foo' self.wildcardTest = ['f*', '*oo', '*foo*', 'f*o*o'] self.irc.feedMsg(ircmsgs.privmsg(self.irc.nick, 'register foo bar', prefix=self.prefix)) _ = self.irc.takeMsg() chancap = ircdb.makeChannelCapability(self.channel, 'op') ircdb.users.getUser(self.nick).addCapability(chancap) def testNoKeyErrorEscapeFromSeen(self): self.irc.feedMsg(ircmsgs.join(self.channel, self.irc.nick, prefix=self.prefix)) self.assertRegexp('seen asldfkjasdlfkj', '^I have not seen') self.assertNotRegexp('seen asldfkjasdlfkj', 'KeyError') def testAny(self): self.irc.feedMsg(ircmsgs.join(self.channel, self.irc.nick, prefix=self.prefix)) self.assertRegexp('seen any', '%s <%s> has joined' % (self.nick, self.prefix)) self.irc.feedMsg(ircmsgs.mode(self.channel, args=('+o', self.nick), prefix=self.prefix)) self.assertRegexp('seen any %s' % self.nick, '^%s was last seen.*:' % self.nick) with conf.supybot.plugins.seen.showLastMessage.context(False): self.assertRegexp('seen any %s' % self.nick, '^%s was last seen[^:]*' % self.nick) self.assertNotError('config plugins.Seen.minimumNonWildcard 0') orig = conf.supybot.protocols.irc.strictRfc() try: for state in (True, False): conf.supybot.protocols.irc.strictRfc.setValue(state) for wildcard in self.wildcardTest: self.assertRegexp('seen any %s' % wildcard, '^%s was last seen' % self.nick) self.assertRegexp('seen any bar*', '^I haven\'t seen anyone matching') finally: conf.supybot.protocols.irc.strictRfc.setValue(orig) def testSeen(self): self.irc.feedMsg(ircmsgs.join(self.channel, self.irc.nick, prefix=self.prefix)) self.assertNotError('seen last') self.assertNotError('list') self.assertNotError('config plugins.Seen.minimumNonWildcard 2') self.assertError('seen *') self.assertNotError('seen %s' % self.nick) m = self.assertNotError('seen %s' % self.nick.upper()) self.assertTrue(self.nick.upper() in m.args[1]) self.assertRegexp('seen user %s' % self.nick, '^%s was last seen' % self.nick) self.assertNotError('config plugins.Seen.minimumNonWildcard 0') orig = conf.supybot.protocols.irc.strictRfc() try: for state in (True, False): conf.supybot.protocols.irc.strictRfc.setValue(state) for wildcard in self.wildcardTest: self.assertRegexp('seen %s' % wildcard, '^%s was last seen' % self.nick) self.assertRegexp('seen bar*', '^I haven\'t seen anyone matching') finally: conf.supybot.protocols.irc.strictRfc.setValue(orig) def testSeenNoUser(self): self.irc.feedMsg(ircmsgs.join(self.channel, self.irc.nick, prefix=self.prefix)) self.assertNotRegexp('seen user alsdkfjalsdfkj', 'KeyError') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Services/���������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016571� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Services/__init__.py����������������������������������������������������0000644�0001750�0001750�00000004722�13634634532�020701� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Services: Handles management of nicks with NickServ, and ops with ChanServ. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������limnoria-2020.03.17/plugins/Services/config.py������������������������������������������������������0000644�0001750�0001750�00000012357�13634634532�020412� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # Copyright (c) 2010, James McCoy # 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.ircutils as ircutils import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Services') def registerNick(nick, password=''): p = conf.supybot.plugins.Services.Nickserv.get('password') h = _('Determines what password the bot will use with NickServ when ' \ 'identifying as %s.') % nick v = conf.registerNetworkValue(p, nick, registry.String(password, h, private=True)) if password: v.setValue(password) def configure(advanced): from supybot.questions import expect, anything, something, yn, getpass conf.registerPlugin('Services', True) nick = something(_('What is your registered nick?')) password = something(_('What is your password for that nick?')) chanserv = something(_('What is your ChanServ named?'), default='ChanServ') nickserv = something(_('What is your NickServ named?'), default='NickServ') conf.supybot.plugins.Services.nicks.setValue([nick]) conf.supybot.plugins.Services.NickServ.setValue(nickserv) registerNick(nick, password) conf.supybot.plugins.Services.ChanServ.setValue(chanserv) class ValidNickOrEmptyString(registry.String): def setValue(self, v): if v and not ircutils.isNick(v): raise registry.InvalidRegistryValue('Value must be a valid nick or the empty string.') registry.String.setValue(self, v) class ValidNickSet(conf.ValidNicks): List = ircutils.IrcSet Services = conf.registerPlugin('Services') conf.registerNetworkValue(Services, 'nicks', ValidNickSet([], _("""Determines what nicks the bot will use with services."""))) class Networks(registry.SpaceSeparatedSetOfStrings): List = ircutils.IrcSet conf.registerGlobalValue(Services, 'disabledNetworks', Networks(_('QuakeNet').split(), _("""Determines what networks this plugin will be disabled on."""))) conf.registerNetworkValue(Services, 'noJoinsUntilIdentified', registry.Boolean(False, _("""Determines whether the bot will not join any channels until it is identified. This may be useful, for instances, if you have a vhost that isn't set until you're identified, or if you're joining +r channels that won't allow you to join unless you identify."""))) conf.registerNetworkValue(Services, 'ghostDelay', registry.NonNegativeInteger(60, _("""Determines how many seconds the bot will wait between successive GHOST attempts. Set this to 0 to disable GHOST."""))) conf.registerNetworkValue(Services, 'NickServ', ValidNickOrEmptyString('NickServ', _("""Determines what nick the 'NickServ' service has."""))) conf.registerGroup(Services.NickServ, 'password') conf.registerNetworkValue(Services, 'ChanServ', ValidNickOrEmptyString('ChanServ', _("""Determines what nick the 'ChanServ' service has."""))) conf.registerChannelValue(Services.ChanServ, 'password', registry.String('', _("""Determines what password the bot will use with ChanServ."""), private=True)) conf.registerChannelValue(Services.ChanServ, 'op', registry.Boolean(False, _("""Determines whether the bot will request to get opped by the ChanServ when it joins the channel."""))) conf.registerChannelValue(Services.ChanServ, 'halfop', registry.Boolean(False, _("""Determines whether the bot will request to get half-opped by the ChanServ when it joins the channel."""))) conf.registerChannelValue(Services.ChanServ, 'voice', registry.Boolean(False, _("""Determines whether the bot will request to get voiced by the ChanServ when it joins the channel."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Services/locales/�������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�020213� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Services/locales/de.po��������������������������������������������������0000644�0001750�0001750�00000022037�13634634532�021141� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-11-13 18:58+0100\n" "Last-Translator: Florian Besser <fbesser@gmail.com>\n" "Language-Team: German <fbesser@gmail.com>\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" #: config.py:39 msgid "Determines what password the bot will use with NickServ when identifying as %s." msgstr "Legt fest welches Passwort der Bot verwenden wird um sich als %s bei NickServ zu identifizieren." #: config.py:49 msgid "What is your registered nick?" msgstr "Wie ist der registrierte Nick?" #: config.py:50 msgid "What is your password for that nick?" msgstr "Was ist dein Passwort für den Nick?" #: config.py:51 msgid "What is your ChanServ named?" msgstr "Wie heißt der ChanServ?" #: config.py:52 msgid "What is your NickServ named?" msgstr "Wie heißt der NickServ?" #: config.py:70 msgid "" "Determines what nicks the bot will use with\n" " services." msgstr "Legt fest der was für einen Nick der Bot mit den Services nutzt." #: config.py:77 msgid "" "Determines what networks this plugin\n" " will be disabled on." msgstr "Legt fest auf welchen Netzwerken dieses Plugin deaktiviert sein soll." #: config.py:77 msgid "QuakeNet" msgstr "QuakeNet" #: config.py:81 msgid "" "Determines whether the bot will not join any\n" " channels until it is identified. This may be useful, for instances, if\n" " you have a vhost that isn't set until you're identified, or if you're\n" " joining +r channels that won't allow you to join unless you identify." msgstr "Legt fest ob der Bot Kanäle betreten soll, solange er noch nicht identifiziert ist. Das könnte nützlich sein falls du einen vHost hast der erst nach dem Identifizeren gesetzt wird, oder du +R Kanaäle betreten willt, die es nicht erlauben sie zu betreten solange du nicht identifiziert bist." #: config.py:86 msgid "" "Determines how many seconds the bot will\n" " wait between successive GHOST attempts." msgstr "legt fest wie viele Sekunden der Bot zwischen GHOST versuchen wartet." #: config.py:89 msgid "" "Determines what nick the 'NickServ' service\n" " has." msgstr "Legt fest welchen Nick der 'NickServ' Service hat." #: config.py:93 msgid "" "Determines what nick the 'ChanServ' service\n" " has." msgstr "Legt fest welchen Nick der 'ChanServ' Service hat." #: config.py:96 msgid "" "Determines what password the bot will use with\n" " ChanServ." msgstr "Legt fest welches Passwort der Bot für den ChanServ nutzt." #: config.py:99 msgid "" "Determines whether the bot will request to get\n" " opped by the ChanServ when it joins the channel." msgstr "Legt fest ob der Bot Op von ChanServ erfragen soll, sobald er einen Kanal betritt." #: config.py:102 msgid "" "Determines whether the bot will request to get\n" " half-opped by the ChanServ when it joins the channel." msgstr "Legt fest ob der Bot halb-Op von ChanServ erfragen soll, sobald er einen Kanal betritt." #: config.py:105 msgid "" "Determines whether the bot will request to get\n" " voiced by the ChanServ when it joins the channel." msgstr "Legt fest ob der Bot Voice von ChanServ erfragen soll, sobald er einen Kanal betritt." #: plugin.py:47 msgid "" "This plugin handles dealing with Services on networks that provide them.\n" " Basically, you should use the \"password\" command to tell the bot a nick to\n" " identify with and what password to use to identify with that nick. You can\n" " use the password command multiple times if your bot has multiple nicks\n" " registered. Also, be sure to configure the NickServ and ChanServ\n" " configuration variables to match the NickServ and ChanServ nicks on your\n" " network. Other commands such as identify, op, etc. should not be\n" " necessary if the bot is properly configured." msgstr "Die Plugin kümmert sich um die Services, die das Netzwerk anbietet. Du solltest den \"password\" Befehl nutzen um den Bot zu sagen mit welchem Nick er sich identifizieren soll und welches Passwort er verwenden soll um sich zu identifizieren. Du kanns den Passwort Befehl mehrere Male benutzen, falls dein Bot mehrere Nicks registriert hat. Stelle außerdem sicher,dass die NickServ und ChanServ Variablen auf die NickServ und ChanServ Nicks im Netzwerk zutreffen. Andere Befehle, wie identify, op etc., sollten nicht nötig sein falls der Bot richtig konfiguriert ist." #: plugin.py:397 msgid "You must set supybot.plugins.Services.ChanServ before I'm able to send the %s command." msgstr "Du musst supybot.plugins.Services.ChanServ setzen, damit es mir möglich ist den %s Befehl auszuführen." #: plugin.py:403 msgid "" "[<channel>]\n" "\n" " Attempts to get opped by ChanServ in <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<Kanal>]\n" "\n" "Versucht Op durch ChanServ im <Kanal> zu bekommen. <Kanal> ist nur notwendig, falls die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:409 msgid "I'm already opped in %s." msgstr "Ich habe schon Op in %s." #: plugin.py:416 msgid "" "[<channel>]\n" "\n" " Attempts to get voiced by ChanServ in <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<Kanal>]\n" "\n" "Versucht Voice durch ChanServ im <Kanal> zu bekommen. <Kanal> ist nur notwendig, falls die Nachricht nicht im Kanal selbst gesendet wurde." #: plugin.py:422 msgid "I'm already voiced in %s." msgstr "Ich habe schon Voice in %s." #: plugin.py:439 msgid "" "[<channel>]\n" "\n" " Attempts to get unbanned by ChanServ in <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself, but chances\n" " are, if you need this command, you're not sending it in the channel\n" " itself.\n" " " msgstr "" "[<Kanal>]\n" "\n" "Versucht Op durch ChanServ im <Kanal> zu bekommen. <Kanal> ist nur notwendig, falls die Nachricht nicht im Kanal selbst gesendet wurde, wahrscheinlich wird der Befehl sowieso nicht im Kanal selbst gesendet, wenn du den Befehl braucht." #: plugin.py:460 msgid "" "[<channel>]\n" "\n" " Attempts to get invited by ChanServ to <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself, but chances\n" " are, if you need this command, you're not sending it in the channel\n" " itself.\n" " " msgstr "" "[<Kanal>]\n" "\n" "Versucht Op durch ChanServ im <Kanal> zu bekommen. <Kanal> ist nur notwendig, falls die Nachricht nicht im Kanal selbst gesendet wurde, wahrscheinlich wird der Befehl sowieso nicht im Kanal selbst gesendet, wenn du den Befehl braucht." #: plugin.py:481 msgid "" "takes no arguments\n" "\n" " Identifies with NickServ using the current nick.\n" " " msgstr "" "hat keine Argumente\n" "\n" "Identifiziert mit dem NickServ mit dem momentanen Nick." #: plugin.py:490 msgid "I don't have a configured password for my current nick." msgstr "Du hast kein Passwort für meinen momentanen Nick konfiguriert." #: plugin.py:493 msgid "You must set supybot.plugins.Services.NickServ before I'm able to do identify." msgstr "Du musst supybot.plugins.Services.NickServ setzen, damit es mich möglich ist mich zu identifizieren." #: plugin.py:499 msgid "" "[<nick>]\n" "\n" " Ghosts the bot's given nick and takes it. If no nick is given,\n" " ghosts the bot's configured nick and takes it.\n" " " msgstr "" "[<Nick>]\n" "\n" "'Ghost' den Bot Nick und nimmt ihn sich. Falls kein Nick angegeben wirde, wird 'ghost' für den konfigurierten Nick gesendet." #: plugin.py:508 msgid "I cowardly refuse to ghost myself." msgstr "Ich verweigere es auf mich selbst 'ghost' anzuwenden." #: plugin.py:513 msgid "You must set supybot.plugins.Services.NickServ before I'm able to ghost a nick." msgstr "Du musst supybot.plugins.Services.NickServ setzen, erst dann ist es mir möglich 'ghost' auf einen Nick anzuwenden." #: plugin.py:519 msgid "" "<nick> [<password>]\n" "\n" " Sets the NickServ password for <nick> to <password>. If <password> is\n" " not given, removes <nick> from the configured nicks.\n" " " msgstr "" "<Nick> [<Passwort>]\\n" "\n" "Setzt das NickServ Passwort für <Nick> auf <Password>. Falls <Passwort> nicht angegeben wurde, wird der <Nick> aus der Liste der konfigurierten Nicks entfernt." #: plugin.py:529 msgid "That nick was not configured with a password." msgstr "Für diesen Nick wurde kein Passwort konfiguriert." #: plugin.py:540 msgid "" "takes no arguments\n" "\n" " Returns the nicks that this plugin is configured to identify and ghost\n" " with.\n" " " msgstr "" "hat keine Argumente \n" "\n" "Gibt die Nicks aus, für die dieses Plugin konfiguriert wurde um sich zu identifizieren und 'ghost' zu benutzen." #: plugin.py:550 msgid "I'm not currently configured for any nicks." msgstr "Ich habe zur Zeit keine Nicks konfiguriert." �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Services/locales/fi.po��������������������������������������������������0000644�0001750�0001750�00000022745�13634634532�021155� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Services plugin in Limnoria. # Copyright (C) 2011 Limnoria # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: \n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-08-13 23:02+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\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" #: config.py:39 msgid "Determines what password the bot will use with NickServ when identifying as %s." msgstr "Määrittää mitä salasanaa botti käyttää, kun tunnistautuu %s:ksi NickServillä." #: config.py:49 msgid "What is your registered nick?" msgstr "Mikä on rekisteröity nimimerkkisi?" #: config.py:50 msgid "What is your password for that nick?" msgstr "Mikä on salasanasi tuolle nimimerkille?" #: config.py:51 msgid "What is your ChanServ named?" msgstr "Minkä niminen ChanServisi on?" #: config.py:52 msgid "What is your NickServ named?" msgstr "Minkä niminen NickServisi on?" #: config.py:70 msgid "" "Determines what nicks the bot will use with\n" " services." msgstr "" "Määrittää mitä nimimerkkiä botti käyttää\n" " palvelujen kanssa." #: config.py:77 msgid "" "Determines what networks this plugin\n" " will be disabled on." msgstr "" "Määrittää missä verkoissa tämä\n" " lisäosa on poistettu käytöstä." #: config.py:77 msgid "QuakeNet" msgstr "QuakeNet" #: config.py:81 msgid "" "Determines whether the bot will not join any\n" " channels until it is identified. This may be useful, for instances, if\n" " you have a vhost that isn't set until you're identified, or if you're\n" " joining +r channels that won't allow you to join unless you identify." msgstr "" "Määrittää liittyykö botti millekään kanavalle ennen\n" " kuin se on tunnistautunut. Tämä voi olla hyödyllinen, esimerkiksi, jos\n" " sinulla on vhost, jota ei aseteta ennen kuin tunnistaudut, tai jos olet\n" " liittymässä +r kanaville, jotka eivät salli sinun liittyvän, paitsi jos olet tunnistautunut." #: config.py:86 msgid "" "Determines how many seconds the bot will\n" " wait between successive GHOST attempts." msgstr "" "Määrittää monta sekuntia botti odottaa\n" " GHOSTaus yritysten välillä." #: config.py:89 msgid "" "Determines what nick the 'NickServ' service\n" " has." msgstr "" "Määrittää mikä nimimerkki 'NickServ' palvelulla\n" " on." #: config.py:93 msgid "" "Determines what nick the 'ChanServ' service\n" " has." msgstr "" "Määrittää mikä nimimerkki 'ChanServ' palvelulla\n" " on." #: config.py:96 msgid "" "Determines what password the bot will use with\n" " ChanServ." msgstr "" "Määrittää mitä salasanaa botti käyttää\n" " ChanServin kanssa." #: config.py:99 msgid "" "Determines whether the bot will request to get\n" " opped by the ChanServ when it joins the channel." msgstr "" "Määrittää pyytääkö botti ChanServin oppaamaksi\n" " tulemista, kun se liittyy kanavalle." #: config.py:102 msgid "" "Determines whether the bot will request to get\n" " half-opped by the ChanServ when it joins the channel." msgstr "" "Määrittää pyytääkö botti tulla ChanServin puolioppaamaksi, kun\n" " se liittyy kanavalle." #: config.py:105 msgid "" "Determines whether the bot will request to get\n" " voiced by the ChanServ when it joins the channel." msgstr "" "Määrittää pyytääkö botti ChanServiltä äänen, kun\n" " se liittyy kanavalle." #: plugin.py:47 msgid "" "This plugin handles dealing with Services on networks that provide them.\n" " Basically, you should use the \"password\" command to tell the bot a nick to\n" " identify with and what password to use to identify with that nick. You can\n" " use the password command multiple times if your bot has multiple nicks\n" " registered. Also, be sure to configure the NickServ and ChanServ\n" " configuration variables to match the NickServ and ChanServ nicks on your\n" " network. Other commands such as identify, op, etc. should not be\n" " necessary if the bot is properly configured." msgstr "" "Tämä lisäosa hoitaa palvelut verkoissa, jotka tarjoavat niitä.\n" " Perusteellisesti, sinun pitäisi käyttää komentoa \"password\" kertoaksesi botille\n" " millä salasanalla ja tunnuksella se tunnistautuu. Voit käyttää\n" " salasanakomentoa monta kertaa, mikäli botillasi on monta rekisteröityä\n" " nimimerkkiä. Varmista myös, että asetusarvot täsmäävät\n" " NickServin ja ChanServin nimimerkkeihin verkossasi.\n" " Muiden komentojen, kuten identify, op, jne. ei\n" " pitäisi olla vaadittuja, jos botti on määritetty hyvin." #: plugin.py:397 msgid "You must set supybot.plugins.Services.ChanServ before I'm able to send the %s command." msgstr "Sinun täytyy asettaa supybot.plugins.Services.ChanServ ennen kuin pystyn lähettämään komennon %s." #: plugin.py:403 msgid "" "[<channel>]\n" "\n" " Attempts to get opped by ChanServ in <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>]\n" "\n" " Yrittää päästä ChanServin oppaamaksi <kanavalla>. <Kanava> on vaadittu vain,\n" " jos viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:409 msgid "I'm already opped in %s." msgstr "Minut on jo opattu kanavalla %s." #: plugin.py:416 msgid "" "[<channel>]\n" "\n" " Attempts to get voiced by ChanServ in <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>]\n" "\n" " Yrittää saada ChanServiltä äänen <kanavalla>. <Kanava> on vaadittu vain, jos\n" " viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:422 msgid "I'm already voiced in %s." msgstr "Minulla on jo ääni kanavalla %s." #: plugin.py:439 msgid "" "[<channel>]\n" "\n" " Attempts to get unbanned by ChanServ in <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself, but chances\n" " are, if you need this command, you're not sending it in the channel\n" " itself.\n" " " msgstr "" "[<kanava>]\n" "\n" " Yrittää poistaa porttikiellon <kanavalta> käyttämällä ChanServiä. <Kanava> on vaadittu vain, jos\n" " viestiä ei lähetetä kanavalla itsellään, mutta luultavast jos tarvitset tätä komentoa, et ole kanavallai\n" " itsellään.\n" " " #: plugin.py:460 msgid "" "[<channel>]\n" "\n" " Attempts to get invited by ChanServ to <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself, but chances\n" " are, if you need this command, you're not sending it in the channel\n" " itself.\n" " " msgstr "" "[<channel>]\n" "\n" " Yrittää saada ChanServiltä kutsun <kanavalle>. <Kanava> on vaadittu vain,\n" " jos viestiä ei lähetetä kanavalla itsellään, mutta luultavasti\n" " jos tarvitset tätä komentoa, et lähetä sitä kanavalla\n" " itsellään.\n" " " #: plugin.py:481 msgid "" "takes no arguments\n" "\n" " Identifies with NickServ using the current nick.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Tunnistautuu NickServille käyttämällä nykyistä nimimerkkiä.\n" " " #: plugin.py:490 msgid "I don't have a configured password for my current nick." msgstr "Minulla ei ole määritettyä salasanaa nimimerkilleni." #: plugin.py:493 msgid "You must set supybot.plugins.Services.NickServ before I'm able to do identify." msgstr "Sinun täytyy asettaa supybot.plugins.Services.NickServ ennen kuin pystyn tunnistautumaan." #: plugin.py:499 msgid "" "[<nick>]\n" "\n" " Ghosts the bot's given nick and takes it. If no nick is given,\n" " ghosts the bot's configured nick and takes it.\n" " " msgstr "" "[<nimimerkki>]\n" "\n" " Ghostaa botin annetun nimimerkin ja ottaa sen. Jos nimimerkkiä ei ole annettu,\n" " ghostaa botin määritetyn nimimerkin ja ottaa sen.\n" " " #: plugin.py:508 msgid "I cowardly refuse to ghost myself." msgstr "Minä pelkurimaisesti kieltäydyn ghostaamasta itseäni." #: plugin.py:513 msgid "You must set supybot.plugins.Services.NickServ before I'm able to ghost a nick." msgstr "Sinun täytyy asettaa supybot.plugins.Services.NickServ ennen kuin pystyn ghostaamaan nimimerkin." #: plugin.py:519 msgid "" "<nick> [<password>]\n" "\n" " Sets the NickServ password for <nick> to <password>. If <password> is\n" " not given, removes <nick> from the configured nicks.\n" " " msgstr "" "<nimimerkki> [<salasana>]\n" "\n" " Asettaa NickServin salasanan <nimimerkille> <salasanaksi>. Jos <salasana> ei\n" " ole annettu, poistaa <nimimerkin> määritetyistä nimimerkeistä.\n" " " #: plugin.py:529 msgid "That nick was not configured with a password." msgstr "Tuota nimimerkkiä ei ole määritetty salasanan kanssa." #: plugin.py:540 msgid "" "takes no arguments\n" "\n" " Returns the nicks that this plugin is configured to identify and ghost\n" " with.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Palauttaa nimimerkin, jolla tämä lisäosa on määritetty tunnistautumaan ja\n" " ghostaamaan.\n" " " #: plugin.py:550 msgid "I'm not currently configured for any nicks." msgstr "Minulle ei ole tällä hetkellä määritetty yhtään nimimerkkiä." ���������������������������limnoria-2020.03.17/plugins/Services/locales/fr.po��������������������������������������������������0000644�0001750�0001750�00000021625�13634634532�021162� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2010-10-20 08:55+CEST\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\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" #: config.py:39 msgid "Determines what password the bot will use with NickServ when identifying as %s." msgstr "Détermine quel mot de passe le bot utilisera pour s'identifier à NickServ lors de l'identification à %s." #: config.py:49 msgid "What is your registered nick?" msgstr "Quel est votre nick enregistré ?" #: config.py:50 msgid "What is your password for that nick?" msgstr "Quel est votre mot de passe pour ce nick ?" #: config.py:51 msgid "What is your ChanServ named?" msgstr "Comment est nommé ChanServ ?" #: config.py:52 msgid "What is your NickServ named?" msgstr "Comment est nommé NickServ ?" #: config.py:70 msgid "" "Determines what nicks the bot will use with\n" " services." msgstr "Détermine quels nicks le bot utilisera avec les services." #: config.py:77 msgid "" "Determines what networks this plugin\n" " will be disabled on." msgstr "Détermine sur quels réseaux ce plugin sera désactivé." #: config.py:77 msgid "QuakeNet" msgstr "QuakeNet" #: config.py:81 msgid "" "Determines whether the bot will not join any\n" " channels until it is identified. This may be useful, for instances, if\n" " you have a vhost that isn't set until you're identified, or if you're\n" " joining +r channels that won't allow you to join unless you identify." msgstr "Détermine si le bot peut rejoindre un ou des canal(aux) avant d'être identifié. Ce peut être utilise, par exemple si vous utilisez une vhost qui n'est activée qu'une fois que vous êtes identifié, ou encore si vous devez rejoindre des canaux en +r (=des canaux que l'on ne peut joindre sans être identifié)" #: config.py:86 msgid "" "Determines how many seconds the bot will\n" " wait between successive GHOST attempts." msgstr "Détermine le nombre de secondes entre deux tentatives successives de GHOST." #: config.py:89 msgid "" "Determines what nick the 'NickServ' service\n" " has." msgstr "Détermine quel nick NickServ a." #: config.py:93 msgid "" "Determines what nick the 'ChanServ' service\n" " has." msgstr "Détermine quel nick ChanServ a." #: config.py:96 msgid "" "Determines what password the bot will use with\n" " ChanServ." msgstr "Détermine quel mot de passe le bot utilisera avec ChanServ." #: config.py:99 msgid "" "Determines whether the bot will request to get\n" " opped by the ChanServ when it joins the channel." msgstr "Détermine si le bot demandera à être oppé par ChanServ lorsqu'il rejoint le canal." #: config.py:102 msgid "" "Determines whether the bot will request to get\n" " half-opped by the ChanServ when it joins the channel." msgstr "Détermine si le bot demandera à être oppé par NickServ lorsqu'il rejoint le canal." #: config.py:105 msgid "" "Determines whether the bot will request to get\n" " voiced by the ChanServ when it joins the channel." msgstr "Détermine si le bot demandera à être voicé par ChanServ lorsqu'il rejoint le canal." #: plugin.py:48 msgid "" "This plugin handles dealing with Services on networks that provide them.\n" " Basically, you should use the \"password\" command to tell the bot a nick to\n" " identify with and what password to use to identify with that nick. You can\n" " use the password command multiple times if your bot has multiple nicks\n" " registered. Also, be sure to configure the NickServ and ChanServ\n" " configuration variables to match the NickServ and ChanServ nicks on your\n" " network. Other commands such as identify, op, etc. should not be\n" " necessary if the bot is properly configured." msgstr "Ce plugin gère les services sur les réseaux qui en fournissent. En général, vous pouvez utiliser la commande 'password' pour dire au bot de prendre un nick et de s'identifier avec le mot de passe. Vous pouvez utiliser la commande 'password' autant de fois que le bot a de nicks enregistrés. Aussi, assurez-vous de configurer les variables NickServ et ChanServ pour correspondre à leurs nicks respectifs sur le réseau en question. D'autres commandes, comme 'identify', 'op', ... ne sont pas nécessaires quand le bot est configuré correctement." #: plugin.py:396 msgid "You must set supybot.plugins.Services.ChanServ before I'm able to send the %s command." msgstr "vous devez définir supybot.plugins.Services.ChanServ avant que je ne puisse envoyer la commande %s" #: plugin.py:402 msgid "" "[<channel>]\n" "\n" " Attempts to get opped by ChanServ in <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>]\n" "\n" "Demande à être opé par ChanServ sur le <canal>. <canal> n'est nécessaire que si la commande n'est pas envoyée sur le canal lui-même." #: plugin.py:408 msgid "I'm already opped in %s." msgstr "Je suis déjà opé sur %s." #: plugin.py:415 msgid "" "[<channel>]\n" "\n" " Attempts to get voiced by ChanServ in <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>]\n" "\n" " Demande à être voicé par ChanServ sur le <canal>. <canal> n'est nécessaire que si la commande n'est pas envoyée sur le canal lui-même." #: plugin.py:421 msgid "I'm already voiced in %s." msgstr "Je suis déjà voicé sur %s." #: plugin.py:438 msgid "" "[<channel>]\n" "\n" " Attempts to get unbanned by ChanServ in <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself, but chances\n" " are, if you need this command, you're not sending it in the channel\n" " itself.\n" " " msgstr "" "[<canal>]\n" "\n" "Cherche à être débanni par ChanServ sur le <canal>. <canal> n'est nécessaire que si la commande n'est pas envoyée sur le canal lui-même, mais, vous n'avez probablement pas besoin d'utiliser cette commande sur le canal en question." #: plugin.py:459 msgid "" "[<channel>]\n" "\n" " Attempts to get invited by ChanServ to <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself, but chances\n" " are, if you need this command, you're not sending it in the channel\n" " itself.\n" " " msgstr "" "[<canal>]\n" "\n" "Cherche à être invité par ChanServ sur le <canal>. <canal> n'est nécessaire que si la commande n'est pas envoyée sur le canal lui-même, mais, vous n'avez probablement pas besoin d'utiliser cette commande sur le canal en question." #: plugin.py:480 msgid "" "takes no arguments\n" "\n" " Identifies with NickServ using the current nick.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "S'identifie auprès de NickServ, en utilisant le nick actuel." #: plugin.py:489 msgid "I don't have a configured password for my current nick." msgstr "Je n'ai pas de mot de passe configuré pour mon nick actuel." #: plugin.py:492 msgid "You must set supybot.plugins.Services.NickServ before I'm able to do identify." msgstr "Vous devez définir supybot.plugins.Services.NickServ avant que je ne puisse m'identifier" #: plugin.py:498 msgid "" "[<nick>]\n" "\n" " Ghosts the bot's given nick and takes it. If no nick is given,\n" " ghosts the bot's configured nick and takes it.\n" " " msgstr "" "[<nick>]\n" "\n" "Ghost le nick donné du bot et le prend. Si aucun nick n'est donné, utilise celui configuré." #: plugin.py:507 msgid "I cowardly refuse to ghost myself." msgstr "Je suis trop couard pour me ghoster moi-même." #: plugin.py:512 msgid "You must set supybot.plugins.Services.NickServ before I'm able to ghost a nick." msgstr "Vous devez définir supybot.plugins.Services.NickServ avant que je ne puisse ghoster un nick." #: plugin.py:518 msgid "" "<nick> [<password>]\n" "\n" " Sets the NickServ password for <nick> to <password>. If <password> is\n" " not given, removes <nick> from the configured nicks.\n" " " msgstr "" "<nick> [<mot de passe>]\n" "\n" "Défini le <mot de passe> NickServ pour le <nick>. Si le <mot de passe> n'est pas donné, supprime <nick> de la liste des nis, configurés." #: plugin.py:528 msgid "That nick was not configured with a password." msgstr "Ce nick n'est pas configuré avec un mot de passe." #: plugin.py:539 msgid "" "takes no arguments\n" "\n" " Returns the nicks that this plugin is configured to identify and ghost\n" " with.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne les nicks pour lesquels ce plugin est configuré pour s'identifier et ghoster." #: plugin.py:549 msgid "I'm not currently configured for any nicks." msgstr "Je ne suis actuellement configuré pour aucun nick." �����������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Services/locales/it.po��������������������������������������������������0000644�0001750�0001750�00000022364�13634634532�021170� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-07-10 12:57+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:39 msgid "Determines what password the bot will use with NickServ when identifying as %s." msgstr "Determina quale password il bot userà con NickServ per identificarsi come %s." #: config.py:49 msgid "What is your registered nick?" msgstr "Qual è il tuo nick registrato?" #: config.py:50 msgid "What is your password for that nick?" msgstr "Qual è la password per questo nick?" #: config.py:51 msgid "What is your ChanServ named?" msgstr "Come si chiama ChanServ?" #: config.py:52 msgid "What is your NickServ named?" msgstr "Come si chiama NickServ?" #: config.py:70 msgid "" "Determines what nicks the bot will use with\n" " services." msgstr "Determina quali nick il bot userà con i servizi." #: config.py:77 msgid "" "Determines what networks this plugin\n" " will be disabled on." msgstr "Determina su quali reti sarà disattivato questo plugin." #: config.py:77 msgid "QuakeNet" msgstr "QuakeNet" #: config.py:81 msgid "" "Determines whether the bot will not join any\n" " channels until it is identified. This may be useful, for instances, if\n" " you have a vhost that isn't set until you're identified, or if you're\n" " joining +r channels that won't allow you to join unless you identify." msgstr "" "Determina se il bot non entrerà in alcun canale finché non è identificato.\n" " Può essere utile, ad esempio, se si utilizza un vhost che non viene\n" " impostato fino a che non ci si identifica oppure se si necessita di\n" " entrare in un canale con un mode +r (su Freenode, +j su Azzurra) che\n" " non permette il join a utenti non identificati." #: config.py:86 msgid "" "Determines how many seconds the bot will\n" " wait between successive GHOST attempts." msgstr "Determina quanti secondi il bot aspetterà tra un tentativo di GHOST e l'altro." #: config.py:89 msgid "" "Determines what nick the 'NickServ' service\n" " has." msgstr "Determina quale nick ha il servizio \"NickServ\"." #: config.py:93 msgid "" "Determines what nick the 'ChanServ' service\n" " has." msgstr "Determina quale nick ha il servizio \"ChanServ\"." #: config.py:96 msgid "" "Determines what password the bot will use with\n" " ChanServ." msgstr "Determina quale password il bot userà con ChanServ." #: config.py:99 msgid "" "Determines whether the bot will request to get\n" " opped by the ChanServ when it joins the channel." msgstr "Determina se il bot richiederà lo stato di op da ChanServ quando entra nel canale." #: config.py:102 msgid "" "Determines whether the bot will request to get\n" " half-opped by the ChanServ when it joins the channel." msgstr "Determina se il bot richiederà lo stato di halfop da ChanServ quando entra nel canale." #: config.py:105 msgid "" "Determines whether the bot will request to get\n" " voiced by the ChanServ when it joins the channel." msgstr "Determina se il bot richiederà il voice da ChanServ quando entra nel canale." #: plugin.py:47 #, docstring msgid "" "This plugin handles dealing with Services on networks that provide them.\n" " Basically, you should use the \"password\" command to tell the bot a nick to\n" " identify with and what password to use to identify with that nick. You can\n" " use the password command multiple times if your bot has multiple nicks\n" " registered. Also, be sure to configure the NickServ and ChanServ\n" " configuration variables to match the NickServ and ChanServ nicks on your\n" " network. Other commands such as identify, op, etc. should not be\n" " necessary if the bot is properly configured." msgstr "" "Questo plugin gestisce i servizi su reti che li forniscono. Fondamentalmente\n" " va usato il comando \"password\" per fornire al bot un nick con il quale\n" " identificarsi e quale password usare; se il bot ha più nick registrati, è\n" " possibile usare tale comando più volte. Assicurati di impostare le variabili\n" " di configurazione NickServ e ChanServ in modo che corrispondano ai rispettivi\n" " nick sulla rete in questione. Se il bot è ben configurato, gli altri comandi\n" " come \"identify\", \"op\", ecc. non dovrebbero essere necessari." #: plugin.py:397 msgid "You must set supybot.plugins.Services.ChanServ before I'm able to send the %s command." msgstr "È necessario impostare la variabile supybot.plugins.Services.ChanServ affinché possa inviare il comando %s." #: plugin.py:403 #, docstring msgid "" "[<channel>]\n" "\n" " Attempts to get opped by ChanServ in <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>]\n" "\n" " Tenta di ricevere lo stato di op da ChanServ in <canale>. <canale>\n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:409 msgid "I'm already opped in %s." msgstr "Sono già op in %s." #: plugin.py:416 #, docstring msgid "" "[<channel>]\n" "\n" " Attempts to get voiced by ChanServ in <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>]\n" "\n" " Tenta di ricevere il voice da ChanServ in <canale>. <canale> è\n" " necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:422 msgid "I'm already voiced in %s." msgstr "Ho già il voice in %s." #: plugin.py:439 #, docstring msgid "" "[<channel>]\n" "\n" " Attempts to get unbanned by ChanServ in <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself, but chances\n" " are, if you need this command, you're not sending it in the channel\n" " itself.\n" " " msgstr "" "[<canale>]\n" "\n" " Tenta di farsi rimuovere il ban da ChanServ in <canale>. <canale> è\n" " necessario solo se il messaggio non viene inviato nel canale stesso, ma è\n" " probabile che se serve questo comando non lo si stia inviando in canale.\n" " " #: plugin.py:460 #, docstring msgid "" "[<channel>]\n" "\n" " Attempts to get invited by ChanServ to <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself, but chances\n" " are, if you need this command, you're not sending it in the channel\n" " itself.\n" " " msgstr "" "[<canale>]\n" "\n" " Tenta di essere invitato da ChanServ in <canale>. <canale> è necessario\n" " solo se il messaggio non viene inviato nel canale stesso, ma è probabile\n" " che se serve questo comando non lo si stia inviando in canale.\n" " " #: plugin.py:481 #, docstring msgid "" "takes no arguments\n" "\n" " Identifies with NickServ using the current nick.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Si identifica con NickServ utilizzando l'attuale nick in uso.\n" " " #: plugin.py:490 msgid "I don't have a configured password for my current nick." msgstr "Non ho una password configurata per il mio attuale nick." #: plugin.py:493 msgid "You must set supybot.plugins.Services.NickServ before I'm able to do identify." msgstr "È necessario impostare la variabile supybot.plugins.Services.NickServ affinché possa identificarmi." #: plugin.py:499 #, docstring msgid "" "[<nick>]\n" "\n" " Ghosts the bot's given nick and takes it. If no nick is given,\n" " ghosts the bot's configured nick and takes it.\n" " " msgstr "" "[<nick>]\n" "\n" " Termina una connessione \"fantasma\" (ghost) e riprende il nick del bot.\n" " Se non è specificato alcun nick, riprende quello configurato.\n" " " #: plugin.py:508 msgid "I cowardly refuse to ghost myself." msgstr "Codardamente mi rifiuto di terminare una connessione \"fantasma\" (ghost)." #: plugin.py:513 msgid "You must set supybot.plugins.Services.NickServ before I'm able to ghost a nick." msgstr "È necessario impostare la variabile supybot.plugins.Services.NickServ affinché possa riprendermi il nick (ghost)." #: plugin.py:519 #, docstring msgid "" "<nick> [<password>]\n" "\n" " Sets the NickServ password for <nick> to <password>. If <password> is\n" " not given, removes <nick> from the configured nicks.\n" " " msgstr "" "<nick> [<password>]\n" "\n" " Imposta la password di NickServ per <nick>. Se <password>\n" " non è specificata, rimuove <nick> dai nick configurati.\n" " " #: plugin.py:529 msgid "That nick was not configured with a password." msgstr "Questo nick non è stato configurato con una password." #: plugin.py:540 #, docstring msgid "" "takes no arguments\n" "\n" " Returns the nicks that this plugin is configured to identify and ghost\n" " with.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Restituisce i nick per i quali questo plugin è configurato per l'identificazione e il ghost.\n" " " #: plugin.py:550 msgid "I'm not currently configured for any nicks." msgstr "Al momento non sono configurato per alcun nick." ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Services/plugin.py������������������������������������������������������0000644�0001750�0001750�00000062056�13634634532�020444� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2010, James McCoy # 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 re import time from . import config import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.schedule as schedule import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Services') class Services(callbacks.Plugin): """This plugin handles dealing with Services on networks that provide them. Basically, you should use the "password" command to tell the bot a nick to identify with and what password to use to identify with that nick. You can use the password command multiple times if your bot has multiple nicks registered. Also, be sure to configure the NickServ and ChanServ configuration variables to match the NickServ and ChanServ nicks on your network. Other commands such as identify, op, etc. should not be necessary if the bot is properly configured.""" def __init__(self, irc): self.__parent = super(Services, self) self.__parent.__init__(irc) for nick in self.registryValue('nicks', network=irc.network): config.registerNick(nick) self.reset() def reset(self): self.channels = [] self.sentGhost = None self.identified = False self.waitingJoins = {} def disabled(self, irc): disabled = self.registryValue('disabledNetworks') if irc.network in disabled or \ irc.state.supported.get('NETWORK', '') in disabled: return True return False def outFilter(self, irc, msg): if msg.command == 'JOIN' and not self.disabled(irc): if not self.identified: if self.registryValue('noJoinsUntilIdentified', network=irc.network): self.log.info('Holding JOIN to %s @ %s until identified.', msg.channel, irc.network) self.waitingJoins.setdefault(irc.network, []) self.waitingJoins[irc.network].append(msg) return None return msg def _getNick(self, network): network_nick = conf.supybot.networks.get(network).nick() if network_nick == '': return conf.supybot.nick() else: return network_nick def _getNickServPassword(self, nick, network): # This should later be nick-specific. assert nick in self.registryValue('nicks', network=network) return self.registryValue('NickServ.password.%s' % nick, network=network) def _setNickServPassword(self, nick, password, network): # This also should be nick-specific. assert nick in self.registryValue('nicks', network=network) self.setRegistryValue('NickServ.password.%s' % nick, password, network=network) def _doIdentify(self, irc, nick=None): if self.disabled(irc): return if nick is None: nick = self._getNick(irc.network) if nick not in self.registryValue('nicks', network=irc.network): return nickserv = self.registryValue('NickServ', network=irc.network) password = self._getNickServPassword(nick, irc.network) if not nickserv or not password: s = 'Tried to identify without a NickServ or password set.' self.log.warning(s) return assert ircutils.strEqual(irc.nick, nick), \ 'Identifying with not normal nick.' self.log.info('Sending identify (current nick: %s)', irc.nick) identify = 'IDENTIFY %s' % password # It's important that this next statement is irc.sendMsg, not # irc.queueMsg. We want this message to get through before any # JOIN messages also being sent on 376. irc.sendMsg(ircmsgs.privmsg(nickserv, identify)) def _doGhost(self, irc, nick=None): if self.disabled(irc): return if nick is None: nick = self._getNick(irc.network) if nick not in self.registryValue('nicks', network=irc.network): return nickserv = self.registryValue('NickServ', network=irc.network) password = self._getNickServPassword(nick, irc.network) ghostDelay = self.registryValue('ghostDelay', network=irc.network) if not ghostDelay: return if not nickserv or not password: s = 'Tried to ghost without a NickServ or password set.' self.log.warning(s) return if self.sentGhost and time.time() < (self.sentGhost + ghostDelay): self.log.warning('Refusing to send GHOST more than once every ' '%s seconds.' % ghostDelay) elif not password: self.log.warning('Not ghosting: no password set.') return else: self.log.info('Sending ghost (current nick: %s; ghosting: %s)', irc.nick, nick) ghost = 'GHOST %s %s' % (nick, password) # Ditto about the sendMsg (see _doIdentify). irc.sendMsg(ircmsgs.privmsg(nickserv, ghost)) self.sentGhost = time.time() def __call__(self, irc, msg): self.__parent.__call__(irc, msg) if self.disabled(irc): return nick = self._getNick(irc.network) if nick not in self.registryValue('nicks', network=irc.network): return nickserv = self.registryValue('NickServ', network=irc.network) password = self._getNickServPassword(nick, irc.network) ghostDelay = self.registryValue('ghostDelay', network=irc.network) if not ghostDelay: return if nick and nickserv and password and \ not ircutils.strEqual(nick, irc.nick): if irc.afterConnect and (self.sentGhost is None or (self.sentGhost + ghostDelay) < time.time()): if nick in irc.state.nicksToHostmasks: self._doGhost(irc) else: irc.sendMsg(ircmsgs.nick(nick)) # 433 is handled elsewhere. def do001(self, irc, msg): # New connection, make sure sentGhost is False. self.sentGhost = None def do376(self, irc, msg): if self.disabled(irc): return nick = self._getNick(irc.network) if nick not in self.registryValue('nicks', network=irc.network): return nickserv = self.registryValue('NickServ', network=irc.network) if not nickserv: self.log.warning('NickServ is unset, cannot identify.') return password = self._getNickServPassword(nick, irc.network) if not password: self.log.warning('Password for %s is unset, cannot identify.',nick) return if not nick: self.log.warning('Cannot identify without a nick being set. ' 'Set supybot.plugins.Services.nick.') return if ircutils.strEqual(irc.nick, nick): self._doIdentify(irc) else: self._doGhost(irc) do422 = do377 = do376 def do433(self, irc, msg): if self.disabled(irc): return nick = self._getNick(irc.network) if nick not in self.registryValue('nicks', network=irc.network): return if nick and irc.afterConnect: password = self._getNickServPassword(nick, irc.network) if not password: return self._doGhost(irc) def do515(self, irc, msg): # Can't join this channel, it's +r (we must be identified). self.channels.append(msg.args[1]) def doNick(self, irc, msg): nick = self._getNick(irc.network) if ircutils.strEqual(msg.args[0], irc.nick) and \ ircutils.strEqual(irc.nick, nick): self._doIdentify(irc) elif ircutils.strEqual(msg.nick, nick): irc.sendMsg(ircmsgs.nick(nick)) def _ghosted(self, irc, s): nick = self._getNick(irc.network) lowered = s.lower() return bool('killed' in lowered and (nick in s or 'ghost' in lowered)) def doNotice(self, irc, msg): if irc.afterConnect: nickserv = self.registryValue('NickServ', network=irc.network) chanserv = self.registryValue('ChanServ', network=irc.network) if nickserv and ircutils.strEqual(msg.nick, nickserv): self.doNickservNotice(irc, msg) elif chanserv and ircutils.strEqual(msg.nick, chanserv): self.doChanservNotice(irc, msg) _chanRe = re.compile('\x02(#.*?)\x02') def doChanservNotice(self, irc, msg): if self.disabled(irc): return s = msg.args[1].lower() channel = None m = self._chanRe.search(s) networkGroup = conf.supybot.networks.get(irc.network) on = 'on %s' % irc.network if m is not None: channel = m.group(1) if 'all bans' in s or 'unbanned from' in s or \ ('unbanned %s' % irc.nick.lower()) in \ ircutils.stripFormatting(s): # All bans removed (old freenode?) # You have been unbanned from (oftc, anope) # "Unbanned \x02someuser\x02 from \x02#channel\x02 (\x02N\x02 # ban(s) removed)" (atheme 7.x) irc.sendMsg(networkGroup.channels.join(channel)) elif 'isn\'t registered' in s: self.log.warning('Received "%s isn\'t registered" from ChanServ %s', channel, on) elif 'this channel has been registered' in s: self.log.debug('Got "Registered channel" from ChanServ %s.', on) elif 'already opped' in s: # This shouldn't happen, Services.op should refuse to run if # we already have ops. self.log.debug('Got "Already opped" from ChanServ %s.', on) elif 'access level' in s and 'is required' in s: self.log.warning('Got "Access level required" from ChanServ %s.', on) elif 'inviting' in s: self.log.debug('Got "Inviting to channel" from ChanServ %s.', on) elif s.startswith('['): chanTypes = irc.state.supported['CHANTYPES'] if re.match(r'^\[[%s]' % re.escape(chanTypes), s): self.log.debug('Got entrymsg from ChanServ %s.', on) elif irc.isChannel(msg.args[0]): # Atheme uses channel-wide notices for alerting channel access # changes if the FANTASY or VERBOSE setting is on; we can suppress # these 'unexpected notice' warnings since they're not really # important. pass else: self.log.warning('Got unexpected notice from ChanServ %s: %r.', on, msg) def doNickservNotice(self, irc, msg): if self.disabled(irc): return nick = self._getNick(irc.network) s = ircutils.stripFormatting(msg.args[1].lower()) on = 'on %s' % irc.network networkGroup = conf.supybot.networks.get(irc.network) if 'incorrect' in s or 'denied' in s: log = 'Received "Password Incorrect" from NickServ %s. ' \ 'Resetting password to empty.' % on self.log.warning(log) self.sentGhost = time.time() self._setNickServPassword(nick, '', irc.network) elif self._ghosted(irc, s): self.log.info('Received "GHOST succeeded" from NickServ %s.', on) self.sentGhost = None self.identified = False irc.queueMsg(ircmsgs.nick(nick)) elif 'is not registered' in s: self.log.info('Received "Nick not registered" from NickServ %s.', on) elif 'currently' in s and 'isn\'t' in s or 'is not' in s: # The nick isn't online, let's change our nick to it. self.sentGhost = None irc.queueMsg(ircmsgs.nick(nick)) elif ('owned by someone else' in s) or \ ('nickname is registered and protected' in s) or \ ('nick belongs to another user' in s): # freenode, arstechnica, chatjunkies # oftc, zirc.org # sorcery self.log.info('Received "Registered nick" from NickServ %s.', on) elif '/msg' in s and 'id' in s and 'password' in s: # Usage info for identify command; ignore. self.log.debug('Got usage info for identify command %s.', on) elif ('please choose a different nick' in s): # oftc, part 3 # This is a catch-all for redundant messages from nickserv. pass elif ('now recognized' in s) or \ ('already identified' in s) or \ ('already logged in' in s) or \ ('successfully identified' in s) or \ ('password accepted' in s) or \ ('now identified' in s): # freenode, oftc, arstechnica, zirc, .... # sorcery self.log.info('Received "Password accepted" from NickServ %s.', on) self.identified = True for channel in irc.state.channels.keys(): self.checkPrivileges(irc, channel) for channel in self.channels: irc.queueMsg(networkGroup.channels.join(channel)) waitingJoins = self.waitingJoins.pop(irc.network, None) if waitingJoins: for m in waitingJoins: irc.sendMsg(m) elif 'not yet authenticated' in s: # zirc.org has this, it requires an auth code. email = s.split()[-1] self.log.warning('Received "Nick not yet authenticated" from ' 'NickServ %s. Check email at %s and send the ' 'auth command to NickServ.', on, email) else: self.log.info('Received notice from NickServ %s: %q.', on, ircutils.stripFormatting(msg.args[1])) def checkPrivileges(self, irc, channel): if self.disabled(irc): return chanserv = self.registryValue('ChanServ', network=irc.network) on = 'on %s' % irc.network if chanserv and self.registryValue('ChanServ.op', channel, irc.network): if irc.nick not in irc.state.channels[channel].ops: self.log.info('Requesting op from %s in %s %s.', chanserv, channel, on) irc.sendMsg(ircmsgs.privmsg(chanserv, 'op %s' % channel)) if chanserv and self.registryValue('ChanServ.halfop', channel, irc.network): if irc.nick not in irc.state.channels[channel].halfops: self.log.info('Requesting halfop from %s in %s %s.', chanserv, channel, on) irc.sendMsg(ircmsgs.privmsg(chanserv, 'halfop %s' % channel)) if chanserv and self.registryValue('ChanServ.voice', channel, irc.network): if irc.nick not in irc.state.channels[channel].voices: self.log.info('Requesting voice from %s in %s %s.', chanserv, channel, on) irc.sendMsg(ircmsgs.privmsg(chanserv, 'voice %s' % channel)) def doMode(self, irc, msg): if self.disabled(irc): return chanserv = self.registryValue('ChanServ', network=irc.network) on = 'on %s' % irc.network if ircutils.strEqual(msg.nick, chanserv): channel = msg.args[0] if len(msg.args) == 3: if ircutils.strEqual(msg.args[2], irc.nick): mode = msg.args[1] info = self.log.info if mode == '+o': info('Received op from ChanServ in %s %s.', channel, on) elif mode == '+h': info('Received halfop from ChanServ in %s %s.', channel, on) elif mode == '+v': info('Received voice from ChanServ in %s %s.', channel, on) def do366(self, irc, msg): # End of /NAMES list; finished joining a channel if self.identified: channel = msg.args[1] # nick is msg.args[0]. self.checkPrivileges(irc, channel) def callCommand(self, command, irc, msg, *args, **kwargs): if self.disabled(irc): irc.error('Services plugin is disabled on this network', Raise=True) self.__parent.callCommand(command, irc, msg, *args, **kwargs) def _chanservCommand(self, irc, channel, command, log=False): chanserv = self.registryValue('ChanServ', network=irc.network) if chanserv: msg = ircmsgs.privmsg(chanserv, ' '.join([command, channel])) irc.sendMsg(msg) else: if log: self.log.warning('Unable to send %s command to ChanServ, ' 'you must set ' 'supybot.plugins.Services.ChanServ before ' 'I can send commands to ChanServ.', command) else: irc.error(_('You must set supybot.plugins.Services.ChanServ ' 'before I\'m able to send the %s command.') % command, Raise=True) @internationalizeDocstring def op(self, irc, msg, args, channel): """[<channel>] Attempts to get opped by ChanServ in <channel>. <channel> is only necessary if the message isn't sent in the channel itself. """ if irc.nick in irc.state.channels[channel].ops: irc.error(format(_('I\'m already opped in %s.'), channel)) else: self._chanservCommand(irc, channel, 'op') op = wrap(op, [('checkChannelCapability', 'op'), 'inChannel']) @internationalizeDocstring def voice(self, irc, msg, args, channel): """[<channel>] Attempts to get voiced by ChanServ in <channel>. <channel> is only necessary if the message isn't sent in the channel itself. """ if irc.nick in irc.state.channels[channel].voices: irc.error(format(_('I\'m already voiced in %s.'), channel)) else: self._chanservCommand(irc, channel, 'voice') voice = wrap(voice, [('checkChannelCapability', 'op'), 'inChannel']) def do474(self, irc, msg): if self.disabled(irc): return channel = msg.args[1] on = 'on %s' % irc.network self.log.info('Banned from %s, attempting ChanServ unban %s.', channel, on) self._chanservCommand(irc, channel, 'unban', log=True) # Success log in doChanservNotice. @internationalizeDocstring def unban(self, irc, msg, args, channel): """[<channel>] Attempts to get unbanned by ChanServ in <channel>. <channel> is only necessary if the message isn't sent in the channel itself, but chances are, if you need this command, you're not sending it in the channel itself. """ self._chanservCommand(irc, channel, 'unban') irc.replySuccess() unban = wrap(unban, [('checkChannelCapability', 'op')]) def do473(self, irc, msg): if self.disabled(irc): return channel = msg.args[1] on = 'on %s' % irc.network self.log.info('%s is +i, attempting ChanServ invite %s.', channel, on) self._chanservCommand(irc, channel, 'invite', log=True) @internationalizeDocstring def invite(self, irc, msg, args, channel): """[<channel>] Attempts to get invited by ChanServ to <channel>. <channel> is only necessary if the message isn't sent in the channel itself, but chances are, if you need this command, you're not sending it in the channel itself. """ self._chanservCommand(irc, channel, 'invite') irc.replySuccess() invite = wrap(invite, [('checkChannelCapability', 'op'), 'inChannel']) def doInvite(self, irc, msg): if ircutils.strEqual( msg.nick, self.registryValue('ChanServ', network=irc.network)): channel = msg.args[1] on = 'on %s' % irc.network networkGroup = conf.supybot.networks.get(irc.network) self.log.info('Joining %s, invited by ChanServ %s.', channel, on) irc.queueMsg(networkGroup.channels.join(channel)) @internationalizeDocstring def identify(self, irc, msg, args): """takes no arguments Identifies with NickServ using the current nick. """ if self.registryValue('NickServ', network=irc.network): if irc.nick in self.registryValue('nicks', network=irc.network): self._doIdentify(irc, irc.nick) irc.replySuccess() else: irc.error(_('I don\'t have a configured password for ' 'my current nick.')) else: irc.error(_('You must set supybot.plugins.Services.NickServ before ' 'I\'m able to do identify.')) identify = wrap(identify, [('checkCapability', 'admin')]) @internationalizeDocstring def ghost(self, irc, msg, args, nick): """[<nick>] Ghosts the bot's given nick and takes it. If no nick is given, ghosts the bot's configured nick and takes it. """ if self.registryValue('NickServ', network=irc.network): if not nick: nick = self._getNick(irc.network) if ircutils.strEqual(nick, irc.nick): irc.error(_('I cowardly refuse to ghost myself.')) else: self._doGhost(irc, nick=nick) irc.replySuccess() else: irc.error(_('You must set supybot.plugins.Services.NickServ before ' 'I\'m able to ghost a nick.')) ghost = wrap(ghost, [('checkCapability', 'admin'), additional('nick')]) @internationalizeDocstring def password(self, irc, msg, args, nick, password): """<nick> [<password>] Sets the NickServ password for <nick> to <password>. If <password> is not given, removes <nick> from the configured nicks. """ if not password: try: v = self.registryValue('nicks', network=irc.network).copy() v.remove(nick) self.setRegistryValue('nicks', value=v, network=irc.network) irc.replySuccess() except KeyError: irc.error(_('That nick was not configured with a password.')) return else: v = self.registryValue('nicks', network=irc.network).copy() v.add(nick) self.setRegistryValue('nicks', value=v, network=irc.network) config.registerNick(nick, password) irc.replySuccess() password = wrap(password, [('checkCapability', 'admin'), 'private', 'nick', 'text']) @internationalizeDocstring def nicks(self, irc, msg, args): """takes no arguments Returns the nicks that this plugin is configured to identify and ghost with. """ L = list(self.registryValue('nicks', network=irc.network)) if L: utils.sortBy(ircutils.toLower, L) irc.reply(format('%L', L)) else: irc.reply(_('I\'m not currently configured for any nicks.')) nicks = wrap(nicks, [('checkCapability', 'admin')]) Services = internationalizeDocstring(Services) Class = Services # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Services/test.py��������������������������������������������������������0000644�0001750�0001750�00000007001�13634634532�020112� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * class ServicesTestCase(PluginTestCase): plugins = ('Services', 'Config') config = { 'plugins.Services.NickServ': 'NickServ', 'plugins.Services.ChanServ': 'ChanServ', } def testPasswordAndIdentify(self): self.assertNotError('services password foo bar') self.assertError('services identify') # Don't have a password. self.assertNotError('services password %s baz' % self.nick) m = self.assertNotError('services identify') self.assertTrue(m.args[0] == 'NickServ') self.assertTrue(m.args[1].lower() == 'identify baz') self.assertNotError('services password %s biff' % self.nick) m = self.assertNotError('services identify') self.assertTrue(m.args[0] == 'NickServ') self.assertTrue(m.args[1].lower() == 'identify biff') def testPasswordConfg(self): self.assertNotError('config plugins.Services.nicks ""') self.assertNotError('config network plugins.Services.nicks ""') self.assertNotError('services password %s bar' % self.nick) self.assertResponse( 'config plugins.Services.nicks', 'Global: ; test: %s' % self.nick) self.assertResponse( 'config plugins.Services.nickserv.password.%s' % self.nick, 'Global: bar; test: bar') self.assertNotError( 'config network plugins.Services.nickserv.password.%s bar2' % self.nick) self.assertResponse( 'config plugins.Services.nickserv.password.%s' % self.nick, 'Global: bar; test: bar2') self.assertResponse( 'config plugins.Services.nickserv.password.%s' % self.nick, 'Global: bar; test: bar2') m = self.assertNotError('services identify') self.assertTrue(m.args[0] == 'NickServ') self.assertTrue(m.args[1].lower() == 'identify bar2') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/ShrinkUrl/��������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016727� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/ShrinkUrl/__init__.py���������������������������������������������������0000644�0001750�0001750�00000004663�13634634532�021043� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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. ### """ Shrinks URLs using various URL shrinking services. """ import supybot import supybot.world as world __version__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {supybot.authors.jamessan: ['xrl.us support', 'x0.no support']} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������limnoria-2020.03.17/plugins/ShrinkUrl/config.py�����������������������������������������������������0000644�0001750�0001750�00000011577�13634634532�020553� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # Copyright (c) 2009-2010, James McCoy # 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 from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('ShrinkUrl') def configure(advanced): from supybot.questions import output, expect, anything, something, yn conf.registerPlugin('ShrinkUrl', True) if yn(_("""This plugin offers a snarfer that will go retrieve a shorter version of long URLs that are sent to the channel. Would you like this snarfer to be enabled?"""), default=False): conf.supybot.plugins.ShrinkUrl.shrinkSnarfer.setValue(True) class ShrinkService(registry.OnlySomeStrings): """Valid values include 'tiny', 'ur1', and 'x0'.""" validStrings = ('tiny', 'ur1', 'x0') class ShrinkCycle(registry.SpaceSeparatedListOfStrings): """Valid values include 'ln', 'tiny', 'ur1', and 'x0'.""" Value = ShrinkService def __init__(self, *args, **kwargs): super(ShrinkCycle, self).__init__(*args, **kwargs) self.lastIndex = -1 def setValue(self, v): super(self.__class__, self).setValue(v) self.lastIndex = -1 def getService(self): L = self() if L: self.lastIndex = (self.lastIndex + 1) % len(L) return L[self.lastIndex] raise ValueError('No services have been configured for rotation. ' \ 'See conf.supybot.plugins.ShrinkUrl.serviceRotation.') ShrinkUrl = conf.registerPlugin('ShrinkUrl') conf.registerChannelValue(ShrinkUrl, 'shrinkSnarfer', registry.Boolean(False, _("""Determines whether the shrink snarfer is enabled. This snarfer will watch for URLs in the channel, and if they're sufficiently long (as determined by supybot.plugins.ShrinkUrl.minimumLength) it will post a smaller URL from the service as denoted in supybot.plugins.ShrinkUrl.default."""))) conf.registerChannelValue(ShrinkUrl.shrinkSnarfer, 'showDomain', registry.Boolean(True, _("""Determines whether the snarfer will show the domain of the URL being snarfed along with the shrunken URL."""))) conf.registerChannelValue(ShrinkUrl, 'minimumLength', registry.PositiveInteger(48, _("""The minimum length a URL must be before the bot will shrink it."""))) conf.registerChannelValue(ShrinkUrl, 'nonSnarfingRegexp', registry.Regexp(None, _("""Determines what URLs are to be snarfed; URLs matching the regexp given will not be snarfed. Give the empty string if you have no URLs that you'd like to exclude from being snarfed."""))) conf.registerChannelValue(ShrinkUrl, 'outFilter', registry.Boolean(False, _("""Determines whether the bot will shrink the URLs of outgoing messages if those URLs are longer than supybot.plugins.ShrinkUrl.minimumLength."""))) conf.registerChannelValue(ShrinkUrl, 'default', ShrinkService('x0', _("""Determines what website the bot will use when shrinking a URL."""))) conf.registerGlobalValue(ShrinkUrl, 'bold', registry.Boolean(True, _("""Determines whether this plugin will bold certain portions of its replies."""))) conf.registerChannelValue(ShrinkUrl, 'serviceRotation', ShrinkCycle([], _("""If set to a non-empty value, specifies the list of services to rotate through for the shrinkSnarfer and outFilter."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/ShrinkUrl/locales/������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�020351� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/ShrinkUrl/locales/fi.po�������������������������������������������������0000644�0001750�0001750�00000013275�13634634532�021311� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# ShrinkUrl plugin in Limnoria. # Copyright (C) 2011, 2012 Limnoria # Mikaela Suomalainen <mkaysi@outlook.com>, 2011, 2012. # msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2015-01-24 19:31+CET\n" "PO-Revision-Date: 2015-01-24 19:31+0100\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: suomi <>\n" "Language: fi\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" "Plural-Forms: \n" "X-Generator: Poedit 1.6.10\n" #: config.py:39 msgid "" "This plugin offers a snarfer that will go retrieve a shorter\n" " version of long URLs that are sent to the channel. Would you\n" " like this snarfer to be enabled?" msgstr "" "Tämä lisäosa tarjoaa kaappaajan,joka palauttaa lyhyemmän\n" " version pitkistä URL-osoitteista, jotka lähetetään kanavalle. Haluaisitko\n" " tämän kaappaajan olevan käytössä?" #: config.py:45 config.py:49 msgid "Valid values include 'ln', 'tiny', 'goo', 'ur1', and 'x0'." msgstr "Kelvolliset arvot ovat 'ln', 'tiny', 'goo', 'ur1' ja 'x0' ." #: config.py:70 msgid "" "Determines whether the\n" " shrink snarfer is enabled. This snarfer will watch for URLs in the\n" " channel, and if they're sufficiently long (as determined by\n" " supybot.plugins.ShrinkUrl.minimumLength) it will post a\n" " smaller URL from either ln-s.net or tinyurl.com, as denoted in\n" " supybot.plugins.ShrinkUrl.default." msgstr "" "Määrittää onko kutistuskaappain käytössä.\n" " Tämä kaappaaja vahtii URL-osoitteita kanavalla\n" " ja jos ne ovat tarpeeksi pitkiä (määritetty asetusarvolla\n" " supybot.plugins.ShrinkUrl.minimumLength), se lähettää lyhyemmän URL-" "osoitteen\n" " joko sivustolta ln-s.net tai tinyurl.com, riippuen, minkä asetuksen\n" " supybot.plugins.ShrinkUrl.default määrittää." #: config.py:77 msgid "" "Determines whether the snarfer will show the\n" " domain of the URL being snarfed along with the shrunken URL." msgstr "" "Määrittää näyttääkö kaappaaja kaapatun URL-osoitteen domainin lyhennetyn\n" " URL-osoitteen kanssa." #: config.py:80 msgid "" "The minimum length a URL must be before\n" " the bot will shrink it." msgstr "" "Vähimmäispituus joka URL-osoitteen täytyy olla, ennen kuin\n" " botti kutistaa sen." #: config.py:83 msgid "" "Determines what URLs are to be snarfed; URLs\n" " matching the regexp given will not be snarfed. Give the empty string " "if\n" " you have no URLs that you'd like to exclude from being snarfed." msgstr "" "Määrittää mitä URL-osoitteita ei kaapata; URL-osoitteet, jotka\n" " täsmäävät annettuun säännölliseen lausekkeeseen jätetään kaappaamatta. " "Anna tyhjä merkkiketju, jos\n" " et halua estää mitään URL-osoitetta tulemasta kaapatuksi." #: config.py:87 msgid "" "Determines whether the bot will shrink the\n" " URLs of outgoing messages if those URLs are longer than\n" " supybot.plugins.ShrinkUrl.minimumLength." msgstr "" "Määrittää lyhentääkö botti ulosmenevien viestien\n" " URL-osoitteet, jos ne ovat pidempiä kuin\n" " supybot.plugins.ShrinkUrl.minimumLength." #: config.py:91 msgid "" "Determines what website the bot will use when\n" " shrinking a URL." msgstr "" "Määrittää mitä verkkosivua botti käyttää lyhentäessään\n" " URL-osoitetta." #: config.py:94 msgid "" "Determines whether this plugin will bold\n" " certain portions of its replies." msgstr "" "Määrittää korostaako botti tietyt osat\n" " vastauksissaan." #: config.py:97 msgid "" "If set to a non-empty value, specifies the list of\n" " services to rotate through for the shrinkSnarfer and outFilter." msgstr "" "Jos tämä on asetettu muuksi, kuin tyhjäksi arvoksi, määrittää listan\n" " palveluista, joita käytetään kutistuskaappaajalle ja ulostulon " "suodattimelle." #: plugin.py:90 msgid "" "This plugin features commands to shorten URLs through different services,\n" " like tinyurl." msgstr "" "Tämä plugin sisältää komennot URL-osoitteiden lyhentämiseen eri palveluilla, " "kuten esimerkiksi\n" " tinyurl:illa." #: plugin.py:192 msgid "" "<url>\n" "\n" " Returns an ln-s.net version of <url>.\n" " " msgstr "" "<URL-osoite>\n" "\n" " Palauttaa ln-s.net version <URL-osoitteesta>.\n" " " #: plugin.py:219 msgid "" "<url>\n" "\n" " Returns a TinyURL.com version of <url>\n" " " msgstr "" "<URL-osoite>\n" "\n" " Palauttaa TinyURL.com palvelun lyhentämän version <URL-" "osoitteesta>.\n" " " #: plugin.py:252 msgid "" "<url>\n" "\n" " Returns an goo.gl version of <url>.\n" " " msgstr "" "<url>\n" "\n" " Palauttaa goo.gl-palvelun lyhentämän version <URL-osoitteesta>." #: plugin.py:282 msgid "" "<url>\n" "\n" " Returns an ur1 version of <url>.\n" " " msgstr "" "<url>\n" " Palauttaa ur1-version <url:stä>.\n" " " #: plugin.py:309 msgid "" "<url>\n" "\n" " Returns an x0.no version of <url>.\n" " " msgstr "" "<url>\n" "\n" " Palauttaa x0.no palvelun lyhentämän version <URL-osoitteesta>.\n" " " #: plugin.py:336 msgid "" "<url>\n" "\n" " Returns an expanded version of <url>.\n" " " msgstr "" "<url>\n" " Palauttaa laajennetun version <url-osoitteesta>.\n" " " #~ msgid "" #~ "<url>\n" #~ "\n" #~ " Returns an xrl.us version of <url>.\n" #~ " " #~ msgstr "" #~ "<URL-osoite>\n" #~ "\n" #~ " Palauttaa xrl.us palvelun lyhentämän version <URL-osoitteesta>.\n" #~ " " #~ msgid "Valid values include 'ln', 'tiny', 'xrl', 'goo', and 'x0'." #~ msgstr "Kelvolliset arvot ovat 'ln', 'tiny', 'xrl', 'goo' ja 'x0' ." �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/ShrinkUrl/locales/fr.po�������������������������������������������������0000644�0001750�0001750�00000011666�13634634532�021324� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2015-01-24 19:31+CET\n" "PO-Revision-Date: 2015-01-24 19:32+0100\n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" #: config.py:39 msgid "" "This plugin offers a snarfer that will go retrieve a shorter\n" " version of long URLs that are sent to the channel. Would you\n" " like this snarfer to be enabled?" msgstr "" "Ce plugin offre un snarfer qui récupère de longues URLs envoyées sur un " "canal pour en envoyer une version plus courte. Voulez-vous activer ce " "snarfer ?" #: config.py:45 config.py:49 msgid "Valid values include 'ln', 'tiny', 'goo', 'ur1', and 'x0'." msgstr "" "Les valeurs valides incluent 'ln', 'tiny', 'goo', 'ur1', et 'x0'." #: config.py:70 msgid "" "Determines whether the\n" " shrink snarfer is enabled. This snarfer will watch for URLs in the\n" " channel, and if they're sufficiently long (as determined by\n" " supybot.plugins.ShrinkUrl.minimumLength) it will post a\n" " smaller URL from either ln-s.net or tinyurl.com, as denoted in\n" " supybot.plugins.ShrinkUrl.default." msgstr "" "Détermine si le snarfer d'URL est activé. Ce remplaceur sera à l'écoute de " "toutes les URLs sur lle canal, et, si elle sont suffisamment longues " "(déterminé par supybot.plugins.ShrinkUrl.minimumLength) le bot postera une " "URL raccourcie avec ln-s.net ou tinyurl.com, comme défini par supybot." "plugins.ShrinkUrl.default." #: config.py:77 msgid "" "Determines whether the snarfer will show the\n" " domain of the URL being snarfed along with the shrunken URL." msgstr "" "Détermine si le snarfer affichera le domaine de l'URL snarfée avec l'URL " "raccourcie." #: config.py:80 msgid "" "The minimum length a URL must be before\n" " the bot will shrink it." msgstr "La taille minimum d'une URL pour que le bot la raccourcice." #: config.py:83 msgid "" "Determines what URLs are to be snarfed; URLs\n" " matching the regexp given will not be snarfed. Give the empty string " "if\n" " you have no URLs that you'd like to exclude from being snarfed." msgstr "" "Détermine quelles URLs seront snarfées ; les URLs correspondant à " "l'expression régulière ne seront par snarfées. Donnez une chaîne vide si il " "n'y a pas d'URL que vous voulez exclure." #: config.py:87 msgid "" "Determines whether the bot will shrink the\n" " URLs of outgoing messages if those URLs are longer than\n" " supybot.plugins.ShrinkUrl.minimumLength." msgstr "" "Détermine si le bot raccourcira les URLs des messages sortant si ces URLs " "sont plus longues que supybot.plugins.ShrinkUrl.minimumLength." #: config.py:91 msgid "" "Determines what website the bot will use when\n" " shrinking a URL." msgstr "Détermine quel site web le bot utilisera pour raccourcir une URL" #: config.py:94 msgid "" "Determines whether this plugin will bold\n" " certain portions of its replies." msgstr "" "Détermine si ce plugin mettra en gras certaines portions de ses réponses." #: config.py:97 msgid "" "If set to a non-empty value, specifies the list of\n" " services to rotate through for the shrinkSnarfer and outFilter." msgstr "" "Si définit à une valeur non vide, définit la liste des services à faire " "tourner pour shrinkSnarfer et outFilter." #: plugin.py:90 msgid "" "This plugin features commands to shorten URLs through different services,\n" " like tinyurl." msgstr "" #: plugin.py:192 msgid "" "<url>\n" "\n" " Returns an ln-s.net version of <url>.\n" " " msgstr "" "<url>\n" "\n" "Retourne une version de ln-s.net de l'<url>." #: plugin.py:219 msgid "" "<url>\n" "\n" " Returns a TinyURL.com version of <url>\n" " " msgstr "" "<url>\n" "\n" "Retourne une version de TinyURL.com de l'<url>." #: plugin.py:252 msgid "" "<url>\n" "\n" " Returns an goo.gl version of <url>.\n" " " msgstr "" "<url>\n" "\n" "Retourne une version de goo.gl de l'<url>." #: plugin.py:282 msgid "" "<url>\n" "\n" " Returns an ur1 version of <url>.\n" " " msgstr "" "<url>\n" "\n" "Retourne une version de ur1 de l'<url>." #: plugin.py:309 msgid "" "<url>\n" "\n" " Returns an x0.no version of <url>.\n" " " msgstr "" "<url>\n" "\n" "Retourne une version de x0.no de l'<url>." #: plugin.py:336 msgid "" "<url>\n" "\n" " Returns an expanded version of <url>.\n" " " msgstr "" "<url>\n" "\n" "Retourne la version large de l'<url>." #~ msgid "" #~ "<url>\n" #~ "\n" #~ " Returns an xrl.us version of <url>.\n" #~ " " #~ msgstr "" #~ "<url>\n" #~ "\n" #~ "Retourne une version de xrl.us de l'<url>." #~ msgid "Valid values include 'ln', 'tiny', 'xrl', 'goo', and 'x0'." #~ msgstr "Les valeurs valides incluent 'ln', 'tiny', 'xrl', 'goo', et 'x0'." ��������������������������������������������������������������������������limnoria-2020.03.17/plugins/ShrinkUrl/locales/it.po�������������������������������������������������0000644�0001750�0001750�00000011735�13634634532�021326� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2015-01-24 19:31+CET\n" "PO-Revision-Date: 2015-01-24 11:45+0100\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:39 msgid "" "This plugin offers a snarfer that will go retrieve a shorter\n" " version of long URLs that are sent to the channel. Would you\n" " like this snarfer to be enabled?" msgstr "" "Questo plugin offre un cattura URL che riporterà una versione accorciata\n" " di quelli lunghi inviati al canale. Lo si vuole abilitare?\n" #: config.py:45 config.py:49 #, fuzzy msgid "Valid values include 'ln', 'tiny', 'goo', 'ur1', and 'x0'." msgstr "" "I valori validi comprendono \"ln\", \"tiny\", \"goo\" e \"x0\"." #: config.py:70 msgid "" "Determines whether the\n" " shrink snarfer is enabled. This snarfer will watch for URLs in the\n" " channel, and if they're sufficiently long (as determined by\n" " supybot.plugins.ShrinkUrl.minimumLength) it will post a\n" " smaller URL from either ln-s.net or tinyurl.com, as denoted in\n" " supybot.plugins.ShrinkUrl.default." msgstr "" "Determina se l'accorcia URL è abilitato. Questo controllerà gli URL che " "passano\n" " in canale e se sono sufficientemente lunghi (determinato da\n" " supybot.plugins.ShrinkUrl.minimumLength) il bot ne invierà uno più " "corto\n" " tramite ln-s.net o tinyurl.com, come definito in supybot.plugins." "ShrinkUrl.default." #: config.py:77 msgid "" "Determines whether the snarfer will show the\n" " domain of the URL being snarfed along with the shrunken URL." msgstr "" "Determina se l'accorcia URL mostrerà il dominio dell'URL originale assieme a " "quello accorciato." #: config.py:80 msgid "" "The minimum length a URL must be before\n" " the bot will shrink it." msgstr "" "La lunghezza minima che un URL deve avere affinché il bot decida di " "accorciarlo." #: config.py:83 msgid "" "Determines what URLs are to be snarfed; URLs\n" " matching the regexp given will not be snarfed. Give the empty string " "if\n" " you have no URLs that you'd like to exclude from being snarfed." msgstr "" "Determina quali URL debbano essere intercettati; quelli che corrispondono " "alla\n" " regexp fornita non verranno coinvolti. Se non si vuole escludere alcun " "URL,\n" " aggiungere una stringa vuota." #: config.py:87 msgid "" "Determines whether the bot will shrink the\n" " URLs of outgoing messages if those URLs are longer than\n" " supybot.plugins.ShrinkUrl.minimumLength." msgstr "" "Determina se il bot accorcerà gli URL dei messaggi in uscita se questi sono " "più\n" " lunghi del valore di supybot.plugins.ShrinkUrl.minimumLength." #: config.py:91 msgid "" "Determines what website the bot will use when\n" " shrinking a URL." msgstr "Determina quale sito web il bot userà per accorciare un URL." #: config.py:94 msgid "" "Determines whether this plugin will bold\n" " certain portions of its replies." msgstr "" "Determina se il plugin riporterà in grassetto alcune porzioni delle risposte." #: config.py:97 msgid "" "If set to a non-empty value, specifies the list of\n" " services to rotate through for the shrinkSnarfer and outFilter." msgstr "" "Se impostato ad un valore non vuoto, specifica l'elenco dei servizi a cui\n" " rivolgersi per le variabili shrinkSnarfer e outFilter." #: plugin.py:90 msgid "" "This plugin features commands to shorten URLs through different services,\n" " like tinyurl." msgstr "" #: plugin.py:192 msgid "" "<url>\n" "\n" " Returns an ln-s.net version of <url>.\n" " " msgstr "" "<url>\n" "\n" " Restituisce una versione di ln-s.net di <url>.\n" " " #: plugin.py:219 msgid "" "<url>\n" "\n" " Returns a TinyURL.com version of <url>\n" " " msgstr "" "<url>\n" "\n" " Restituisce una versione di TinyURL.com di <url>\n" " " #: plugin.py:252 msgid "" "<url>\n" "\n" " Returns an goo.gl version of <url>.\n" " " msgstr "" "<url>\n" "\n" " Restituisce una versione di goo.gl di <url>.\n" " " #: plugin.py:282 #, fuzzy msgid "" "<url>\n" "\n" " Returns an ur1 version of <url>.\n" " " msgstr "" "<url>\n" "\n" " Restituisce una versione di xrl.us di <url>.\n" " " #: plugin.py:309 msgid "" "<url>\n" "\n" " Returns an x0.no version of <url>.\n" " " msgstr "" "<url>\n" "\n" " Restituisce una versione di x0.no di <url>.\n" " " #: plugin.py:336 #, fuzzy msgid "" "<url>\n" "\n" " Returns an expanded version of <url>.\n" " " msgstr "" "<url>\n" "\n" " Restituisce una versione di x0.no di <url>.\n" " " #~ msgid "" #~ "<url>\n" #~ "\n" #~ " Returns an xrl.us version of <url>.\n" #~ " " #~ msgstr "" #~ "<url>\n" #~ "\n" #~ " Restituisce una versione di xrl.us di <url>.\n" #~ " " �����������������������������������limnoria-2020.03.17/plugins/ShrinkUrl/plugin.py�����������������������������������������������������0000644�0001750�0001750�00000023003�13634634532�020567� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2009-2010, James McCoy # 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 re import sys import time import json import supybot.log as log import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('ShrinkUrl') class CdbShrunkenUrlDB(object): def __init__(self, filename): self.dbs = {} cdb = conf.supybot.databases.types.cdb def register_service(service): dbname = filename.replace('.db', service.capitalize() + '.db') self.dbs[service] = cdb.connect(dbname) for service in conf.supybot.plugins.ShrinkUrl.default.validStrings: register_service(service) register_service('Expand') def get(self, service, url): return self.dbs[service][url] def set(self, service, url, shrunkurl): self.dbs[service][url] = shrunkurl def close(self): for service in self.dbs: self.dbs[service].close() def flush(self): for service in self.dbs: self.dbs[service].flush() ShrunkenUrlDB = plugins.DB('ShrinkUrl', {'cdb': CdbShrunkenUrlDB}) class ShrinkError(Exception): pass def retry(f): def newf(*args, **kwargs): for x in range(0, 3): try: return f(*args, **kwargs) except Exception: log.exception('Shrinking URL failed. Trying again.') time.sleep(1) return f(*args, **kwargs) return newf class ShrinkUrl(callbacks.PluginRegexp): """This plugin features commands to shorten URLs through different services, like tinyurl.""" regexps = ['shrinkSnarfer'] def __init__(self, irc): self.__parent = super(ShrinkUrl, self) self.__parent.__init__(irc) self.db = ShrunkenUrlDB() def die(self): self.db.close() def callCommand(self, command, irc, msg, *args, **kwargs): try: self.__parent.callCommand(command, irc, msg, *args, **kwargs) except utils.web.Error as e: irc.error(str(e)) def _outFilterThread(self, irc, msg): (channel, text) = msg.args network = irc.network for m in utils.web.httpUrlRe.finditer(text): url = m.group(1) if len(url) > self.registryValue('minimumLength', channel, network): try: cmd = self.registryValue('serviceRotation', channel, network, value=False) cmd = cmd.getService().capitalize() except ValueError: cmd = self.registryValue('default', channel, network) \ .capitalize() try: shortUrl = getattr(self, '_get%sUrl' % cmd)(url) text = text.replace(url, shortUrl) except (utils.web.Error, AttributeError, ShrinkError): pass newMsg = ircmsgs.privmsg(channel, text, msg=msg) newMsg.tag('shrunken') irc.queueMsg(newMsg) def outFilter(self, irc, msg): if msg.command != 'PRIVMSG': return msg if msg.channel: if not msg.shrunken: if self.registryValue('outFilter', msg.channel, irc.network): if utils.web.httpUrlRe.search(msg.args[1]): self._outFilterThread(irc, msg) return None return msg def shrinkSnarfer(self, irc, msg, match): channel = msg.channel network = irc.network if not channel: return if self.registryValue('shrinkSnarfer', channel, network): url = match.group(0) r = self.registryValue('nonSnarfingRegexp', channel, network) if r and r.search(url) is not None: self.log.debug('Matched nonSnarfingRegexp: %u', url) return minlen = self.registryValue('minimumLength', channel, network) try: cmd = self.registryValue('serviceRotation', channel, network, value=False) cmd = cmd.getService().capitalize() except ValueError: cmd = self.registryValue('default', channel, network) \ .capitalize() if len(url) >= minlen: try: shorturl = getattr(self, '_get%sUrl' % cmd)(url) except (utils.web.Error, AttributeError, ShrinkError): self.log.info('Couldn\'t get shorturl for %u', url) return if self.registryValue('shrinkSnarfer.showDomain', channel, network): domain = ' (at %s)' % utils.web.getDomain(url) else: domain = '' if self.registryValue('bold'): s = format('%u%s', ircutils.bold(shorturl), domain) else: s = format('%u%s', shorturl, domain) m = irc.reply(s, prefixNick=False) if m is not None: m.tag('shrunken') shrinkSnarfer = urlSnarfer(shrinkSnarfer) shrinkSnarfer.__doc__ = utils.web._httpUrlRe @retry def _getTinyUrl(self, url): try: return self.db.get('tiny', url) except KeyError: text = utils.web.getUrl('http://tinyurl.com/api-create.php?url=' + url) text = text.decode() if text.startswith('Error'): raise ShrinkError(text[5:]) self.db.set('tiny', url, text) return text @internationalizeDocstring def tiny(self, irc, msg, args, url): """<url> Returns a TinyURL.com version of <url> """ try: tinyurl = self._getTinyUrl(url) m = irc.reply(tinyurl) if m is not None: m.tag('shrunken') except ShrinkError as e: irc.errorPossibleBug(str(e)) tiny = thread(wrap(tiny, ['httpUrl'])) _ur1Api = 'http://ur1.ca/' _ur1Regexp = re.compile(r'<a href="(?P<url>[^"]+)">') @retry def _getUr1Url(self, url): try: return self.db.get('ur1ca', utils.web.urlquote(url)) except KeyError: parameters = utils.web.urlencode({'longurl': url}) response = utils.web.getUrl(self._ur1Api, data=parameters) ur1ca = self._ur1Regexp.search(response.decode()).group('url') if ur1ca: self.db.set('ur1', url, ur1ca) return ur1ca else: raise ShrinkError(response) def ur1(self, irc, msg, args, url): """<url> Returns an ur1 version of <url>. """ try: ur1url = self._getUr1Url(url) m = irc.reply(ur1url) if m is not None: m.tag('shrunken') except ShrinkError as e: irc.error(str(e)) ur1 = thread(wrap(ur1, ['httpUrl'])) _x0Api = 'https://x0.no/api/?%s' @retry def _getX0Url(self, url): try: return self.db.get('x0', url) except KeyError: text = utils.web.getUrl(self._x0Api % url).decode() if text.startswith('ERROR:'): raise ShrinkError(text[6:]) self.db.set('x0', url, text) return text @internationalizeDocstring def x0(self, irc, msg, args, url): """<url> Returns an x0.no version of <url>. """ try: x0url = self._getX0Url(url) m = irc.reply(x0url) if m is not None: m.tag('shrunken') except ShrinkError as e: irc.error(str(e)) x0 = thread(wrap(x0, ['httpUrl'])) Class = ShrinkUrl # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/ShrinkUrl/test.py�������������������������������������������������������0000644�0001750�0001750�00000012170�13634634532�020253� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2009-2010, James McCoy # 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 ShrinkUrlTestCase(ChannelPluginTestCase): plugins = ('ShrinkUrl',) config = {'supybot.snarfThrottle': 0} sfUrl ='http://sourceforge.net/p/supybot/bugs/?source=navbar' udUrl = 'http://www.urbandictionary.com/define.php?' \ 'term=all+your+base+are+belong+to+us' tests = {'tiny': [(sfUrl, r'http://tinyurl.com/b7wyvfz'), (udUrl, r'http://tinyurl.com/u479')], 'ur1': [(sfUrl, r'http://ur1.ca/ceqh8'), (udUrl, r'http://ur1.ca/9xl9k')], 'x0': [(sfUrl, r'https://x0.no/a53s'), (udUrl, r'https://x0.no/0l2k')] } if network: def testShrink(self): for (service, testdata) in self.tests.items(): for (url, shrunkurl) in testdata: self.assertRegexp('shrinkurl %s %s' % (service, url), shrunkurl) def testShrinkCycle(self): cycle = conf.supybot.plugins.ShrinkUrl.serviceRotation snarfer = conf.supybot.plugins.ShrinkUrl.shrinkSnarfer origcycle = cycle() origsnarfer = snarfer() try: self.assertNotError( 'config plugins.ShrinkUrl.serviceRotation tiny x0') self.assertError( 'config plugins.ShrinkUrl.serviceRotation tiny x1') snarfer.setValue(True) self.assertSnarfRegexp(self.udUrl, r'.*%s.* \(at' % self.tests['tiny'][1][1]) self.assertSnarfRegexp(self.udUrl, r'.*%s.* \(at' % self.tests['x0'][1][1]) finally: cycle.setValue(origcycle) snarfer.setValue(origsnarfer) def _snarf(self, service): shrink = conf.supybot.plugins.ShrinkUrl origService = shrink.default() origSnarf = shrink.shrinkSnarfer() shrink.default.setValue(service) shrink.shrinkSnarfer.setValue(True) try: for (url, shrunkurl) in self.tests[service]: teststr = r'.*%s.* \(at' % shrunkurl self.assertSnarfRegexp(url, teststr) finally: shrink.default.setValue(origService) shrink.shrinkSnarfer.setValue(origSnarf) def testTinysnarf(self): self._snarf('tiny') def testUr1snarf(self): self._snarf('ur1') def testX0snarf(self): self._snarf('x0') def testNonSnarfing(self): shrink = conf.supybot.plugins.ShrinkUrl origService = shrink.default() origSnarf = shrink.shrinkSnarfer() origLen = shrink.minimumLength() origRegexp = shrink.nonSnarfingRegexp() shrink.default.setValue('tiny') shrink.shrinkSnarfer.setValue(True) shrink.minimumLength.setValue(10) shrink.nonSnarfingRegexp.set('m/sf/') try: self.assertSnarfNoResponse('http://sf.net/', 5) self.assertSnarfRegexp('http://sourceforge.net/', r'http://tinyurl.com/7vm7.*\(at ') finally: shrink.default.setValue(origService) shrink.shrinkSnarfer.setValue(origSnarf) shrink.minimumLength.setValue(origLen) shrink.nonSnarfingRegexp.setValue(origRegexp) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Status/�����������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016271� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Status/__init__.py������������������������������������������������������0000644�0001750�0001750�00000004534�13634634532�020402� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ A simple module to handle various informational commands querying the bot's current status and statistics. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload reload(plugin) # In case we're being reloaded. if world.testing: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Status/config.py��������������������������������������������������������0000644�0001750�0001750�00000005451�13634634532�020107� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Status') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Status', True) Status = conf.registerPlugin('Status') conf.registerGroup(Status, 'cpu') conf.registerChannelValue(Status.cpu, 'children', registry.Boolean(True, _("""Determines whether the cpu command will list the time taken by children as well as the bot's process."""))) conf.registerChannelValue(Status.cpu, 'threads', registry.Boolean(False, _("""Determines whether the cpu command will provide the number of threads spawned and active."""))) conf.registerChannelValue(Status.cpu, 'memory', registry.Boolean(True, _("""Determines whether the cpu command will report the amount of memory being used by the bot."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Status/locales/���������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017713� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Status/locales/de.po����������������������������������������������������0000644�0001750�0001750�00000011372�13634634532�020641� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2012-03-11 20:58+UTC\n" "PO-Revision-Date: 2012-04-27 15:46+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: German <fbesser@gmail.com>\n" "Language: de\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: German\n" "X-Poedit-Country: GERMANY\n" #: config.py:46 msgid "" "Determines whether the cpu command will list\n" " the time taken by children as well as the bot's process." msgstr "legt fest ob der cpu Befehl die Zeit die die Kindprozesse verbrauchten, als auch die Zeit des Botprozesses." #: config.py:49 msgid "" "Determines whether the cpu command will\n" " provide the number of threads spawned and active." msgstr "Legt fest ob der cpu Befehl die Anzahl der gestarteten Threads und der Aktiven anzeigen soll." #: config.py:52 msgid "" "Determines whether the cpu command will report\n" " the amount of memory being used by the bot." msgstr "Legt fest ob der cpu Befehl den gebrauchten Speicher des Bots ausgeben soll." #: plugin.py:71 msgid "" "takes no arguments\n" "\n" " Returns the status of the bot.\n" " " msgstr "" "hat keine Argumente\n" "\n" "Gibt den Status des Bots aus." #: plugin.py:80 msgid "%s as %L" msgstr "%s als %L" #: plugin.py:81 msgid "I am connected to %L." msgstr "Ich bin verbunden zu %L." #: plugin.py:83 msgid "I am currently in code profiling mode." msgstr "Momentan bin ich im quelltextanalyse Modus." #: plugin.py:89 msgid "" "takes no arguments\n" "\n" " Returns the current threads that are active.\n" " " msgstr "" "hat keine Argumente\n" "\n" "Gibt aus wieviele Threads momentan aktiv sind." #: plugin.py:95 msgid "I have spawned %n; %n %b still currently active: %L." msgstr "Ich habe %n erzeugt. %n %b sind jetzt noch Aktiv: %L." #: plugin.py:103 msgid "" "takes no arguments\n" "\n" " Returns some interesting network-related statistics.\n" " " msgstr "" "hat keine Argumente\n" "\n" "Gibt einige interessante Netzwerk bezogene Statistiken aus." #: plugin.py:111 msgid "an indeterminate amount of time" msgstr "eine unbestimmte Zeit" #: plugin.py:112 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 "Ich habe %s Nachrichten empfangen, im totalen %S. Ich habe %s Nachrichten gesendet, im totalen %S. Ich bin mit %s verbunden, seit %s." #: plugin.py:121 msgid "" "takes no arguments\n" "\n" " Returns some interesting CPU-related statistics on the bot.\n" " " msgstr "" "hat keine Argumente\n" "\n" "Gibt einige interessante CPU bezogene Statistiken des Bots aus." #: plugin.py:131 msgid "My children have taken %.2f seconds of user time and %.2f seconds of system time for a total of %.2f seconds of CPU time." msgstr "Meine Kindsprozesse haben %.2f Sekunden an Nutzerzeit und %.2f Systemzeit gebraucht, im totalen %.2f Sekunden der CPU Zeit." #: plugin.py:138 msgid "I have taken %.2f seconds of user time and %.2f seconds of system time, for a total of %.2f seconds of CPU time. %s" msgstr "Ich habe %.2f Sekunden an benutzer Zeit und %.2f Sekunden an Systemzeit benötigt, in allen %.2f CPU Zeit. %s" #: plugin.py:160 msgid "Unable to run ps command." msgstr "Ich kann den Befehl ps nicht ausführen." #: plugin.py:166 msgid " I'm taking up %S of memory." msgstr " Ich verbrauche %S Speicher." #: plugin.py:175 msgid "" "takes no arguments\n" "\n" " Returns some interesting command-related statistics.\n" " " msgstr "" "hat keine Argumente\n" "\n" "Gibt einige interessante befehlbezogene Statistiken aus." #: plugin.py:185 msgid "I offer a total of %n in %n. I have processed %n." msgstr "Ich biete %n in %n an. Ich habe %n bearbeitet." #: plugin.py:194 msgid "" "takes no arguments\n" "\n" " Returns a list of the commands offered by the bot.\n" " " msgstr "" "hat keine Argumente\n" "\n" "Gibt eine Liste der Befehle zurück, die der Bot anbietet." #: plugin.py:208 msgid "" "takes no arguments\n" "\n" " Returns the amount of time the bot has been running.\n" " " msgstr "" "hat keine Argumente\n" "\n" "Gibt aus wie lange der Bot schon läuft." #: plugin.py:212 msgid "I have been running for %s." msgstr "Ich laufe seit %s." #: plugin.py:219 msgid "" "takes no arguments\n" "\n" " Returns the server the bot is on.\n" " " msgstr "" "hat keine Arguments\n" "\n" "Gibt den Server aus mit dem der Bot verbunden ist." #: plugin.py:228 #, fuzzy msgid "" "takes no arguments\n" "\n" " Returns the network the bot is on.\n" " " msgstr "" "hat keine Arguments\n" "\n" "Gibt den Server aus mit dem der Bot verbunden ist." ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Status/locales/fi.po����������������������������������������������������0000644�0001750�0001750�00000013555�13634634532�020654� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Status plugin in Limnoria # Copyright (C) 2011 Limnoria # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: Status plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 11:29+EET\n" "PO-Revision-Date: 2014-12-20 11:37+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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-Generator: Poedit 1.6.10\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: config.py:46 msgid "" "Determines whether the cpu command will list\n" " the time taken by children as well as the bot's process." msgstr "" "Määrittää luetteleeko cpu komento lasten ottaman ajan\n" " botin prosessin lisäksi." #: config.py:49 msgid "" "Determines whether the cpu command will\n" " provide the number of threads spawned and active." msgstr "" "Määrittää ilmoittaako cpu komento ilmestyneiden ja aktiiviseten ketjujen\n" " lukumäärät." #: config.py:52 msgid "" "Determines whether the cpu command will report\n" " the amount of memory being used by the bot." msgstr "" "Määrittää ilmoittaako cpu komento\n" " botin käyttämän muistin määrän." #: plugin.py:47 msgid "" "This plugin allows you to view different bot statistics, for example,\n" " uptime." msgstr "" "Tämä plugini näyttää erilaisia tilastoja botista, kuten esimerkiksi " "käynnissäolo-ajan." #: plugin.py:74 msgid "" "takes no arguments\n" "\n" " Returns the status of the bot.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Palauttaa botin tilan.\n" " " #: plugin.py:83 msgid "%s as %L" msgstr "%s verkossa %L" #: plugin.py:84 msgid "I am connected to %L." msgstr "Olen yhdistänyt verkkoon %L" #: plugin.py:86 msgid "I am currently in code profiling mode." msgstr "Olen tällä hetkellä koodin profilointi tilassa." #: plugin.py:92 msgid "" "takes no arguments\n" "\n" " Returns the current threads that are active.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Palauttaa botin tämänhetkiset ketjut, jotka ovat aktiivisia.\n" " " #: plugin.py:98 msgid "I have spawned %n; %n %b still currently active: %L." msgstr "Minä olen ilmestyttänyt %n; %n %b yhä aktiivinen: %L." #: plugin.py:105 msgid "" "takes no arguments\n" "\n" " Returns the number of processes that have been spawned, and list of\n" " ones that are still active.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Palauttaa prosessien määrän, jotka on luotu ja listan niistä prosesseista, " "jotka ovat yhä\n" " aktiivisia..\n" " " #: plugin.py:120 msgid "" "takes no arguments\n" "\n" " Returns some interesting network-related statistics.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Palauttaa joitakin mielenkiintoisia verkkoon liittyviä " "tilastotietoja.\n" " " #: plugin.py:128 msgid "an indeterminate amount of time" msgstr "määrittämätön määrä aikaa" #: plugin.py:129 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 "" "Minä olen vastaanottanut %s viestiä yhteensä määrän %S edestä. Olen " "lähettänyt %s viestiä määrän %S edestä. Olen ollut yhdistettynä palvelimeen " "%s ajan %s." #: plugin.py:138 msgid "" "takes no arguments\n" "\n" " Returns some interesting CPU-related statistics on the bot.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Palauttaa joitakin mielenkiintoisia suoritinkäyttöön liittyviä " "tilastotietoja botista.\n" " " #: plugin.py:148 msgid "" "My children have taken %.2f seconds of user time and %.2f seconds of system " "time for a total of %.2f seconds of CPU time." msgstr "" "Minun lapseni ovat vieneet %.2f käyttäjän aikaa ja %.2f sekuntia " "järjestelmän aikaa. Lapseni ovat ottaneet yhteensä %.2f sekuntia CPU aikaa." #: plugin.py:155 msgid "" "I have taken %.2f seconds of user time and %.2f seconds of system time, for " "a total of %.2f seconds of CPU time. %s" msgstr "" "Olen ottanut %.2f sekuntia käyttäjän aikaa %.2f sekuntia järjestelmän aikaa. " "Olen ottanut yhteensä %.2f sekuntia CPU ajasta. %s" #: plugin.py:177 msgid "Unable to run ps command." msgstr "ps komentoa ei pystytä suorittamaan." #: plugin.py:183 msgid " I'm taking up %S of memory." msgstr " Muistinkäyttöni on yhteensä %S." #: plugin.py:192 msgid "" "takes no arguments\n" "\n" " Returns some interesting command-related statistics.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Palauttaa joitakin mielenkiintoisia komentoihin liittyviä " "tilastotietoja.\n" " " #: plugin.py:202 msgid "I offer a total of %n in %n. I have processed %n." msgstr "Tarjoan yhteensä %n määrän %n sisässä. Olen käsitellyt %n." #: plugin.py:211 msgid "" "takes no arguments\n" "\n" " Returns a list of the commands offered by the bot.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Palauttaa listan komennoista, jotka botti tarjoaa.\n" " " #: plugin.py:225 msgid "" "takes no arguments\n" "\n" " Returns the amount of time the bot has been running.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Palauttaa ajan, jonka botti on ollut käynnissä.\n" " " #: plugin.py:229 msgid "I have been running for %s." msgstr "Olen ollut käynnissä ajan %s." #: plugin.py:236 msgid "" "takes no arguments\n" "\n" " Returns the server the bot is on.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Palauttaa palvelimen, jolla botti on.\n" " " #: plugin.py:245 msgid "" "takes no arguments\n" "\n" " Returns the network the bot is on.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Palauttaa verkon, jossa botti on.\n" " " ���������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Status/locales/fr.po����������������������������������������������������0000644�0001750�0001750�00000012161�13634634532�020655� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-01-21 22:33+CET\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: Limnoria <progval@gmail.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" "Language: fr\n" #: config.py:46 msgid "" "Determines whether the cpu command will list\n" " the time taken by children as well as the bot's process." msgstr "" "Détermine si la commande cpu listera le temps pris par les enfants aussi " "bien que celui du processus du bot." #: config.py:49 msgid "" "Determines whether the cpu command will\n" " provide the number of threads spawned and active." msgstr "" "Détermine si la commande cpu donnera le nombre de threads lancés et actifs." #: config.py:52 msgid "" "Determines whether the cpu command will report\n" " the amount of memory being used by the bot." msgstr "" "Détermine si la commande cpu donnera la quantité de mémoire utilisée par le " "bot." #: plugin.py:72 msgid "" "takes no arguments\n" "\n" " Returns the status of the bot.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne le status du bot." #: plugin.py:81 msgid "%s as %L" msgstr "%s en tant que %L" #: plugin.py:82 msgid "I am connected to %L." msgstr "Je suis connecté à %L" #: plugin.py:84 msgid "I am currently in code profiling mode." msgstr "Je suis actuellement en mode de profiling du code." #: plugin.py:90 msgid "" "takes no arguments\n" "\n" " Returns the current threads that are active.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne les threads actifs." #: plugin.py:96 msgid "I have spawned %n; %n %b still currently active: %L." msgstr "J'ai lancé %n ; %n %b encore actuellement en vie : %L." #: plugin.py:103 msgid "" "takes no arguments\n" "\n" " Returns the number of processes that have been spawned, and list of\n" " ones that are still active.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne le nombre de processus qui ont été lancés, et la liste de ceux qui " "sont encore actifs." #: plugin.py:118 msgid "" "takes no arguments\n" "\n" " Returns some interesting network-related statistics.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne quelques statistiques intéressantes liées au réseau." #: plugin.py:126 msgid "an indeterminate amount of time" msgstr "une durée indéterminée" #: plugin.py:127 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:136 msgid "" "takes no arguments\n" "\n" " Returns some interesting CPU-related statistics on the bot.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne quelques statistiques intéressantes reliées au CPU." #: plugin.py:146 msgid "" "My children have taken %.2f seconds of user time and %.2f seconds of system " "time for a total of %.2f seconds of CPU time." msgstr "" "Mes enfants ont pris %.2f secondes du temps utilisateur et %.2f secondes du " "temps système, pour un total de %.2f secondes de temps CPU." #: plugin.py:153 msgid "" "I have taken %.2f seconds of user time and %.2f seconds of system time, for " "a total of %.2f seconds of CPU time. %s" msgstr "" "J'ai pris %.2f secondes du temps utilisateur et %.2f secondes du temps " "système, pour un total de %.2f secondes de temps CPU. %s" #: plugin.py:175 msgid "Unable to run ps command." msgstr "Impossible de lancer la commande ps." #: plugin.py:181 msgid " I'm taking up %S of memory." msgstr " Je prend plus de %S de mémoire." #: plugin.py:190 msgid "" "takes no arguments\n" "\n" " Returns some interesting command-related statistics.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne quelques statistiques intéressantes liées aux commandes." #: plugin.py:200 msgid "I offer a total of %n in %n. I have processed %n." msgstr "J'offre un total de %n dans %n plugins. J'ai géré %n." #: plugin.py:209 msgid "" "takes no arguments\n" "\n" " Returns a list of the commands offered by the bot.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne une liste des commandes offertes par le bot." #: plugin.py:223 msgid "" "takes no arguments\n" "\n" " Returns the amount of time the bot has been running.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne la durée durant laquelle le bot est lancée." #: plugin.py:227 msgid "I have been running for %s." msgstr "Je suis lancé depuis %s." #: plugin.py:234 msgid "" "takes no arguments\n" "\n" " Returns the server the bot is on.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne le(s) serveur(s) sur le(s)quel(s) le bot est." #: plugin.py:243 msgid "" "takes no arguments\n" "\n" " Returns the network the bot is on.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne le(s) serveur(s) sur le(s)quel(s) le bot est." ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Status/locales/it.po����������������������������������������������������0000644�0001750�0001750�00000011752�13634634532�020667� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2012-03-16 12:41+0100\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:46 msgid "" "Determines whether the cpu command will list\n" " the time taken by children as well as the bot's process." msgstr "" "Determina se il comando cpu elencherà il tempo impiegato\n" " dal processo figlio così come il processo del bot." #: config.py:49 msgid "" "Determines whether the cpu command will\n" " provide the number of threads spawned and active." msgstr "" "Determina se il comando cpu fornirà il numero di thread avviati e attivi." #: config.py:52 msgid "" "Determines whether the cpu command will report\n" " the amount of memory being used by the bot." msgstr "" "Determina se il comando cpu riporterà la quantità di memoria usata dal bot." #: plugin.py:71 #, docstring msgid "" "takes no arguments\n" "\n" " Returns the status of the bot.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Riporta lo stato del bot.\n" " " #: plugin.py:80 msgid "%s as %L" msgstr "%s come %L" #: plugin.py:81 msgid "I am connected to %L." msgstr "Sono connesso a %L." #: plugin.py:83 msgid "I am currently in code profiling mode." msgstr "Sto analizzando i dati." #: plugin.py:89 #, docstring msgid "" "takes no arguments\n" "\n" " Returns the current threads that are active.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Riporta gli attuali thread attivi.\n" " " #: plugin.py:95 msgid "I have spawned %n; %n %b still currently active: %L." msgstr "Ho avviato %n; %n %b attualmente ancora attivi: %L." #: plugin.py:103 #, docstring msgid "" "takes no arguments\n" "\n" " Returns some interesting network-related statistics.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Riporta alcune statistiche interessanti riguardanti la rete.\n" " " #: plugin.py:111 msgid "an indeterminate amount of time" msgstr "una quantità di tempo indeterminato" #: plugin.py:112 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 "Ho ricevuto %s messaggi per un totale di %S. Ho inviato %s messaggi per un totale di %S. Sono connesso a %s da %s." #: plugin.py:121 #, docstring msgid "" "takes no arguments\n" "\n" " Returns some interesting CPU-related statistics on the bot.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Riporta alcune statistiche interessanti riguardanti la CPU.\n" " " #: plugin.py:131 msgid "My children have taken %.2f seconds of user time and %.2f seconds of system time for a total of %.2f seconds of CPU time." msgstr "I miei processi figli hanno impiegato %.2f secondi di tempo a livello utente e %.2f secondi a livello di sistema per un totale di %.2f secondi di tempo di CPU." #: plugin.py:138 msgid "I have taken %.2f seconds of user time and %.2f seconds of system time, for a total of %.2f seconds of CPU time. %s" msgstr "Ho impiegato %.2f secondi di tempo a livello utente e %.2f secondi a livello di sistema per un totale di %.2f secondi di tempo di CPU. %s" #: plugin.py:160 msgid "Unable to run ps command." msgstr "Impossibile eseguire il comando ps." #: plugin.py:166 msgid " I'm taking up %S of memory." msgstr " Sto impiegando il %S di memoria. " #: plugin.py:175 #, docstring msgid "" "takes no arguments\n" "\n" " Returns some interesting command-related statistics.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Riporta alcune statistiche interessanti riguardanti i comandi.\n" " " #: plugin.py:185 msgid "I offer a total of %n in %n. I have processed %n." msgstr "Offro un totale di %n in %n. Ho elaborato %n." #: plugin.py:194 #, docstring msgid "" "takes no arguments\n" "\n" " Returns a list of the commands offered by the bot.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Restituisce un elenco dei comandi offerti dal bot.\n" " " #: plugin.py:208 #, docstring msgid "" "takes no arguments\n" "\n" " Returns the amount of time the bot has been running.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Riporta da quanto tempo il bot è in esecuzione.\n" " " #: plugin.py:212 msgid "I have been running for %s." msgstr "Sono in funzione da %s." #: plugin.py:219 #, docstring msgid "" "takes no arguments\n" "\n" " Returns the server the bot is on.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Restituisce i server ai quali è connesso il bot.\n" " " #: plugin.py:228 #, docstring msgid "" "takes no arguments\n" "\n" " Returns the network the bot is on.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Restituisce la network alla quale è connesso il bot.\n" " " ����������������������limnoria-2020.03.17/plugins/Status/plugin.py��������������������������������������������������������0000644�0001750�0001750�00000023422�13634634532�020136� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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 os import sys import time import threading import multiprocessing import subprocess import supybot.conf as conf import supybot.utils as utils import supybot.world as world from supybot.commands import * import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Status') class Status(callbacks.Plugin): """This plugin allows you to view different bot statistics, for example, uptime.""" def __init__(self, irc): self.__parent = super(Status, self) self.__parent.__init__(irc) # XXX It'd be nice if these could be kept in the registry. self.sentMsgs = 0 self.recvdMsgs = 0 self.sentBytes = 0 self.recvdBytes = 0 self.connected = {} def __call__(self, irc, msg): self.recvdMsgs += 1 self.recvdBytes += len(msg) self.__parent.__call__(irc, msg) def outFilter(self, irc, msg): self.sentMsgs += 1 self.sentBytes += len(msg) return msg def do001(self, irc, msg): self.connected[irc] = time.time() @internationalizeDocstring def status(self, irc, msg, args): """takes no arguments Returns the status of the bot. """ networks = {} for Irc in world.ircs: networks.setdefault(Irc.network, []).append(Irc.nick) networks = sorted(networks.items()) networks = [format(_('%s as %L'), net, nicks) for (net,nicks) in networks] L = [format(_('I am connected to %L.'), networks)] if world.profiling: L.append(_('I am currently in code profiling mode.')) irc.reply(' '.join(L)) status = wrap(status) @internationalizeDocstring def threads(self, irc, msg, args): """takes no arguments Returns the current threads that are active. """ threads = [t.getName() for t in threading.enumerate()] threads.sort() s = format(_('I have spawned %n; %n %b still currently active: %L.'), (world.threadsSpawned, 'thread'), (len(threads), 'thread'), len(threads), threads) irc.reply(s) threads = wrap(threads) def processes(self, irc, msg, args): """takes no arguments Returns the number of processes that have been spawned, and list of ones that are still active. """ ps = [multiprocessing.current_process().name] ps = ps + [p.name for p in multiprocessing.active_children()] s = format('I have spawned %n; %n %b still currently active: %L.', (world.processesSpawned, 'process'), (len(ps), 'process'), len(ps), ps) irc.reply(s) processes = wrap(processes) def net(self, irc, msg, args): """takes no arguments Returns some interesting network-related statistics. """ try: elapsed = time.time() - self.connected[irc.getRealIrc()] timeElapsed = utils.timeElapsed(elapsed) except KeyError: timeElapsed = _('an indeterminate amount of time') irc.reply(format(_('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.'), self.recvdMsgs, self.recvdBytes, self.sentMsgs, self.sentBytes, irc.server, timeElapsed)) net = wrap(net) @internationalizeDocstring def cpu(self, irc, msg, args): """takes no arguments Returns some interesting CPU-related statistics on the bot. """ (user, system, childUser, childSystem, elapsed) = os.times() now = time.time() target = (msg.channel, irc.network) timeRunning = now - world.startedAt if self.registryValue('cpu.children', *target) and \ user+system < timeRunning+1: # Fudge for FPU inaccuracies. children = _('My children have taken %.2f seconds of user time ' 'and %.2f seconds of system time ' 'for a total of %.2f seconds of CPU time.') % \ (childUser, childSystem, childUser+childSystem) else: children = '' activeThreads = threading.activeCount() response = _('I have taken %.2f seconds of user time and %.2f seconds ' 'of system time, for a total of %.2f seconds of CPU ' 'time. %s') % (user, system, user + system, children) if self.registryValue('cpu.threads', *target): response += format('I have spawned %n; I currently have %i still ' 'running.', (world.threadsSpawned, 'thread'), activeThreads) if self.registryValue('cpu.memory', *target): mem = None pid = os.getpid() plat = sys.platform try: if plat.startswith('linux') or plat.startswith('sunos') or \ plat.startswith('freebsd') or plat.startswith('openbsd') or \ plat.startswith('darwin'): cmd = 'ps -o rss -p %s' % pid try: inst = subprocess.Popen(cmd.split(), close_fds=True, stdin=open(os.devnull), stdout=subprocess.PIPE, stderr=subprocess.PIPE) except OSError: irc.error(_('Unable to run ps command.'), Raise=True) (out, foo) = inst.communicate() inst.wait() mem = int(out.splitlines()[1]) elif sys.platform.startswith('netbsd'): mem = int(os.stat('/proc/%s/mem' % pid)[7]) if mem: response += format(_(' I\'m taking up %S of memory.'), mem*1024) else: response += _(' I\'m taking up an unknown amount of memory.') except Exception: self.log.exception('Uncaught exception in cpu.memory:') irc.reply(utils.str.normalizeWhitespace(response)) cpu = wrap(cpu) @internationalizeDocstring def cmd(self, irc, msg, args): """takes no arguments Returns some interesting command-related statistics. """ commands = 0 callbacksPlugin = 0 for cb in irc.callbacks: if isinstance(cb, callbacks.Plugin): callbacksPlugin += 1 commands += len(cb.listCommands()) s = format(_('I offer a total of %n in %n. I have processed %n.'), (commands, 'command'), (callbacksPlugin, 'command-based', 'plugin'), (world.commandsProcessed, 'command')) irc.reply(s) cmd = wrap(cmd) @internationalizeDocstring def commands(self, irc, msg, args): """takes no arguments Returns a list of the commands offered by the bot. """ commands = set() for cb in irc.callbacks: if isinstance(cb, callbacks.Plugin): for command in cb.listCommands(): commands.add(command) irc.reply(format('%L', sorted(commands))) commands = wrap(commands) @internationalizeDocstring def uptime(self, irc, msg, args): """takes no arguments Returns the amount of time the bot has been running. """ response = _('I have been running for %s.') % \ utils.timeElapsed(time.time() - world.startedAt) irc.reply(response) uptime = wrap(uptime) @internationalizeDocstring def server(self, irc, msg, args): """takes no arguments Returns the server the bot is on. """ irc.reply(irc.server) server = wrap(server) @internationalizeDocstring def network(self, irc, msg, args): """takes no arguments Returns the network the bot is on. """ irc.reply(irc.network) network = wrap(network) Class = Status # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Status/test.py����������������������������������������������������������0000644�0001750�0001750�00000006104�13634634532�017615� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * import sys import supybot.world as world class StatusTestCase(PluginTestCase): plugins = ('Status',) def testNet(self): self.assertNotError('net') def testCpu(self): m = self.assertNotError('status cpu') self.assertFalse('kB kB' in m.args[1]) self.assertFalse('None' in m.args[1], 'None in cpu output: %r.' % m) for s in ['linux', 'freebsd', 'openbsd', 'netbsd', 'darwin']: if sys.platform.startswith(s): self.assertTrue('B' in m.args[1] or 'KB' in m.args[1] or 'MB' in m.args[1], 'No memory string on supported platform.') try: original = conf.supybot.plugins.Status.cpu.get('children')() conf.supybot.plugins.Status.cpu.get('children').setValue(False) self.assertNotRegexp('cpu', 'children') finally: conf.supybot.plugins.Status.cpu.get('children').setValue(original) def testUptime(self): self.assertNotError('uptime') def testCmd(self): self.assertNotError('cmd') def testCommands(self): self.assertNotError('commands') def testLogfilesize(self): self.feedMsg('list') self.feedMsg('list Status') self.assertNotError('upkeep') def testThreads(self): self.assertNotError('threads') def testProcesses(self): self.assertNotError('processes') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/String/�����������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016254� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/String/__init__.py������������������������������������������������������0000644�0001750�0001750�00000004665�13634634532�020372� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Provides various string-related commands. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������limnoria-2020.03.17/plugins/String/config.py��������������������������������������������������������0000644�0001750�0001750�00000006471�13634634532�020075� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('String') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('String', True) String = conf.registerPlugin('String') conf.registerGroup(String, 'levenshtein') conf.registerGlobalValue(String.levenshtein, 'max', registry.PositiveInteger(256, _("""Determines the maximum size of a string given to the levenshtein command. The levenshtein command uses an O(m*n) algorithm, which means that with strings of length 256, it can take 1.5 seconds to finish; with strings of length 384, though, it can take 4 seconds to finish, and with strings of much larger lengths, it takes more and more time. Using nested commands, strings can get quite large, hence this variable, to limit the size of arguments passed to the levenshtein command."""))) conf.registerGroup(String, 're') conf.registerGlobalValue(String.re, 'timeout', registry.PositiveFloat(0.1, _("""Determines the maximum time, in seconds, that a regular expression is given to execute before being terminated. Since there is a possibility that user input for the re command can cause it to eat up large amounts of ram or cpu time, it's a good idea to keep this low. Most normal regexps should not take very long at all."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/String/locales/���������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017676� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/String/locales/fi.po����������������������������������������������������0000644�0001750�0001750�00000020264�13634634532�020632� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# String plugin in Limnoria # Copyright (C) 2011 Limnoria # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: String plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 11:59+EET\n" "PO-Revision-Date: 2014-12-20 12:20+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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-Generator: Poedit 1.6.10\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: config.py:47 msgid "" "Determines the maximum size of a string\n" " given to the levenshtein command. The levenshtein command uses an " "O(m*n)\n" " algorithm, which means that with strings of length 256, it can take 1.5\n" " seconds to finish; with strings of length 384, though, it can take 4\n" " seconds to finish, and with strings of much larger lengths, it takes " "more\n" " and more time. Using nested commands, strings can get quite large, " "hence\n" " this variable, to limit the size of arguments passed to the levenshtein\n" " command." msgstr "" "Määrittää levenshtein komentoon annettavan merkkiketjun maksimimäärän.\n" " Levenshtein komento käyttää O(m*n) algoritmiä, mikä tarkoittaa, että\n" " merkkiketju, jonka koko on 256 voi viedä 1.5\n" " sekuntia valmistumiseen; 384:n merkit piduudella se voi viedä 4\n" " sekuntia valmistumiseen, ja suuremmilla merkkiketjuilla, se vie\n" " enemmän ja enemmän aikaa. Käyttäen sisäkkäisia komentoja, merkkiketjut " "voivat tulla hyvin pitkiksi ja tästä johtuen\n" " tämä asetusarvo on olemassa, rajoittaakseen levenshtein komentoon " "annettujen parametrien määrää." #: config.py:58 msgid "" "Determines the maximum time, in seconds, that\n" " a regular expression is given to execute before being terminated. Since\n" " there is a possibility that user input for the re command can cause it " "to\n" " eat up large amounts of ram or cpu time, it's a good idea to keep this \n" " low. Most normal regexps should not take very long at all." msgstr "" "Määrittää enimmäisajan sekunteissa, joka annetaan säännöllisen lausekkeen\n" " suorittamiseksi, ennen lopetusta. Koska on mahdollista, että käyttäjän " "syöte\n" " re-komennolle voi aiheuttaa suuren RAM- tai CPU-ajan käytön, on hyvä ajatus " "pitää\n" " tämä matalana. Tavallisten säännöllisten lausekkeiden ei pitäisi kestää " "kauan." #: plugin.py:51 msgid "Provides useful commands for manipulating characters and strings." msgstr "" "Tarjoaa hyödyllisiä komentoja merkkien ja merkkiketjujen manipulointiin." #: plugin.py:54 msgid "" "<letter>\n" "\n" " Returns the 8-bit value of <letter>.\n" " " msgstr "" "<kirjain>\n" "\n" " Palauttaa <kirjaimen> 8-bittisen arvon.\n" " " #: plugin.py:63 msgid "" "<number>\n" "\n" " Returns the character associated with the 8-bit value <number>\n" " " msgstr "" "<numero>\n" "\n" " Palauttaa merkin, joka on yhdistetty 8-bittisen <numeron> arvon.\n" " " #: plugin.py:70 msgid "That number doesn't map to an 8-bit character." msgstr "Tuo numero ei kartoitu 8-bittiseen merkkiin." #: plugin.py:75 msgid "" "<encoding> <text>\n" "\n" " Returns an encoded form of the given text; the valid encodings are\n" " available in the documentation of the Python codecs module:\n" " <http://docs.python.org/library/codecs.html#standard-encodings>.\n" " " msgstr "" "<salaus> <teksti>\n" "\n" " Palauttaa salatun version annetusta tekstistä; kelvolliset " "salaukset\n" " ovat saatavilla Python codecs moduulin dokumentaatiossa:\n" " <http://docs.python.org/library/codecs.html#standard-encodings>.\n" " " #: plugin.py:91 plugin.py:128 msgid "encoding" msgstr "salaus" #: plugin.py:110 msgid "" "<encoding> <text>\n" "\n" " Returns an un-encoded form of the given text; the valid encodings " "are\n" " available in the documentation of the Python codecs module:\n" " <http://docs.python.org/library/codecs.html#standard-encodings>.\n" " " msgstr "" "<salaus> <teksti>\n" "\n" " Palauttaa salaamattoman version annetusta tekstistä; kelvolliset " "salaukset\n" " ovat saatavilla Python codecs moduulin dokumentaatiossa:\n" " <http://docs.python.org/library/codecs.html#standard-encodings>.\n" " " #: plugin.py:134 msgid "base64 string" msgstr "base64 merkkiketju" #: plugin.py:135 msgid "" "Base64 strings must be a multiple of 4 in length, padded with '=' if " "necessary." msgstr "" "Base64 merkkiketjujen täytyy olla kerrollisia 4:llä, pehmustettuna '='-" "merkillä jos vaadittu." #: plugin.py:151 msgid "" "<string1> <string2>\n" "\n" " Returns the levenshtein distance (also known as the \"edit distance" "\"\n" " between <string1> and <string2>)\n" " " msgstr "" "<mekkiketju1> <merkkiketju2>\n" "\n" " Palauttaa levenshtein etäisyyden (tunnetaan myös nimellä \"muokkaus " "etäisyys\"\n" " <merkkiketju1:den> ja <merkkiketju2:den> välillä.)\n" " " #: plugin.py:158 msgid "" "Levenshtein distance is a complicated algorithm, try it with some smaller " "inputs." msgstr "" "Levenshtein etäisyys on monimutkainen algoritmi, kokeile sitä pienemmillä " "sisäänmenoilla." #: plugin.py:166 #, fuzzy msgid "" "<string> [<length>]\n" "\n" " Returns the Soundex hash to a given length. The length defaults to\n" " 4, since that's the standard length for a soundex hash. For " "unlimited\n" " length, use 0. Maximum length 1024.\n" " " msgstr "" "<merkkiketju> [<pituus>]\n" "\n" " Palauttaa Soundex hashin annetulle pituudelle. Pituus on oletuksena " "4, koska se on\n" " peruspituus. Jos haluat loputtoman pituuden,\n" " käytä 0:aa.\n" " " #: plugin.py:180 msgid "" "<text>\n" "\n" " Returns the length of <text>.\n" " " msgstr "" "<teksti>\n" "\n" " Palauttaa <tekstin> pituuden.\n" " " #: plugin.py:189 msgid "" "<regexp> <text>\n" "\n" " If <regexp> is of the form m/regexp/flags, returns the portion of\n" " <text> that matches the regexp. If <regexp> is of the form\n" " s/regexp/replacement/flags, returns the result of applying such a\n" " regexp to <text>.\n" " " msgstr "" "<säännöllinen lauseke> <teksti>\n" "\n" " Jos <säännöllinen lauseke> on yksi muodosta m/säännöllinen lauseke/" "liput, palauta\n" " <tekstin> osa, joka täsmää säännölliseen lausekkeeseen. Jos " "<säännöllinen lauseke> on muotoa\n" " s/säännöllinen lauseke/korvaus/liput, palauttaa sellaisen " "säännöllisen lausekkeen käytön\n" " <tekstiin>.\n" " " #: plugin.py:201 msgid "You probably don't want to match the empty string." msgstr "Et luultavasti halua täsmätä tyhjään merkkiketjuun." #: plugin.py:216 msgid "" "<password> <text>\n" "\n" " Returns <text> XOR-encrypted with <password>. See\n" " http://www.yoe.org/developer/xor.html for information about XOR\n" " encryption.\n" " " msgstr "" "<salasana> <teksti>\n" "\n" " Palauttaa <tekstin> XOR-salattuna <salasanalla>. Katso\n" " http://www.yoe.org/developer/xor.html saadaksesi lisätietoja XOR\n" " salauksesta.\n" " " #: plugin.py:229 msgid "" "<text>\n" "\n" " Returns the md5 hash of a given string. Read\n" " http://www.rsasecurity.com/rsalabs/faq/3-6-6.html for more " "information\n" " about md5.\n" " " msgstr "" "<teksti>\n" "\n" " Palauttaa md5 hashin annetusta merkkiketjusta. Lue\n" " http://www.rsasecurity.com/rsalabs/faq/3-6-6.html saadaksesi " "lisätietoja\n" " md5:stä.\n" " " #: plugin.py:240 msgid "" "<text>\n" "\n" " Returns the SHA hash of a given string. Read\n" " http://www.secure-hash-algorithm-md5-sha-1.co.uk/ for more " "information\n" " about SHA.\n" " " msgstr "" "<teksti>\n" "\n" " Palauttaa SHA hashin annetulle merkkiketjulle. Lue\n" " http://www.secure-hash-algorithm-md5-sha-1.co.uk/ saadaksesi\n" " lisätietoa SHA:sta.\n" " " ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/String/locales/fr.po����������������������������������������������������0000644�0001750�0001750�00000016751�13634634532�020651� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-01-22 13:38+CET\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: Limnoria <progval@gmail.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" "Language: fr\n" #: config.py:47 msgid "" "Determines the maximum size of a string\n" " given to the levenshtein command. The levenshtein command uses an " "O(m*n)\n" " algorithm, which means that with strings of length 256, it can take 1.5\n" " seconds to finish; with strings of length 384, though, it can take 4\n" " seconds to finish, and with strings of much larger lengths, it takes " "more\n" " and more time. Using nested commands, strings can get quite large, " "hence\n" " this variable, to limit the size of arguments passed to the levenshtein\n" " command." msgstr "" "Détermine la taille maximum d'une chaîne donnée à la commande 'levenshtein'. " "Cette commande utiliser un algorithme O(m*n), ce qui signifie que pour une " "chaîne de taille 256, il peut prendre 1,5 seconde à se finir ; pour les " "chaînes de taille 384, il peut en prendre 4, et ainsi de suite. En utilisant " "des commandes inmbriquées, les chaînes peuvent devenir très grandes, donc, " "utilisez cette variable pour limiter la taille des arguments passés à la " "commande 'levenshtein'." #: config.py:58 msgid "" "Determines the maximum time, in seconds, that\n" " a regular expression is given to execute before being terminated. Since\n" " there is a possibility that user input for the re command can cause it " "to\n" " eat up large amounts of ram or cpu time, it's a good idea to keep this \n" " low. Most normal regexps should not take very long at all." msgstr "" "Détermine le temps maximum, en seconde, que peut prendre l’exécution d’une " "expression rationnelle avant qu’elle ne soit terminée. Étant donné qu’il est " "possible aux utilisateurs de donner des paramètres à la commande « re » qui " "font qu’elle utilise beaucoup de RAM ou de temps CPU, c’est une bonne idée " "de garder ce temps assez bas. L’exécution de la plupart des expressions " "rationnelles normales n’est pas longue du tout." #: plugin.py:52 msgid "" "<letter>\n" "\n" " Returns the 8-bit value of <letter>.\n" " " msgstr "" "<lettre>\n" "\n" "Retourne la valeur 8 bits de la <lettre>." #: plugin.py:61 msgid "" "<number>\n" "\n" " Returns the character associated with the 8-bit value <number>\n" " " msgstr "" "<nombre>\n" "\n" "Retourne le caractère associé à la valeur 8-bits du <nombre>" #: plugin.py:68 msgid "That number doesn't map to an 8-bit character." msgstr "Ce nombre ne correspond pas à un caractère 8 bits." #: plugin.py:73 msgid "" "<encoding> <text>\n" "\n" " Returns an encoded form of the given text; the valid encodings are\n" " available in the documentation of the Python codecs module:\n" " <http://docs.python.org/library/codecs.html#standard-encodings>.\n" " " msgstr "" "<encodage> <texte>\n" "\n" "Retourne la forme encodée du texte donnée ; les encodages valides sont " "disponibles dans la documentation du module codec de Python : http://docs." "python.org/library/codecs.html#standard-encodings" #: plugin.py:89 plugin.py:126 msgid "encoding" msgstr "encodage" #: plugin.py:108 msgid "" "<encoding> <text>\n" "\n" " Returns an un-encoded form of the given text; the valid encodings " "are\n" " available in the documentation of the Python codecs module:\n" " <http://docs.python.org/library/codecs.html#standard-encodings>.\n" " " msgstr "" "<encodage> <texte>\n" "\n" "Retourne la forme désencodée du texte donnée ; les encodages valides sont " "disponibles dans la documentation du module codec de Python : http://docs." "python.org/library/codecs.html#standard-encodings" #: plugin.py:132 msgid "base64 string" msgstr "chaîne base64" #: plugin.py:133 msgid "" "Base64 strings must be a multiple of 4 in length, padded with '=' if " "necessary." msgstr "" "Les chaînes base64 doivent avoir une longueur multiple de 4, entourées de " "'=' si nécessaire." #: plugin.py:149 msgid "" "<string1> <string2>\n" "\n" " Returns the levenshtein distance (also known as the \"edit distance" "\"\n" " between <string1> and <string2>)\n" " " msgstr "" "<chaîne 1> <chaîne 2>\n" "\n" "Retourne la distance levenhtein (aussi connue sous le nom de \"distance " "d'édition) entre les deux chaînes." #: plugin.py:156 msgid "" "Levenshtein distance is a complicated algorithm, try it with some smaller " "inputs." msgstr "" "La distance levenshtein est un algorithme compliqué ; essayez avec des " "données plus petites." #: plugin.py:164 msgid "" "<string> [<length>]\n" "\n" " Returns the Soundex hash to a given length. The length defaults to\n" " 4, since that's the standard length for a soundex hash. For " "unlimited\n" " length, use 0. Maximum length 1024.\n" " " msgstr "" "<chaîne> [<longueur>]\n" "\n" "Retourne un hash Soundex de la longueur donnée. La longueur par défaut est " "de 4, c'est le standard pour un hash soundex. Pour une longueur illimitée, " "utilisez 0. La longueur maximum est 1024." #: plugin.py:178 msgid "" "<text>\n" "\n" " Returns the length of <text>.\n" " " msgstr "" "<texte>\n" "\n" "Retourne la longueur du <texte>." #: plugin.py:187 msgid "" "<regexp> <text>\n" "\n" " If <regexp> is of the form m/regexp/flags, returns the portion of\n" " <text> that matches the regexp. If <regexp> is of the form\n" " s/regexp/replacement/flags, returns the result of applying such a\n" " regexp to <text>.\n" " " msgstr "" "<expression régulière> <texte>\n" "\n" "Si l'<expression régulière> est de la forme de m/regexp/flags, retourne la " "portion du <texte> correspondant à l'expression régulière. Si l'<expression " "régulière> est de la forme de s/regexp/remplacement/flags, retourne le " "résultat de l'application de l'expression régulière sur le <texte>." #: plugin.py:199 msgid "You probably don't want to match the empty string." msgstr "" "Vous ne voulez probablement appliquer une expression régulière sur une " "chaîne vide." #: plugin.py:212 msgid "" "<password> <text>\n" "\n" " Returns <text> XOR-encrypted with <password>. See\n" " http://www.yoe.org/developer/xor.html for information about XOR\n" " encryption.\n" " " msgstr "" "<mot de passe> <texte>\n" "\n" "Retourne le <texte>, encrypté par la méthode XOR avec le <mot de passe>. " "Lisez http://www.yoe.org/developer/xor.html pour plus d'informations sur le " "cryptage XOR." #: plugin.py:225 msgid "" "<text>\n" "\n" " Returns the md5 hash of a given string. Read\n" " http://www.rsasecurity.com/rsalabs/faq/3-6-6.html for more " "information\n" " about md5.\n" " " msgstr "" "<texte>\n" "\n" "Retourne le hash md5 de la chaîne donnée. Lisez http://www.rsasecurity.com/" "rsalabs/faq/3-6-6.html pour plus d'informations à propos de md5." #: plugin.py:236 msgid "" "<text>\n" "\n" " Returns the SHA hash of a given string. Read\n" " http://www.secure-hash-algorithm-md5-sha-1.co.uk/ for more " "information\n" " about SHA.\n" " " msgstr "" "<texte>\n" "\n" "Retourne le hash SDA de la chaîne donnée. Lisez http://www.secure-hash-" "algorithm-md5-sha-1.co.uk/ pour plus d'informations à propos de SHA." �����������������������limnoria-2020.03.17/plugins/String/locales/it.po����������������������������������������������������0000644�0001750�0001750�00000015504�13634634532�020651� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-07-07 11:35+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:47 msgid "" "Determines the maximum size of a string\n" " given to the levenshtein command. The levenshtein command uses an O(m*n)\n" " algorithm, which means that with strings of length 256, it can take 1.5\n" " seconds to finish; with strings of length 384, though, it can take 4\n" " seconds to finish, and with strings of much larger lengths, it takes more\n" " and more time. Using nested commands, strings can get quite large, hence\n" " this variable, to limit the size of arguments passed to the levenshtein\n" " command." msgstr "" "Determina la dimensione massima di una stringa passata al comando \"levenshtein\".\n" " Quest'ultimo utilizza un algoritmo O(m*n), significa che con stringhe di lunghezza\n" " 256, può impiegare 1.5 secondi a terminare; con stringhe lunghe 384, sebbene,\n" " impiega 4 secondi e con stringhe più lunghe impiega molto più tempo. Utilizzando\n" " comandi nidificati le stringhe possono essere abbastanza grosse; questa variabile\n" " esiste per limitare la dimensione degli argomenti passati al comando \"levenshtein\"." #: plugin.py:46 #, docstring msgid "" "<letter>\n" "\n" " Returns the 8-bit value of <letter>.\n" " " msgstr "" "<lettera>\n" "\n" " Restituisce il valore 8-bit di <lettera>.\n" " " #: plugin.py:55 #, docstring msgid "" "<number>\n" "\n" " Returns the character associated with the 8-bit value <number>\n" " " msgstr "" "<numero>\n" "\n" " Restituisce il carattere associato al valore 8-bit di <numero>\n" " " #: plugin.py:62 msgid "That number doesn't map to an 8-bit character." msgstr "Questo numero non corrisponde a un carattere 8-bit." #: plugin.py:67 #, docstring msgid "" "<encoding> <text>\n" "\n" " Returns an encoded form of the given text; the valid encodings are\n" " available in the documentation of the Python codecs module:\n" " <http://docs.python.org/library/codecs.html#standard-encodings>.\n" " " msgstr "" "<codifica> <testo>\n" "\n" " Restituisce la forma codificata del testo specificato; le codifiche\n" " valide sono disponibili nella documentazione del modulo Python codecs:\n" " <http://docs.python.org/library/codecs.html#standard-encodings>.\n" " " #: plugin.py:76 plugin.py:90 msgid "encoding" msgstr "codifica" #: plugin.py:81 #, docstring msgid "" "<encoding> <text>\n" "\n" " Returns an un-encoded form of the given text; the valid encodings are\n" " available in the documentation of the Python codecs module:\n" " <http://docs.python.org/library/codecs.html#standard-encodings>.\n" " " msgstr "" "<codifica> <testo>\n" "\n" " Restituisce la forma non codificata del testo specificato; le codifiche\n" " valide sono disponibili nella documentazione del modulo Python codecs:\n" " <http://docs.python.org/library/codecs.html#standard-encodings>.\n" " " #: plugin.py:92 msgid "base64 string" msgstr "stringa base64" #: plugin.py:93 msgid "Base64 strings must be a multiple of 4 in length, padded with '=' if necessary." msgstr "Le stringhe in base64 devono avere una lunghezza in multipli di 4, circondate da \"=\" se necessario." #: plugin.py:99 #, docstring msgid "" "<string1> <string2>\n" "\n" " Returns the levenshtein distance (also known as the \"edit distance\"\n" " between <string1> and <string2>)\n" " " msgstr "" "<stringa1> <stringa2>\n" "\n" " Riporta la distanza levenshtein (anche conosciuta come \"distanza di\n" " modifica\" tra <stringa1> e <stringa2>)\n" " " #: plugin.py:106 msgid "Levenshtein distance is a complicated algorithm, try it with some smaller inputs." msgstr "La distanza levenshtein è un algoritmo complesso, prova a inserire meno dati." #: plugin.py:114 #, docstring msgid "" "<string> [<length>]\n" "\n" " Returns the Soundex hash to a given length. The length defaults to\n" " 4, since that's the standard length for a soundex hash. For unlimited\n" " length, use 0.\n" " " msgstr "" "<stringa> [<lunghezza>]\n" "\n" " Restituisce l'hash Soundex alla lunghezza specificata. In modo predefinito\n" " è impostata a 4, giacché è la lunghezza standard per questo tipo di hash.\n" " Per lunghezze illimitate usa 0.\n" " " #: plugin.py:125 #, docstring msgid "" "<text>\n" "\n" " Returns the length of <text>.\n" " " msgstr "" "<testo>\n" "\n" " Riporta la lunghezza di <testo>.\n" " " #: plugin.py:134 #, docstring msgid "" "<regexp> <text>\n" "\n" " If <regexp> is of the form m/regexp/flags, returns the portion of\n" " <text> that matches the regexp. If <regexp> is of the form\n" " s/regexp/replacement/flags, returns the result of applying such a\n" " regexp to <text>.\n" " " msgstr "" "<regexp> <testo>\n" "\n" " Se <regexp> è nella forma m/regexp/flags, restituisce la porzione\n" " di <testo> che corrisponde alla regexp. Se non lo è, restituisce\n" " il risultato dell'applicare la regexp a <testo>.\n" " " #: plugin.py:146 msgid "You probably don't want to match the empty string." msgstr "È probabile che tu non voglia confrontare una stringa vuota." #: plugin.py:156 #, docstring msgid "" "<password> <text>\n" "\n" " Returns <text> XOR-encrypted with <password>. See\n" " http://www.yoe.org/developer/xor.html for information about XOR\n" " encryption.\n" " " msgstr "" "<password> <testo>\n" "\n" " Restituisce <testo> cifrato con XOR con <password>. Vedi\n" " http://www.yoe.org/developer/xor.html per informazioni sulla cifratura XOR.\n" " " #: plugin.py:169 #, docstring msgid "" "<text>\n" "\n" " Returns the md5 hash of a given string. Read\n" " http://www.rsasecurity.com/rsalabs/faq/3-6-6.html for more information\n" " about md5.\n" " " msgstr "" "<testo>\n" "\n" " Restituisce l'hash md5 di una data stringa. Vedi\n" " http://www.rsasecurity.com/rsalabs/faq/3-6-6.html per ulteriori informazioni su md5.\n" " " #: plugin.py:180 #, docstring msgid "" "<text>\n" "\n" " Returns the SHA hash of a given string. Read\n" " http://www.secure-hash-algorithm-md5-sha-1.co.uk/ for more information\n" " about SHA.\n" " " msgstr "" "<testo>\n" "\n" " Restituisce l'hash SHA di una data stringa. Vedi\n" " http://www.secure-hash-algorithm-md5-sha-1.co.uk/ per ulteriori informazioni su SHA.\n" " " ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/String/plugin.py��������������������������������������������������������0000644�0001750�0001750�00000021762�13634634532�020126� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Jeremiah Fincher # Copyright (c) 2008-2009, James McCoy # 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 re import sys import types import codecs import base64 import binascii import supybot.utils as utils from supybot.commands import * import supybot.utils.minisix as minisix import supybot.plugins as plugins import supybot.commands as commands import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('String') import multiprocessing class String(callbacks.Plugin): """Provides useful commands for manipulating characters and strings.""" @internationalizeDocstring def ord(self, irc, msg, args, letter): """<letter> Returns the 8-bit value of <letter>. """ irc.reply(str(ord(letter))) ord = wrap(ord, ['letter']) @internationalizeDocstring def chr(self, irc, msg, args, i): """<number> Returns the character associated with the 8-bit value <number> """ try: irc.reply(chr(i), stripCtcp=False) except ValueError: irc.error(_('That number doesn\'t map to an 8-bit character.')) chr = wrap(chr, ['int']) @internationalizeDocstring def encode(self, irc, msg, args, encoding, text): """<encoding> <text> Returns an encoded form of the given text; the valid encodings are available in the documentation of the Python codecs module: <http://docs.python.org/library/codecs.html#standard-encodings>. """ # Binary codecs are prefixed with _codec in Python 3 if encoding in 'base64 bz2 hex quopri uu zlib': encoding += '_codec' if encoding.endswith('_codec'): text = text.encode() # Do the encoding try: encoder = codecs.getencoder(encoding) except LookupError: irc.errorInvalid(_('encoding'), encoding) text = encoder(text)[0] # If this is a binary codec, re-encode it with base64 if encoding.endswith('_codec') and encoding != 'base64_codec': text = codecs.getencoder('base64_codec')(text)[0].decode() # Change result into a string if minisix.PY2 and isinstance(text, unicode): text = text.encode('utf-8') elif minisix.PY3 and isinstance(text, bytes): text = text.decode() if encoding in ('base64', 'base64_codec'): text = text.replace('\n', '') # Reply irc.reply(text.rstrip('\n')) encode = wrap(encode, ['something', 'text']) @internationalizeDocstring def decode(self, irc, msg, args, encoding, text): """<encoding> <text> Returns an un-encoded form of the given text; the valid encodings are available in the documentation of the Python codecs module: <http://docs.python.org/library/codecs.html#standard-encodings>. """ # Binary codecs are prefixed with _codec in Python 3 if encoding in 'base64 bz2 hex quopri uu zlib': encoding += '_codec' # If this is a binary codec, pre-decode it with base64 if encoding.endswith('_codec') and encoding != 'base64_codec': text = codecs.getdecoder('base64_codec')(text.encode())[0] # Do the decoding try: decoder = codecs.getdecoder(encoding) except LookupError: irc.errorInvalid(_('encoding'), encoding) if minisix.PY3 and not isinstance(text, bytes): text = text.encode() try: text = decoder(text)[0] except binascii.Error: irc.errorInvalid(_('base64 string'), s=_('Base64 strings must be a multiple of 4 in ' 'length, padded with \'=\' if necessary.')) return # Change result into a string if minisix.PY2 and isinstance(text, unicode): text = text.encode('utf-8') elif minisix.PY3 and isinstance(text, bytes): try: text = text.decode() except UnicodeDecodeError: pass # Reply irc.reply(text) decode = wrap(decode, ['something', 'text']) @internationalizeDocstring def levenshtein(self, irc, msg, args, s1, s2): """<string1> <string2> Returns the levenshtein distance (also known as the "edit distance" between <string1> and <string2>) """ max = self.registryValue('levenshtein.max') if len(s1) > max or len(s2) > max: irc.error(_('Levenshtein distance is a complicated algorithm, try ' 'it with some smaller inputs.')) else: irc.reply(str(utils.str.distance(s1, s2))) levenshtein = thread(wrap(levenshtein, ['something', 'text'])) @internationalizeDocstring def soundex(self, irc, msg, args, text, length): """<string> [<length>] Returns the Soundex hash to a given length. The length defaults to 4, since that's the standard length for a soundex hash. For unlimited length, use 0. Maximum length 1024. """ if length > 1024: irc.error("Maximum allowed length is 1024.") return irc.reply(utils.str.soundex(text, length)) soundex = wrap(soundex, ['somethingWithoutSpaces', additional('int', 4)]) @internationalizeDocstring def len(self, irc, msg, args, text): """<text> Returns the length of <text>. """ irc.reply(str(len(text))) len = wrap(len, ['text']) @internationalizeDocstring def re(self, irc, msg, args, f, text): """<regexp> <text> If <regexp> is of the form m/regexp/flags, returns the portion of <text> that matches the regexp. If <regexp> is of the form s/regexp/replacement/flags, returns the result of applying such a regexp to <text>. """ if f('') and len(f(' ')) > len(f(''))+1: # Matches the empty string. s = _('You probably don\'t want to match the empty string.') irc.error(s) else: t = self.registryValue('re.timeout') try: v = process(f, text, timeout=t, pn=self.name(), cn='re') if isinstance(v, list): v = format('%L', v) irc.reply(v) except commands.ProcessTimeoutError as e: irc.error("ProcessTimeoutError: %s" % (e,)) except re.error as e: irc.error(e.args[0]) re = thread(wrap(re, [first('regexpMatcherMany', 'regexpReplacer'), 'text'])) def xor(self, irc, msg, args, password, text): """<password> <text> Returns <text> XOR-encrypted with <password>. """ chars = utils.iter.cycle(password) ret = [chr(ord(c) ^ ord(next(chars))) for c in text] irc.reply(''.join(ret)) xor = wrap(xor, ['something', 'text']) @internationalizeDocstring def md5(self, irc, msg, args, text): """<text> Returns the md5 hash of a given string. """ irc.reply(utils.crypt.md5(text.encode('utf8')).hexdigest()) md5 = wrap(md5, ['text']) @internationalizeDocstring def sha(self, irc, msg, args, text): """<text> Returns the SHA1 hash of a given string. """ irc.reply(utils.crypt.sha(text.encode('utf8')).hexdigest()) sha = wrap(sha, ['text']) Class = String # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������limnoria-2020.03.17/plugins/String/test.py����������������������������������������������������������0000644�0001750�0001750�00000014727�13634634532�017612� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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 re from supybot.test import * import supybot.utils as utils nicks = ['fatjim','scn','moshez','LordVan','MetaCosm','pythong','fishfart', 'alb','d0rt','jemfinch','StyxAlso','fors','deltab','gd', 'hellz_hunter','are_j|pub_comp','jason_','dreid','sayke_','winjer', 'TenOfTen','GoNoVas','queuetue','the|zzz','Hellfried','Therion', 'shro','DaCa','rexec','polin8','r0ky','aaron_','ironfroggy','eugene', 'faassen','tirloni','mackstann','Yhg1s','ElBarono','vegai','shang', 'typo_','kikoforgetme','asqui','TazyTiggy','fab','nixman','liiwi', 'AdamV','paolo','red_one','_AleX_','lament','jamessan','supybot', 'macr0_zzz','plaisthos','redghost','disco','mphardy','gt3','mathie', 'jonez','r0ky-office','tic','d33p','ES3merge','talin','af','flippo', 'sholden','ameoba','shepherg','j2','Acapnotic','dash','merlin262', 'Taaus','_moshez','rik','jafo__','blk-majik','JT__','itamar', 'kermit-','davidmccabe','glyph','jojo','dave_p','goo','hyjinx', 'SamB','exarkun','drewp','Ragica','skylan','redgore','k3','Ra1stlin', 'StevenK','carball','h3x','carljm','_jacob','teratorn','frangen', 'phed','datazone','Yaggo','acct_','nowhere','pyn','ThomasWaldmann', 'dunker','pilotLight','brainless','LoganH_','jmpnz','steinn', 'EliasREC','lowks__','OldSmrf','Mad77','snibril','delta','psy', 'skimpIzu','Kengur','MoonFallen','kotkis','Hyperi'] def group(seq, groupSize, noneFill=True): """Groups a given sequence into sublists of length groupSize.""" ret = [] L = [] i = groupSize for elt in seq: if i > 0: L.append(elt) else: ret.append(L) i = groupSize L = [] L.append(elt) i -= 1 if L: if noneFill: while len(L) < groupSize: L.append(None) ret.append(L) return ret class StringTestCase(PluginTestCase): plugins = ('String', 'Format', 'Status') def testLen(self): self.assertResponse('len foo', '3') self.assertHelp('len') def testNoErrors(self): self.assertNotError('levenshtein Python Perl') def testSoundex(self): self.assertNotError('soundex jemfinch') self.assertNotRegexp('soundex foobar 3:30', 'ValueError') def testChr(self): for i in range(256): c = chr(i) regexp = r'%s|%s' % (re.escape(c), re.escape(repr(c))) self.assertRegexp('chr %s' % i, regexp) def testOrd(self): for c in map(chr, range(256)): i = ord(c) self.assertResponse('ord %s' % utils.str.dqrepr(c), str(i)) def testMd5(self): self.assertResponse('md5 supybot', '1360578d1276e945cc235654a53f9c65') def testEncodeDecode(self): # This no longer works correctly. It almost seems like were throwing # in a repr() somewhere. s = 'the recalcitrant jamessan tests his scramble function' self.assertNotRegexp('encode aldkfja foobar', 'LookupError') self.assertNotRegexp('decode asdflkj foobar', 'LookupError') self.assertResponse('decode zlib [encode zlib %s]' % s, s) self.assertRegexp('decode base64 $BCfBg7;9D;R(B', 'padded with') def testRe(self): self.assertResponse('re "m/system time/" [status cpu]', 'system time') self.assertResponse('re s/user/luser/g user user', 'luser luser') self.assertResponse('re s/user/luser/ user user', 'luser user') self.assertNotRegexp('re m/foo/ bar', 'has no attribute') self.assertResponse('re m/a\\S+y/ "the bot angryman is hairy"', 'angry') self.assertResponse('re m/a\\S+y/g "the bot angryman is hairy"', 'angry and airy') def testReNotEmptyString(self): self.assertError('re s//foo/g blah') def testReWorksWithJustCaret(self): self.assertResponse('re s/^/foo/ bar', 'foobar') def testReNoEscapingUnpackListOfWrongSize(self): self.assertNotRegexp('re foo bar baz', 'unpack list of wrong size') def testReBug850931(self): self.assertResponse(r're s/\b(\w+)\b/\1./g foo bar baz', 'foo. bar. baz.') def testNotOverlongRe(self): self.assertError('re [strjoin "" s/./ [eval \'xxx\'*400]] blah blah') def testXor(self): # This no longer works correctly. It almost seems like were throwing # in a repr() somewhere. L = [nick for nick in nicks if '|' not in nick and '[' not in nick and ']' not in nick] for s0, s1, s2, s3, s4, s5, s6, s7, s8, s9 in group(L, 10): data = '%s%s%s%s%s%s%s%s%s' % (s0, s1, s2, s3, s4, s5, s6, s7, s8) self.assertResponse('xor %s [xor %s %s]' % (s9, s9, data), data) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������limnoria-2020.03.17/plugins/Success/����������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016416� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Success/__init__.py�����������������������������������������������������0000644�0001750�0001750�00000004731�13634634532�020526� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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. ### """ The Success plugin spices up success replies just like Dunno spices up "no such command" replies. """ 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__ = "" __author__ = supybot.authors.strike __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=8 expandtab textwidth=78: ���������������������������������������limnoria-2020.03.17/plugins/Success/config.py�������������������������������������������������������0000644�0001750�0001750�00000005223�13634634532�020231� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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 from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Success') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Success', True) Success = conf.registerPlugin('Success') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Success, 'someConfigVariableName', # registry.Boolean(False, _("""Help for someConfigVariableName."""))) conf.registerChannelValue(conf.supybot.plugins.Success, 'prefixNick', registry.Boolean(True, _("""Determines whether the bot will prefix the nick of the user giving an invalid command to the success response."""))) # vim:set shiftwidth=4 softtabstop=8 expandtab textwidth=78 �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Success/locales/��������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�020040� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Success/locales/fi.po���������������������������������������������������0000644�0001750�0001750�00000003000�13634634532�020761� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Success plugin in Limnoria. # Copyright (C) 2011 Limnoria # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: \n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-10-25 15:18+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\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" #: config.py:49 msgid "" "Determines whether the bot will prefix the nick\n" " of the user giving an invalid command to the success response." msgstr "Määrittää lisääkö botti käyttäjän nimimerkin lähettämänsä viestin alkuun, jos\n" " käyttäjä antaa viallisen komennon onnistumisvastaukselle." #: plugin.py:38 msgid "" "This plugin was written initially to work with MoobotFactoids, the two\n" " of them to provide a similar-to-moobot-and-blootbot interface for factoids.\n" " Basically, it replaces the standard 'The operation succeeded.' messages\n" " with messages kept in a database, able to give more personable\n" " responses." msgstr "Tämä lisäosa kirjoitettiin alunperin toimimaan MoobotFactoids lisäosan kanssa, molemmat kaksi\n" " tarjoaisivat samankaltainen-kuin-moobot-ja-blootbot käyttöliittymän factoideille.\n" " Perusteellisesti, se korvaa perus 'Tehtävä suoritettu.' viestit\n" " vastauksilla tietokannoista, antaen mahdollisuuden luoda persoonallisempia\n" " vastauksia." limnoria-2020.03.17/plugins/Success/locales/fr.po���������������������������������������������������0000644�0001750�0001750�00000002577�13634634532�021014� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2010-10-20 09:09+CEST\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\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" #: config.py:49 msgid "" "Determines whether the bot will prefix the nick\n" " of the user giving an invalid command to the success response." msgstr "Détermine si le bot utilisera le nick de l'utilisateur donnant une commande invalide comme préfixe pour la réponse de Success." #: plugin.py:39 msgid "" "This plugin was written initially to work with MoobotFactoids, the two\n" " of them to provide a similar-to-moobot-and-blootbot interface for factoids.\n" " Basically, it replaces the standard 'The operation succeeded.' messages\n" " with messages kept in a database, able to give more personable\n" " responses." msgstr "Ce plugin était à l'origine écrit pour fonctionner avec MoobotFactoids, qui fournit une interface similaire à celle de moobot et blootbot pour les factoids. Il remplacement le message 'the operation succeeded.' par un message stoché dans la base de données, ce qui vous permet d'en avoir un plus personnalisable." ���������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Success/locales/it.po���������������������������������������������������0000644�0001750�0001750�00000002457�13634634532�021016� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-12 18:43+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:49 msgid "" "Determines whether the bot will prefix the nick\n" " of the user giving an invalid command to the success response." msgstr "" "Determina se il bot userà il nick dell'utente che dà un comando non\n" " valido come prefisso per la risposta di successo." #: plugin.py:38 #, docstring msgid "" "This plugin was written initially to work with MoobotFactoids, the two\n" " of them to provide a similar-to-moobot-and-blootbot interface for factoids.\n" " Basically, it replaces the standard 'The operation succeeded.' messages\n" " with messages kept in a database, able to give more personable\n" " responses." msgstr "" "Questo plugin fu inizialmente scritto per funzionare con MoobotFactoids, per\n" " fornire un'interfaccia simile ai factoid di moobot e blootbot.\n" " Sostituisce i messaggi standard \"L'operazione è riuscita.\"\n" " con quelli contenuti in un database, in modo da rendere le risposte più piacevoli." �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Success/plugin.py�������������������������������������������������������0000644�0001750�0001750�00000006543�13634634532�020270� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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 from supybot.commands import * import supybot.plugins as plugins import supybot.ircutils as ircutils from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Success') class Success(plugins.ChannelIdDatabasePlugin): """This plugin was written initially to work with MoobotFactoids, the two of them to provide a similar-to-moobot-and-blootbot interface for factoids. Basically, it replaces the standard 'The operation succeeded.' messages with messages kept in a database, able to give more personable responses.""" def __init__(self, irc): self.__parent = super(Success, self) self.__parent.__init__(irc) self.target = None pluginSelf = self self.originalClass = conf.supybot.replies.success.__class__ class MySuccessClass(self.originalClass): __slots__ = () def __call__(self): msg = dynamic.msg ret = pluginSelf.db.random(msg.channel or msg.args[0]) if ret is None: try: self.__class__ = pluginSelf.originalClass ret = self() finally: self.__class__ = MySuccessClass else: ret = ret.text return ret def get(self, attr): if irc.isChannel(attr): pluginSelf.target = attr return self conf.supybot.replies.success.__class__ = MySuccessClass def die(self): self.__parent.die() conf.supybot.replies.success.__class__ = self.originalClass Success = internationalizeDocstring(Success) Class = Success # vim:set shiftwidth=4 softtabstop=8 expandtab textwidth=78: �������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Success/test.py���������������������������������������������������������0000644�0001750�0001750�00000004264�13634634532�017747� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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 from supybot.test import * class SuccessTestCase(ChannelPluginTestCase): plugins = ('Success', 'User') def setUp(self): ChannelPluginTestCase.setUp(self) self.prefix = 'mf2!bar@baz' self.irc.feedMsg(ircmsgs.privmsg(self.nick, 'register tester moo', prefix=self.prefix)) def testSuccess(self): self.assertResponse('success add success1:', 'The operation succeeded.') self.assertResponse('success add success2:', 'success1: Success #1 added.') # vim:set shiftwidth=4 softtabstop=8 expandtab textwidth=78: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Time/�������������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�015704� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Time/__init__.py��������������������������������������������������������0000644�0001750�0001750�00000005105�13634634532�020010� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ A plugin for time-related functions. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {supybot.authors.progval: ['tztime'], supybot.Author('Ken Spencer', 'kspencer', 'iota@electrocode.net'): ['ddate']} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Time/config.py����������������������������������������������������������0000644�0001750�0001750�00000005044�13634634532�017520� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Time') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Time', True) Time = conf.registerPlugin('Time') conf.registerChannelValue(Time, 'format', registry.String(str(conf.supybot.reply.format.time()), _("""Determines the format string for timestamps. Refer to the Python documentation for the time module to see what formats are accepted. If you set this variable to the empty string, the timestamp will not be shown."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Time/locales/�����������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017326� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Time/locales/de.po������������������������������������������������������0000644�0001750�0001750�00000010504�13634634532�020250� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2012-03-11 20:58+UTC\n" "PO-Revision-Date: 2012-04-27 15:34+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: German <fbesser@gmail.com>\n" "Language: de\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" #: config.py:45 msgid "" "Determines the\n" " format string for timestamps. Refer to the Python documentation for the\n" " time module to see what formats are accepted. If you set this variable to\n" " the empty string, the timestamp will not be shown." msgstr "Legt die Formatiertung der Zeichenkette für Zeitstempel fest. Siehe Python Dokumentation für das time Modul um zu sehen welche Formate akzeptiert werden. Falls du diese Variable auf eine leere Zeichenkette setzt, wird kein Zeitstempel angezeigt." #: plugin.py:61 msgid "" "[<years>y] [<weeks>w] [<days>d] [<hours>h] [<minutes>m] [<seconds>s]\n" "\n" " Returns the number of seconds in the number of <years>, <weeks>,\n" " <days>, <hours>, <minutes>, and <seconds> given. An example usage is\n" " \"seconds 2h 30m\", which would return 9000, which is '3600*2 + 30*60'.\n" " Useful for scheduling events at a given number of seconds in the\n" " future.\n" " " msgstr "" "[<Jahre>y] [<Wochen>w] [<Tage>d] [<Stunden>h] [<Minuten>m] [<Sekunden>s]\n" "\n" "Gibt die Sekunden von der Anzahl von <Jahren>, <Wochen>, <Tagen>, <Stunden>, <Minuten> und Sekunden an. Ein Beispiel ist \"seconds 2h 30m\", das würde 9000 zurückgeben, dass ist '3600*2 + 30*60'. Nützlich um Events zu einem Zeitpunkt von Sekunden in der Zukunft zu planen." #: plugin.py:96 msgid "" "<time string>\n" "\n" " Returns the number of seconds since epoch <time string> is.\n" " <time string> can be any number of natural formats; just try something\n" " and see if it will work.\n" " " msgstr "" "<Zeit Zeichenkette>\n" "\n" "Gibt die " #: plugin.py:107 #: plugin.py:123 msgid "That's right now!" msgstr "Das ist jetzt" #: plugin.py:112 msgid "" "<time string>\n" "\n" " Returns the number of seconds until <time string>.\n" " " msgstr "" "<Zeit Zeichenkette>\n" "\n" "Gibt die Nummer der Sekunden bis <Zeit Zeichenkette> zurück." #: plugin.py:128 msgid "" "[<seconds since epoch>]\n" "\n" " Returns the ctime for <seconds since epoch>, or the current ctime if\n" " no <seconds since epoch> is given.\n" " " msgstr "" "[<Sekunden seit Beginn der Unix Zeitrechnung>]\n" "\n" "Gibt ctime for <Sekunden seit Beginn der Unix Zeitrechnung> an oder die momentane ctime, falls <Sekunden seit Beginn der Unix Zeitrechnung> nicht angegeben wurde." #: plugin.py:134 msgid "number of seconds since epoch" msgstr "Sekunden seit Beginn der Unix Zeitrechnung" #: plugin.py:139 msgid "" "[<format>] [<seconds since epoch>]\n" "\n" " Returns the current time in <format> format, or, if <format> is not\n" " given, uses the configurable format for the current channel. If no\n" " <seconds since epoch> time is given, the current time is used.\n" " " msgstr "" "<Format> [<Sekunden seit Beginn der Unix Zeitrechnung>]\n" "\n" "Gibt die Zeit im <Format> an oder falls die <Format> nicht angegeben wird, wird das konfigurierte Format für den Kanal benutzt. Falls <Sekunden seit Beginn der Unix Zeitrechnung> nicht angegeben ist, wird die momentante Zeit benutzt. " #: plugin.py:156 msgid "" "<seconds>\n" "\n" " Returns a pretty string that is the amount of time represented by\n" " <seconds>.\n" " " msgstr "" "<Sekunden>\n" "\n" "Gibt eine schöne Zeichenkette zurück die die Zeitspanne von <Sekunden> repräsentiert." #: plugin.py:166 msgid "" "<region>/<city>\n" "\n" " Takes a city and its region, and returns the locale time." msgstr "" "<Region>/<Stadt>\n" "\n" "Gibt die lokale zeit von <Region>/<Stadt> zurück" #: plugin.py:172 msgid "Python-tz is required by the command, but is not installed on this computer." msgstr "Python-tz wird für diesen Befehl benötigt, ist aber nicht installiert." #: plugin.py:177 msgid "Unknown timezone" msgstr "Unbekannte Zeitzone" #~ msgid "A timezone must be in the format region/city." #~ msgstr "Es muss eine Zeitzone im format Region/Stadt angegeben werden." ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Time/locales/fi.po������������������������������������������������������0000644�0001750�0001750�00000013102�13634634532�020253� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Time plugin in Limnoria. # Copyright (C) 2011 Limnoria # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: Time plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 13:29+EET\n" "PO-Revision-Date: 2014-12-20 13:43+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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-Generator: Poedit 1.6.10\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: config.py:45 msgid "" "Determines the\n" " format string for timestamps. Refer to the Python documentation for " "the\n" " time module to see what formats are accepted. If you set this variable " "to\n" " the empty string, the timestamp will not be shown." msgstr "" "Määrittää\n" " muotoketjun aikaleimoille. Katso Python documentaatiosta\n" " aikamoduulin kohdalta nähdäksesi, mitkä muodot hyväksytään. Jos asetat " "tämän tyhjäksi\n" " merkkiketjuksi, aikaleimaa ei näytetä." #: plugin.py:67 msgid "This plugin allows you to use different time-related functions." msgstr "Tämä plugini sallii erilaisten aikaan liittyvien funktioiden käytön." #: plugin.py:70 msgid "" "[<years>y] [<weeks>w] [<days>d] [<hours>h] [<minutes>m] [<seconds>s]\n" "\n" " Returns the number of seconds in the number of <years>, <weeks>,\n" " <days>, <hours>, <minutes>, and <seconds> given. An example usage " "is\n" " \"seconds 2h 30m\", which would return 9000, which is '3600*2 + " "30*60'.\n" " Useful for scheduling events at a given number of seconds in the\n" " future.\n" " " msgstr "" "[<vuosia>y] [<viikkoja>w] [<days>d] [<hours>h] [<minutes>m] [<seconds>s]\n" "\n" " Palauttaa annettujen <vuosien>, <viikkojen>,\n" " <päivien>, <tuntien>, <minuuttien>, ja <sekuntejen> määrän " "sekunteina. Esimerkki käyttö on\n" " \"seconds 2h 30m\", joka palauttaisi 9000, joka on '3600*2 + " "30*60'.\n" " Hyödyllinen ajastamaan tapahtumia annettujen sekuntien päästä\n" " tulevaisuudessa.\n" " " #: plugin.py:105 #, fuzzy msgid "" "[<time string>]\n" "\n" " Returns the number of seconds since epoch <time string> is.\n" " <time string> can be any number of natural formats; just try " "something\n" " and see if it will work.\n" " If the <time string> is not given, defaults to now.\n" " " msgstr "" "<aika merkkiketju>\n" "\n" " Palauttaa sen määrän sekunteja ajanlaskun alusta, kuin " "<aikamerkkiketju> on.\n" " <Aika merkkiketju> voi olla mikä tahansa määrä luonnollisia " "muodostelmia; kokeile vain jotakin\n" " ja katso toimiiko se.\n" " " #: plugin.py:116 plugin.py:133 msgid "" "This command is not available on this bot, ask the owner to install the " "python-dateutil library." msgstr "" "Tämä komento ei ole saatavilla tällä botilla. Pyydä omistajaa asentamaan " "python-dateutil sovelluskirjasto." #: plugin.py:123 plugin.py:142 msgid "That's right now!" msgstr "Se on juuri nyt!" #: plugin.py:128 msgid "" "<time string>\n" "\n" " Returns the number of seconds until <time string>.\n" " " msgstr "" "<aika merkkiketju>\n" "\n" " Palauttaa sekuntien määrän <aika merkkiketjuun>.\n" " " #: plugin.py:147 msgid "" "[<seconds since epoch>]\n" "\n" " Returns the ctime for <seconds since epoch>, or the current ctime " "if\n" " no <seconds since epoch> is given.\n" " " msgstr "" "[<sekunteja ajanlaskun alkamisen jälkeen>]\n" "\n" " Palauttaa ctimen <sekunteja ajanlaskun alkamisen jälkeen>, tai " "nykyisen ctimen, jos\n" " <sekunteja ajanlaskun alkamisen jälkeen> on annettu.\n" " " #: plugin.py:153 msgid "number of seconds since epoch" msgstr "sekuntien määrä ajanlaskun alkamisen jälkeen" #: plugin.py:158 msgid "" "[<format>] [<seconds since epoch>]\n" "\n" " Returns the current time in <format> format, or, if <format> is not\n" " given, uses the configurable format for the current channel. If no\n" " <seconds since epoch> time is given, the current time is used.\n" " " msgstr "" "[<muoto>] [<sekunteja ajanlaskun alkamisen jälkeen>]\n" "\n" " Palauttaa nykyisen ajan <muodon> muodossa, tai, jos <format> ei ole " "annettu\n" " käyttää muokattavaa muotoa nykyiselle kanavalle. Jos\n" " <sekunteja ajanlaskun alkamisen jälkeen> ei ole annettu, käytetään " "nykyistä aikaa.\n" " " #: plugin.py:181 msgid "" "<seconds>\n" "\n" " Returns a pretty string that is the amount of time represented by\n" " <seconds>.\n" " " msgstr "" "<sekunteja>\n" "\n" " Palauttaa nätin merkkiketjun, joka on <sekunteilla>\n" " esitetty.\n" " " #: plugin.py:191 msgid "" "<region>/<city>\n" "\n" " Takes a city and its region, and returns the locale time. This \n" " command uses the IANA Time Zone Database." msgstr "" "<alue>/<kaupunki>\n" "\n" " Näyttää kaupungin ja sen osan paikallisen ajan. Tämä komento käyttää\n" " IANAn aikavyöhyketietokantaa." #: plugin.py:198 msgid "" "Python-tz is required by the command, but is not installed on this computer." msgstr "" "Python-tz on vaadittu tämän komennon käyttämiseen, mutta se ei ole " "asennettuna tälle tietokoneelle." #: plugin.py:204 msgid "Unknown timezone" msgstr "Tuntematon aikavyöhyke" #~ msgid "A timezone must be in the format region/city." #~ msgstr "Aikavyöhykkeen täytyy olla muodossa alue/kaupunki." ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Time/locales/fr.po������������������������������������������������������0000644�0001750�0001750�00000011144�13634634532�020270� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-01-21 16:48+CET\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: Limnoria <progval@gmail.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" "Language: fr\n" #: config.py:45 msgid "" "Determines the\n" " format string for timestamps. Refer to the Python documentation for " "the\n" " time module to see what formats are accepted. If you set this variable " "to\n" " the empty string, the timestamp will not be shown." msgstr "" "Détermine la chaîne de format pour les timestamps. Référez-vous à la " "documentation du module Python time pour voir quels formats sont acceptés. " "Si vous définissez cette variable à une chaîne vide, le timestamp ne sera " "pas affiché." #: plugin.py:64 msgid "" "[<years>y] [<weeks>w] [<days>d] [<hours>h] [<minutes>m] [<seconds>s]\n" "\n" " Returns the number of seconds in the number of <years>, <weeks>,\n" " <days>, <hours>, <minutes>, and <seconds> given. An example usage " "is\n" " \"seconds 2h 30m\", which would return 9000, which is '3600*2 + " "30*60'.\n" " Useful for scheduling events at a given number of seconds in the\n" " future.\n" " " msgstr "" "[<années>y] [<semaines>w] [<jours>d] [<heures>h] [<minutes>m] [<secondes>s]\n" "\n" "Retourne le nombre de secondes de la date donnée. Un exemple d'utilisation " "est \"seconds 2h 30m\", ce qui retournera 9000. Utile pour planifier des " "évènement à un certain nombre de secondes dans le futur." #: plugin.py:99 msgid "" "[<time string>]\n" "\n" " Returns the number of seconds since epoch <time string> is.\n" " <time string> can be any number of natural formats; just try " "something\n" " and see if it will work.\n" " If the <time string> is not given, defaults to now.\n" " " msgstr "" "<moment>\n" "\n" "Retourne le nombre de secondes depuis le <moment>. Le <moment> peut être " "n'importe lequel des formats naturels, essayez seulement, et voyez si ça " "marche.<moment> vaut l’instant actuel par défaut." #: plugin.py:110 plugin.py:127 msgid "" "This command is not available on this bot, ask the owner to install the " "python-dateutil library." msgstr "" "Cette commande n’est pas disponible sur ce bot, demandez au/à la " "propriétaire d’installer la bibliothèque python-dateutil." #: plugin.py:117 plugin.py:136 msgid "That's right now!" msgstr "C'est maintenant !" #: plugin.py:122 msgid "" "<time string>\n" "\n" " Returns the number of seconds until <time string>.\n" " " msgstr "" "<moment>\n" "\n" "Retourne le nombre de secondes depuis le <moment>." #: plugin.py:141 msgid "" "[<seconds since epoch>]\n" "\n" " Returns the ctime for <seconds since epoch>, or the current ctime " "if\n" " no <seconds since epoch> is given.\n" " " msgstr "" "[<temps>]\n" "\n" " Renvoie le ctime pour le moment il y a <temps> secondes, ou le " "ctime courant, si aucun <temps> n'est donné." #: plugin.py:147 msgid "number of seconds since epoch" msgstr "nombre de secondes depuis une époque" #: plugin.py:152 msgid "" "[<format>] [<seconds since epoch>]\n" "\n" " Returns the current time in <format> format, or, if <format> is not\n" " given, uses the configurable format for the current channel. If no\n" " <seconds since epoch> time is given, the current time is used.\n" " " msgstr "" "[<format>] [<temps>]\n" "\n" "Retourne le temps courant dans le <format>, ou, si le <format> n'est pas " "donné, utilise le format actuellement configuré pour le canal. Si le <temps> " "n'est pas donné, le temps actuel est utilisé." #: plugin.py:169 msgid "" "<seconds>\n" "\n" " Returns a pretty string that is the amount of time represented by\n" " <seconds>.\n" " " msgstr "" "<secondes>\n" "\n" "Retourne le nombre de <secondes>, joliement formatté." #: plugin.py:179 msgid "" "<region>/<city>\n" "\n" " Takes a city and its region, and returns the locale time." msgstr "" "<zone>/<ville>\n" "\n" "Prend en argument une ville et sa zone, et retourne le temps local." #: plugin.py:185 msgid "" "Python-tz is required by the command, but is not installed on this computer." msgstr "" "Python-tz est requis par la commande, mais n'est pas installé sur cet " "ordinateur." #: plugin.py:191 msgid "Unknown timezone" msgstr "Timezone inconnue" #~ msgid "A timezone must be in the format region/city." #~ msgstr "Une timezone doit être dans le format zone/ville." ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Time/locales/hu.po������������������������������������������������������0000644�0001750�0001750�00000011255�13634634532�020300� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Limnoria Time plugin. # Copyright (C) 2011 Limnoria # nyuszika7h <litemininyuszika@gmail.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: Limnoria Time\n" "POT-Creation-Date: 2012-03-11 20:58+UTC\n" "PO-Revision-Date: 2012-04-27 14:49+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: \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" #: config.py:45 msgid "" "Determines the\n" " format string for timestamps. Refer to the Python documentation for the\n" " time module to see what formats are accepted. If you set this variable to\n" " the empty string, the timestamp will not be shown." msgstr "Meghatározza az időbélyegzők formátum karakterláncát. Lásd a time modul Python dokumentációját, hogy lásd, milyen formátumok fogadhatók el. Ha ezt a változót üres karakterláncra állítod, az időbélygező nem lesz megjelenítve." #: plugin.py:61 msgid "" "[<years>y] [<weeks>w] [<days>d] [<hours>h] [<minutes>m] [<seconds>s]\n" "\n" " Returns the number of seconds in the number of <years>, <weeks>,\n" " <days>, <hours>, <minutes>, and <seconds> given. An example usage is\n" " \"seconds 2h 30m\", which would return 9000, which is '3600*2 + 30*60'.\n" " Useful for scheduling events at a given number of seconds in the\n" " future.\n" " " msgstr "" "[<évek>y] [<hetek>w] [<napok>d] [<órák>h] [<percek>m] [<másodpercek>s]\n" "\n" "Kiírja a másodpercek számát a megadott számú <évek>-ben, <hetek>-ben, <napok>-ban, <órák>-ban és <másodperc>-ekben. Egy lehetőség \"seconds 2h 30m\", amely 9000-et írna ki, ami '3600*2 + 30*60'. Hasznos az események időzítésére megadott számú másodpercek múlva a jövőben." #: plugin.py:96 msgid "" "<time string>\n" "\n" " Returns the number of seconds since epoch <time string> is.\n" " <time string> can be any number of natural formats; just try something\n" " and see if it will work.\n" " " msgstr "" "<időkarakterlánc>\n" "\n" "Kiírja az epoch (1970. január 1. 0:00) óta eltelt másodpercek számát az <időkarakterlánc> alapján. Az <időkarakterlány> lehet bármilyen természetes formátum; csak próbálj ki valamit, és nézd meg, hogy működik-e." #: plugin.py:107 #: plugin.py:123 msgid "That's right now!" msgstr "Ez éppen most van!" #: plugin.py:112 msgid "" "<time string>\n" "\n" " Returns the number of seconds until <time string>.\n" " " msgstr "" "<időkarakterlánc>\n" "\n" "Kiírja, az <időkarakterlánc>-ig hátralévő másodpercek számát." #: plugin.py:128 msgid "" "[<seconds since epoch>]\n" "\n" " Returns the ctime for <seconds since epoch>, or the current ctime if\n" " no <seconds since epoch> is given.\n" " " msgstr "" "[<epoch óta eltelt másodpercek>]\n" "\n" "Kiírja az időkarakterláncot az <epoch óta eltelt másodpercek>-hez, vagy a jelenlegi időt ha nincs <epoch óta eltelt másodpercek> megadva." #: plugin.py:134 msgid "number of seconds since epoch" msgstr "epoch óta eltelt másodpercek száma" #: plugin.py:139 msgid "" "[<format>] [<seconds since epoch>]\n" "\n" " Returns the current time in <format> format, or, if <format> is not\n" " given, uses the configurable format for the current channel. If no\n" " <seconds since epoch> time is given, the current time is used.\n" " " msgstr "" "[<formátum>] [<epoch óta eltelt másodpercek>]\n" "\n" "Kiírja a jelenlegi időt <formátum> formátumban, vagy, ha a <formátum> nincs megadva, a konfigurálható formátumot használja a jelenlegi csatornához. Ha nincs <epoch üta eltelt másodpercek> idő megadva, a jelenlegi idő használt." #: plugin.py:156 msgid "" "<seconds>\n" "\n" " Returns a pretty string that is the amount of time represented by\n" " <seconds>.\n" " " msgstr "" "<másodpercek>\n" "\n" "Kiír egy szép karakterláncot, amely a <másodpercek> által képviselt idő mennyisége." #: plugin.py:166 msgid "" "<region>/<city>\n" "\n" " Takes a city and its region, and returns the locale time." msgstr "" "<régió>/<város>\n" "\n" "Egy várost és a régióját fogadja, és kiírja az időzóna szerinti időt." #: plugin.py:172 msgid "Python-tz is required by the command, but is not installed on this computer." msgstr "A Python-tz szükséges ehhez a parancshoz, de nincs telepítve ezen a számítógépen." #: plugin.py:177 msgid "Unknown timezone" msgstr "Ismeretlen időzóna" #~ msgid "A timezone must be in the format region/city." #~ msgstr "Egy időzónának régió/város formátumban kell lennie." ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Time/locales/it.po������������������������������������������������������0000644�0001750�0001750�00000011012�13634634532�020267� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2012-04-23 19:37+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:45 msgid "" "Determines the\n" " format string for timestamps. Refer to the Python documentation for the\n" " time module to see what formats are accepted. If you set this variable to\n" " the empty string, the timestamp will not be shown." msgstr "" "Determina il formato per i timestamp. Per sapere quali sono i formati validi\n" " fai riferimento alla documentazione di Python per il modulo time. Se si assegna\n" " una stringa vuota a questa variabile, il timestamp non verrà mostrato." #: plugin.py:61 #, docstring msgid "" "[<years>y] [<weeks>w] [<days>d] [<hours>h] [<minutes>m] [<seconds>s]\n" "\n" " Returns the number of seconds in the number of <years>, <weeks>,\n" " <days>, <hours>, <minutes>, and <seconds> given. An example usage is\n" " \"seconds 2h 30m\", which would return 9000, which is '3600*2 + 30*60'.\n" " Useful for scheduling events at a given number of seconds in the\n" " future.\n" " " msgstr "" "[<anni>y] [<settimane>w] [<giorni>d] [<ore>h] [<minuti>m] [<secondi>s]\n" "\n" " Riporta il numero di secondi equivalenti al numero di <anni>, <settimane>,\n" " <giorni>, <ore>, <minuti> e <secondi> specificato. Un esempio di utilizzo è\n" " \"seconds 2h 30m\", che produce 9000, ovvero \"3600*2 + 30*60\".\n" " Utile per pianificare eventi ad un certo numero di secondi nel futuro.\n" " " #: plugin.py:96 #, docstring msgid "" "<time string>\n" "\n" " Returns the number of seconds since epoch <time string> is.\n" " <time string> can be any number of natural formats; just try something\n" " and see if it will work.\n" " " msgstr "" "<tempo>\n" "\n" " Riporta il numero di secondi di <tempo> a partire dall'epoca (Unix time).\n" " <tempo> può essere un qualsiasi numero dei consueti formati; basta provare\n" " qualcosa e vedere se funziona.\n" " " #: plugin.py:107 plugin.py:123 msgid "That's right now!" msgstr "È adesso!" #: plugin.py:112 #, docstring msgid "" "<time string>\n" "\n" " Returns the number of seconds until <time string>.\n" " " msgstr "" "<tempo>\n" "\n" " Riporta il numero di secondi fino a <tempo>.\n" " " #: plugin.py:128 #, docstring msgid "" "[<seconds since epoch>]\n" "\n" " Returns the ctime for <seconds since epoch>, or the current ctime if\n" " no <seconds since epoch> is given.\n" " " msgstr "" "[<secondi dall'epoca>]\n" "\n" " Riporta il ctime per <secondi dall'epoca> o, se non specificati,\n" " l'attuale ctime (epoca equivale all'Unix time).\n" " " #: plugin.py:134 msgid "number of seconds since epoch" msgstr "numero di secondi a partire dall'epoca (Unix time)" #: plugin.py:139 #, docstring msgid "" "[<format>] [<seconds since epoch>]\n" "\n" " Returns the current time in <format> format, or, if <format> is not\n" " given, uses the configurable format for the current channel. If no\n" " <seconds since epoch> time is given, the current time is used.\n" " " msgstr "" "[<formato>] [<secondi dall'epoca>]\n" "\n" " Riporta l'attuale orario nel formato <formato>, oppure, se non\n" " specificato, utilizza il formato configurabile per l'attuale canale.\n" " Se il parametro <secondi dall'epoca> non è specificato, viene utilizzato\n" " l'orario attuale (epoca equivale all'Unix time).\n" " " #: plugin.py:156 #, docstring msgid "" "<seconds>\n" "\n" " Returns a pretty string that is the amount of time represented by\n" " <seconds>.\n" " " msgstr "" "<secondi>\n" "\n" " Riporta l'ammontare di <secondi> in una stringa ben formattata.\n" " " #: plugin.py:166 #, docstring msgid "" "<region>/<city>\n" "\n" " Takes a city and its region, and returns the locale time." msgstr "" "<zona>/<città>\n" "\n" " Prende come argomento una città e la sua zona e restituisce l'ora locale." #: plugin.py:172 msgid "Python-tz is required by the command, but is not installed on this computer." msgstr "Il comando richiede Python-tz ma non è installato sul computer." #: plugin.py:177 msgid "Unknown timezone" msgstr "Fuso orario sconosciuto" ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Time/plugin.py����������������������������������������������������������0000644�0001750�0001750�00000021756�13634634532�017561� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import sys import time TIME = time # For later use. from datetime import datetime import supybot.conf as conf import supybot.log as log import supybot.utils as utils from supybot.commands import * import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Time') try: from ddate.base import DDate as _ddate except ImportError: log.debug("Time: the ddate module is not available; disabling that command.") _ddate = None try: from dateutil import parser def parse(s): todo = [] s = s.replace('noon', '12:00') s = s.replace('midnight', '00:00') if 'tomorrow' in s: todo.append(lambda i: i + 86400) s = s.replace('tomorrow', '') if 'next week' in s: todo.append(lambda i: i + 86400*7) s = s.replace('next week', '') i = int(time.mktime(parser.parse(s, fuzzy=True).timetuple())) for f in todo: i = f(i) return i except ImportError: parse = None try: from dateutil.tz import tzlocal except ImportError: tzlocal = None try: import pytz except ImportError: pytz = None class Time(callbacks.Plugin): """This plugin allows you to use different time-related functions.""" @internationalizeDocstring def seconds(self, irc, msg, args): """[<years>y] [<weeks>w] [<days>d] [<hours>h] [<minutes>m] [<seconds>s] Returns the number of seconds in the number of <years>, <weeks>, <days>, <hours>, <minutes>, and <seconds> given. An example usage is "seconds 2h 30m", which would return 9000, which is '3600*2 + 30*60'. Useful for scheduling events at a given number of seconds in the future. """ if not args: raise callbacks.ArgumentError seconds = 0 for arg in args: if not arg or arg[-1] not in 'ywdhms': raise callbacks.ArgumentError (s, kind) = arg[:-1], arg[-1] try: i = int(s) except ValueError: irc.errorInvalid('argument', arg, Raise=True) if kind == 'y': seconds += i*31536000 elif kind == 'w': seconds += i*604800 elif kind == 'd': seconds += i*86400 elif kind == 'h': seconds += i*3600 elif kind == 'm': seconds += i*60 elif kind == 's': seconds += i irc.reply(str(seconds)) @internationalizeDocstring def at(self, irc, msg, args, s=None): """[<time string>] Returns the number of seconds since epoch <time string> is. <time string> can be any number of natural formats; just try something and see if it will work. If the <time string> is not given, defaults to now. """ if not s or s == 'now': irc.reply(str(int(time.time()))) return if not parse: irc.error(_('This command is not available on this bot, ask the ' 'owner to install the python-dateutil library.'), Raise=True) now = int(time.time()) new = parse(s) if new != now: irc.reply(str(new)) else: irc.error(_('That\'s right now!')) at = wrap(at, [optional('text')]) @internationalizeDocstring def until(self, irc, msg, args, s): """<time string> Returns the number of seconds until <time string>. """ if not parse: irc.error(_('This command is not available on this bot, ask the ' 'owner to install the python-dateutil library.'), Raise=True) now = int(time.time()) new = parse(s) if new != now: if new - now < 0: new += 86400 irc.reply(str(new-now)) else: irc.error(_('That\'s right now!')) until = wrap(until, ['text']) @internationalizeDocstring def ctime(self, irc, msg, args, seconds): """[<seconds since epoch>] Returns the ctime for <seconds since epoch>, or the current ctime if no <seconds since epoch> is given. """ irc.reply(time.ctime(seconds)) ctime = wrap(ctime,[additional(('int', _('number of seconds since epoch')), TIME.time)]) @internationalizeDocstring def time(self, irc, msg, args, channel, format, seconds): """[<channel>] [<format>] [<seconds since epoch>] Returns the current time in <format> format, or, if <format> is not given, uses the configurable format for the current channel. If no <seconds since epoch> time is given, the current time is used. If <channel> is given without <format>, uses the format for <channel>. """ if not format: format = self.registryValue('format', channel or msg.channel, irc.network) if tzlocal: irc.reply(datetime.fromtimestamp(seconds, tzlocal()).strftime(format)) else: # NOTE: This has erroneous behavior on some older Python versions, # including at least up to 2.7.5 and 3.2.3. Install dateutil if you # can't upgrade Python. irc.reply(time.strftime(format, time.localtime(seconds))) time = wrap(time, [optional('channel'), optional('nonInt'), additional('float', TIME.time)]) @internationalizeDocstring def elapsed(self, irc, msg, args, seconds): """<seconds> Returns a pretty string that is the amount of time represented by <seconds>. """ irc.reply(utils.timeElapsed(seconds)) elapsed = wrap(elapsed, ['int']) @internationalizeDocstring def tztime(self, irc, msg, args, timezone): """<region>/<city> Takes a city and its region, and returns its local time. This command uses the IANA Time Zone Database.""" if pytz is None: irc.error(_('Python-tz is required by the command, but is not ' 'installed on this computer.'), Raise=True) try: timezone = pytz.timezone(timezone) except pytz.UnknownTimeZoneError: irc.error(_('Unknown timezone'), Raise=True) format = self.registryValue("format", msg.channel, irc.network) irc.reply(datetime.now(timezone).strftime(format)) tztime = wrap(tztime, ['text']) def ddate(self, irc, msg, args, year=None, month=None, day=None): """[<year> <month> <day>] Returns a the Discordian date today, or an optional different date.""" if _ddate is not None: if year is not None and month is not None and day is not None: try: irc.reply(_ddate(datetime(year=year, month=month, day=day))) except ValueError as e: irc.error("%s", e) else: irc.reply(_ddate()) else: irc.error(format(_("The 'ddate' module is not installed. Use " "'%s -m pip install --user ddate' or see " "%u for more information."), sys.executable, "https://pypi.python.org/pypi/ddate/")) ddate = wrap(ddate, [optional('positiveint'), optional('positiveint'), optional('positiveint')]) Class = Time # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ������������������limnoria-2020.03.17/plugins/Time/test.py������������������������������������������������������������0000644�0001750�0001750�00000010100�13634634532�017217� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * try: import pytz except ImportError: has_pytz = False else: has_pytz = True try: import dateutil except ImportError: has_dateutil = False else: has_dateutil = True try: import ddate.base except ImportError: has_ddate = False else: has_ddate = True try: from unittest import skipIf except ImportError: # Python 2.6 def skipIf(cond, reason): if cond: print('Skipped: %s' % reason) def decorator(f): return None else: def decorator(f): return f return decorator class TimeTestCase(PluginTestCase): plugins = ('Time','Utilities') def testSeconds(self): self.assertResponse('seconds 1s', '1') self.assertResponse('seconds 10s', '10') self.assertResponse('seconds 1m', '60') self.assertResponse('seconds 1m 1s', '61') self.assertResponse('seconds 1h', '3600') self.assertResponse('seconds 1h 1s', '3601') self.assertResponse('seconds 1d', '86400') self.assertResponse('seconds 1d 1s', '86401') self.assertResponse('seconds 2s', '2') self.assertResponse('seconds 2m', '120') self.assertResponse('seconds 2d 2h 2m 2s', '180122') self.assertResponse('seconds 1s', '1') self.assertResponse('seconds 1y 1s', '31536001') self.assertResponse('seconds 1w 1s', '604801') def testNoErrors(self): self.assertNotError('ctime') self.assertNotError('time %Y') @skipIf(not has_pytz, 'pytz is missing') def testTztime(self): self.assertNotError('tztime Europe/Paris') self.assertError('tztime Europe/Gniarf') @skipIf(not has_dateutil, 'python-dateutil is missing') def testUntil(self): self.assertNotError('echo [until 4:00]') self.assertNotError('echo [at now]') def testNoNestedErrors(self): self.assertNotError('echo [seconds 4m]') @skipIf(not has_ddate, 'ddate is missing') def testDDate(self): self.assertNotError('ddate') self.assertHelp('ddate 0 0 0') # because nonsense was put in self.assertHelp('ddate -1 1 1') # because nonsense was put in self.assertHelp('ddate -1 -1 -1') # because nonsense was put in # plugin.py:223 would catch these otherwise self.assertResponse('ddate 1 1 1', 'Sweetmorn, the 1st day of Chaos in the YOLD 1167') # make sure the laws of physics and time aren't out of wack # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Todo/�������������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�015713� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Todo/__init__.py��������������������������������������������������������0000644�0001750�0001750�00000005003�13634634532�020014� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Daniel DiPaolo # 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. ### """ The Todo plugin allows registered users to keep their own personal list of tasks to do, with an optional priority for each. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.strike __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Todo/config.py����������������������������������������������������������0000644�0001750�0001750�00000005117�13634634532�017530� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Daniel DiPaolo # 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 from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Todo') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Todo', True) Todo = conf.registerPlugin('Todo') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Todo, 'someConfigVariableName', # registry.Boolean(False, _("""Help for someConfigVariableName."""))) conf.registerGlobalValue(Todo, 'allowThirdpartyReader', registry.Boolean(False, _("""Determines whether users can read the todo-list of another user."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Todo/locales/�����������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017335� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Todo/locales/de.po������������������������������������������������������0000644�0001750�0001750�00000010464�13634634532�020264� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2011-08-10 11:28+CEST\n" "PO-Revision-Date: 2011-11-05 17:29+0100\n" "Last-Translator: Florian Besser <fbesser@gmail.com>\n" "Language-Team: German <fbesser@gmail.com>\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" "Language: de\n" #: config.py:50 msgid "" "Determines whether users can read the\n" " todo-list of another user." msgstr "Legt fest ob Benutzer die Aufgabenlisten anderes Benutzer lesen können." #: plugin.py:135 msgid "" "[<username>] [<task id>]\n" "\n" " Retrieves a task for the given task id. If no task id is given, it\n" " will return a list of task ids that that user has added to their todo\n" " list.\n" " " msgstr "" "[<Benutzername>] [<Aufgaben ID>]\n" "\n" "Empfängt die Aufgabe für die gegebene Aufgaben ID. Falls keine Aufgaben ID angegeben wird, wird eine Liste der Aufgaben IDs ausgegeben, die der Benutzer zu seiner Aufgabenliste hinzugefügt hat." #: plugin.py:146 msgid "You are not allowed to see other users todo-list." msgstr "Du bist nicht berechtigt, Aufgabenlisten anderer Benutzer zu sehen." #: plugin.py:153 msgid "#%i: %s" msgstr "#%i: %s" #: plugin.py:158 msgid "%s for %s: %L" msgstr "%s für %s: %L" #: plugin.py:162 msgid "That user has no tasks in their todo list." msgstr "Der Benutzer hat keine Aufgaben in seiner Aufgabenliste." #: plugin.py:164 msgid "You have no tasks in your todo list." msgstr "Du hast keine Aufgaben in deiner Aufgabenliste." #: plugin.py:171 msgid "Active" msgstr "Aktiv" #: plugin.py:173 msgid "Inactive" msgstr "Inaktiv" #: plugin.py:175 msgid ", priority: %i" msgstr ", Priorität: %i" #: plugin.py:178 msgid "%s todo for %s: %s (Added at %s)" msgstr "%s Aufgabe für %s: %s (hinzugefügt am %s)" #: plugin.py:182 #: plugin.py:263 #: plugin.py:277 msgid "task id" msgstr "Aufgaben ID" #: plugin.py:187 msgid "" "[--priority=<num>] <text>\n" "\n" " Adds <text> as a task in your own personal todo list. The optional\n" " priority argument allows you to set a task as a high or low priority.\n" " Any integer is valid.\n" " " msgstr "" "[--priority=<Nummer>] <Text>\n" "\n" "Fügt <Text> als Aufgabe deiner persönlinen Aufgabenliste hinzu. Das optionale Prioritätsargument, erlaubt dir deiner Aufgabe eine hohe oder niedrige Priorität zuzuweisen. Jede Ganzzahl ist zulässig." #: plugin.py:198 msgid "(Todo #%i added)" msgstr "(Aufgabe #%i hinzugefügt)" #: plugin.py:204 msgid "" "<task id> [<task id> ...]\n" "\n" " Removes <task id> from your personal todo list.\n" " " msgstr "" "<Aufgaben ID> [<Aufgaben ID> ...]\n" "\n" "Entfernt <Aufgaben ID> von deiner persönlichen Aufgabenliste." #: plugin.py:215 msgid "Task %i could not be removed either because that id doesn't exist or it has been removed already." msgstr "Aufgabe %i konnte nicht entfernt werden, da entweder die ID nicht existiert oder sie bereits entfernt wurde." #: plugin.py:219 msgid "No tasks were removed because the following tasks could not be removed: %L." msgstr "Es wurden keine Aufgaben entfernt, da die folgenen Aufgaben nicht entfernt werden konnten: %L." #: plugin.py:229 msgid "" "[--{regexp} <value>] [<glob> <glob> ...]\n" "\n" " Searches your todos for tasks matching <glob>. If --regexp is given,\n" " its associated value is taken as a regexp and matched against the\n" " tasks.\n" " " msgstr "" "[--{regexp} <Wert>] [<glob> <glob> ...]\n" "\n" "Sucht in deinen Aufgaben nach Aufgaben die auf <glob> passen. Falls --regexp angegeben wird, wird der damit verknüpfte Wert dazu benutzt um nach Aufgaben zu suchen." #: plugin.py:249 msgid "No tasks matched that query." msgstr "Keine Aufgaben die auf die Anfrage passen." #: plugin.py:255 msgid "" "<id> <priority>\n" "\n" " Sets the priority of the todo with the given id to the specified value.\n" " " msgstr "" "<ID> <Priorität>\n" "\n" "Setzte die Priorität der Aufgabe, der ID, auf den angegeben Wert." #: plugin.py:269 msgid "" "<task id> <regexp>\n" "\n" " Modify the task with the given id using the supplied regexp.\n" " " msgstr "" "<Aufgaben ID> <reg. Ausdruck>\n" "\n" "Modifiziert die Aufgaben, der gegeben ID, durch den angegebenen regulären Ausdruck." ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Todo/locales/fi.po������������������������������������������������������0000644�0001750�0001750�00000011740�13634634532�020270� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Todo plugin in Limnoria. # Copyright (C) 2011 Limnoria # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: Todo plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 14:04+EET\n" "PO-Revision-Date: 2014-12-20 14:20+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.10\n" #: config.py:50 msgid "" "Determines whether users can read the\n" " todo-list of another user." msgstr "" "Määrittää voivatko käyttäjät lukea muiden käyttäjien\n" " tehtävälistoja." #: plugin.py:122 msgid "" "This plugin allows you to create your own personal to-do list on\n" " the bot." msgstr "" "Tämä plugin sallii käyttäjien tehdä henkilökohtaisia tehtävälistoja bottiin." #: plugin.py:138 msgid "" "[<username>] [<task id>]\n" "\n" " Retrieves a task for the given task id. If no task id is given, it\n" " will return a list of task ids that that user has added to their " "todo\n" " list.\n" " " msgstr "" "[<käyttäjätunnus>] [<tehtävän ID]\n" "\n" " Palauttaa annetun ID:n tehtävän. Jos tehtävän ID ei ole annettu, " "se\n" " palauttaa listan tehtävien ID:istä, jotka se käyttäjä on lisännyt " "tehtävä\n" " listaansa.\n" " " #: plugin.py:149 msgid "You are not allowed to see other users todo-list." msgstr "Sinulla ei ole oikeutta katsoa toisten käyttäjien tehtävälistoja." #: plugin.py:156 msgid "#%i: %s" msgstr "#%i %s" #: plugin.py:161 msgid "%s for %s: %L" msgstr "%s %s:lle: %L" #: plugin.py:165 msgid "That user has no tasks in their todo list." msgstr "Tuolla käyttäjällä ei ole tehtäviä tehtävälistallaan." #: plugin.py:167 msgid "You have no tasks in your todo list." msgstr "Sinulla ei ole tehtäviä tehtävälistallasi." #: plugin.py:174 msgid "Active" msgstr "Aktiivinen" #: plugin.py:176 msgid "Inactive" msgstr "Epä-aktiivinen" #: plugin.py:178 msgid ", priority: %i" msgstr "tärkeysaste %i" #: plugin.py:181 msgid "%s todo for %s: %s (Added at %s)" msgstr "%s tehtävä %s:lle: %s (Lisätty %s)" #: plugin.py:185 plugin.py:269 plugin.py:283 msgid "task id" msgstr "tehtävä ID" #: plugin.py:190 msgid "" "[--priority=<num>] <text>\n" "\n" " Adds <text> as a task in your own personal todo list. The optional\n" " priority argument allows you to set a task as a high or low " "priority.\n" " Any integer is valid.\n" " " msgstr "" "[--priority=<numero>] <teksti>\n" "\n" " Lisää <tekstin> tehtävänä sinun omalle henkilökohtaiselle " "tehtävälistallesi. Vaihtoehtoinen\n" " priority(=tärkeysaste) sallii sinun asettaa tehtävän korkealle tai " "matalalle tärkeysasteelle.\n" " Mikä tahansa kokonaisluku on kelvollinen.\n" " " #: plugin.py:201 msgid "(Todo #%i added)" msgstr "Tehtävä %i lisätty" #: plugin.py:207 msgid "" "<task id> [<task id> ...]\n" "\n" " Removes <task id> from your personal todo list.\n" " " msgstr "" "<tehtävä ID> [<tehtävä ID> ...]\n" "\n" " Poistaa <tehtävä ID(:eet)> henkilökohtaiselta tehtävälistaltasi.\n" " " #: plugin.py:218 msgid "" "Task %i could not be removed either because that id doesn't exist or it has " "been removed already." msgstr "" "Tehtävää %i ei voitu poistaa, koska se ei ollut olemassa tai se on jo " "poistettu." #: plugin.py:222 msgid "" "No tasks were removed because the following tasks could not be removed: %L." msgstr "Tehtäviä ei poistettu, koska seuraavia tehtäviä ei voitu poistaa: %L." #: plugin.py:232 msgid "" "[--{regexp} <value>] [<glob> <glob> ...]\n" "\n" " Searches your todos for tasks matching <glob>. If --regexp is " "given,\n" " its associated value is taken as a regexp and matched against the\n" " tasks.\n" " " msgstr "" "[--{regexp} <arvo>] [<glob> <glob> ...]\n" "\n" " Etsii <globiin> täsmääviä tehtävia tehtävälistaltasi. Jos --regexp " "on annettu,\n" " sen liitetty arvo otetaan säännölliseksi lausekkeeksi ja sitä " "täsmätään\n" " tehtäviin.\n" " " #: plugin.py:255 msgid "No tasks matched that query." msgstr "Yksikään tehtävä ei täsmännyt tuohon hakuun." #: plugin.py:261 msgid "" "<id> <priority>\n" "\n" " Sets the priority of the todo with the given id to the specified " "value.\n" " " msgstr "" "<ID> <tärkeysaste>\n" "\n" " Asettaa tehtävän ID tärkeysasteen annetulle arvolle.\n" " " #: plugin.py:275 msgid "" "<task id> <regexp>\n" "\n" " Modify the task with the given id using the supplied regexp.\n" " " msgstr "" "<tehtävän ID> <säännöllinen lauseke>\n" "\n" " Muokkaa tehtävää annetulla ID:llä käyttäen annettua säännöllistä " "lauseketta.\n" " " ��������������������������������limnoria-2020.03.17/plugins/Todo/locales/fr.po������������������������������������������������������0000644�0001750�0001750�00000010503�13634634532�020275� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-08-10 11:28+CEST\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\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" #: config.py:50 msgid "" "Determines whether users can read the\n" " todo-list of another user." msgstr "Détermine si les utilisateurs peuvent lire la todo-list d'autres utilisateurs." #: plugin.py:135 msgid "" "[<username>] [<task id>]\n" "\n" " Retrieves a task for the given task id. If no task id is given, it\n" " will return a list of task ids that that user has added to their todo\n" " list.\n" " " msgstr "" "[<utilisateur> <id de tâche>]\n" "\n" "Récupère la tâche correspondant à l'<id de tâche> donné. Si aucun id n'est donné, retourne une liste d'ids que cet utilisateur a ajouté à sa liste." #: plugin.py:146 msgid "You are not allowed to see other users todo-list." msgstr "Vous n'êtes pas autorisé(e) à voir la todo-list d'autres utilisateurs." #: plugin.py:153 msgid "#%i: %s" msgstr "#%i : %s" #: plugin.py:158 msgid "%s for %s: %L" msgstr "%s pour %s : %L" #: plugin.py:162 msgid "That user has no tasks in their todo list." msgstr "Cet utilisateur n'a pas de tâche dans sa todo-list." #: plugin.py:164 msgid "You have no tasks in your todo list." msgstr "Vous n'avez pas de tâche dans votre todo-list." #: plugin.py:171 msgid "Active" msgstr "Active" #: plugin.py:173 msgid "Inactive" msgstr "Inactive" #: plugin.py:175 msgid ", priority: %i" msgstr ", priorité : %i" #: plugin.py:178 msgid "%s todo for %s: %s (Added at %s)" msgstr "%s tâche pour %s : %s (Ajoutée à %s)" #: plugin.py:182 #: plugin.py:263 #: plugin.py:277 msgid "task id" msgstr "id de tâche" #: plugin.py:187 msgid "" "[--priority=<num>] <text>\n" "\n" " Adds <text> as a task in your own personal todo list. The optional\n" " priority argument allows you to set a task as a high or low priority.\n" " Any integer is valid.\n" " " msgstr "" "[--priority=<nombre>] <texte>\n" "\n" "Ajoute le <texte> comme une tâche dans votre liste personnelle de choses à faire. L'argument 'priority' optionnel vous permet de définir une priorité faible ou grande. Tout entier est accepté." #: plugin.py:198 msgid "(Todo #%i added)" msgstr "(Tâche #%i ajoutée)" #: plugin.py:204 msgid "" "<task id> [<task id> ...]\n" "\n" " Removes <task id> from your personal todo list.\n" " " msgstr "" "<id de tâche> [<id de tâche> ...]\n" "\n" "Supprime différentes tâches, désignées par leur ID, de votre liste personnelle de choses à faire." #: plugin.py:215 msgid "Task %i could not be removed either because that id doesn't exist or it has been removed already." msgstr "La tâche %i ne peut être supprimée car cet id n'existe pas, ou a déjà été supprimé." #: plugin.py:219 msgid "No tasks were removed because the following tasks could not be removed: %L." msgstr "Aucune tâche n'a été supprimée car les tâches suivantes ne peuvent l'être : %L." #: plugin.py:229 msgid "" "[--{regexp} <value>] [<glob> <glob> ...]\n" "\n" " Searches your todos for tasks matching <glob>. If --regexp is given,\n" " its associated value is taken as a regexp and matched against the\n" " tasks.\n" " " msgstr "" "[--{regexp} <valeur>] [<glob> <glob> ...]\n" "\n" "Recherche parmis vos tâches celle(s) correspondant au <glob>. Si --regexp est donné, il prend la valeur associée comme étant une expression régulière et re-teste les tâches." #: plugin.py:249 msgid "No tasks matched that query." msgstr "Aucune tâche ne correspond à cette requête." #: plugin.py:255 msgid "" "<id> <priority>\n" "\n" " Sets the priority of the todo with the given id to the specified value.\n" " " msgstr "" "<id> <priorité>\n" "\n" "Défini la priorité de la tâche d'<id> donné, à la valeur choisie." #: plugin.py:269 msgid "" "<task id> <regexp>\n" "\n" " Modify the task with the given id using the supplied regexp.\n" " " msgstr "" "<id de tâche> <regexp>\n" "\n" "Modifie la tâche en utilisant l'expression régulière donnée." ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Todo/locales/it.po������������������������������������������������������0000644�0001750�0001750�00000010725�13634634532�020310� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-08-10 14:53+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:50 msgid "" "Determines whether users can read the\n" " todo-list of another user." msgstr "" "Determina se un utente possa leggere la lista delle cose da fare di un altro utente." #: plugin.py:135 #, docstring msgid "" "[<username>] [<task id>]\n" "\n" " Retrieves a task for the given task id. If no task id is given, it\n" " will return a list of task ids that that user has added to their todo\n" " list.\n" " " msgstr "" "[<utente>] [<id compito>]\n" "\n" " Recupera il compito corrispondente a <id compito> fornito. Se non è\n" " specificato alcun id, riporta un elenco degli id che quell'utente\n" " ha aggiunto alla sua lista delle cose da fare.\n" " " #: plugin.py:146 msgid "You are not allowed to see other users todo-list." msgstr "Non hai l'autorizzazione per leggere la lista delle cose da fare degli altri utenti." #: plugin.py:153 msgid "#%i: %s" msgstr "#%i: %s" #: plugin.py:158 msgid "%s for %s: %L" msgstr "%s per %s: %L" #: plugin.py:162 msgid "That user has no tasks in their todo list." msgstr "Questo utente non ha compiti nella sua lista delle cose da fare." #: plugin.py:164 msgid "You have no tasks in your todo list." msgstr "Non hai compiti nella tua lista delle cose da fare." #: plugin.py:171 msgid "Active" msgstr "Attivo" #: plugin.py:173 msgid "Inactive" msgstr "Inattivo" #: plugin.py:175 msgid ", priority: %i" msgstr ", priorità: %i" #: plugin.py:178 msgid "%s todo for %s: %s (Added at %s)" msgstr "%s compito per %s: %s (Aggiunto il %s)" #: plugin.py:182 plugin.py:263 plugin.py:277 msgid "task id" msgstr "id compito" #: plugin.py:187 #, docstring msgid "" "[--priority=<num>] <text>\n" "\n" " Adds <text> as a task in your own personal todo list. The optional\n" " priority argument allows you to set a task as a high or low priority.\n" " Any integer is valid.\n" " " msgstr "" "[--priority=<numero>] <testo>\n" "\n" " Aggiunge <testo> come compito nella lista personale di cose da fare.\n" " L'argomento opzionale \"priority\" permette di definire un'alta o bassa priorità.\n" " Ogni numero intero è valido.\n" " " #: plugin.py:198 msgid "(Todo #%i added)" msgstr "(Compito #%i aggiunto)" #: plugin.py:204 #, docstring msgid "" "<task id> [<task id> ...]\n" "\n" " Removes <task id> from your personal todo list.\n" " " msgstr "" "<id compito> [<id compito> ...]\n" "\n" " Rimuove <id compito> dalla lista personale delle cose da fare.\n" " " #: plugin.py:215 msgid "Task %i could not be removed either because that id doesn't exist or it has been removed already." msgstr "Il compito %i non può essere rimosso in quanto l'id non esiste o è già stato rimosso." #: plugin.py:219 msgid "No tasks were removed because the following tasks could not be removed: %L." msgstr "Non è stato rimosso nessun compito perché i seguenti non possono essere rimossi: %L." #: plugin.py:229 #, docstring msgid "" "[--{regexp} <value>] [<glob> <glob> ...]\n" "\n" " Searches your todos for tasks matching <glob>. If --regexp is given,\n" " its associated value is taken as a regexp and matched against the\n" " tasks.\n" " " msgstr "" "[--{regexp} <valore>] [<globale> <globale> ...]\n" "\n" " Cerca i compiti che corrispondono a <globale> nella lista di cose da fare.\n" " Se --regexp è fornita, il suo valore associato è usato come regexp e confrontato con i compiti.\n" " " #: plugin.py:249 msgid "No tasks matched that query." msgstr "Nessun compito corrisponde alla richiesta." #: plugin.py:255 #, docstring msgid "" "<id> <priority>\n" "\n" " Sets the priority of the todo with the given id to the specified value.\n" " " msgstr "" "<id> <priorità>\n" "\n" " Imposta la priorità del compito con l'id fornito al valore specificato.\n" " " #: plugin.py:269 #, docstring msgid "" "<task id> <regexp>\n" "\n" " Modify the task with the given id using the supplied regexp.\n" " " msgstr "" "<id compito> <regexp>\n" "\n" " Modifica il compito con il dato id utilizzando una regexp.\n" " " �������������������������������������������limnoria-2020.03.17/plugins/Todo/plugin.py����������������������������������������������������������0000644�0001750�0001750�00000024470�13634634532�017564� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Daniel DiPaolo # Copyright (c) 2010, James McCoy # 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 os import re import time import operator import supybot.dbi as dbi import supybot.conf as conf import supybot.ircdb as ircdb import supybot.utils as utils from supybot.commands import * import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot import commands from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Todo') class TodoRecord(dbi.Record): __fields__ = [ ('priority', int), 'at', 'task', 'active', ] dataDir = conf.supybot.directories.data class FlatTodoDb(object): def __init__(self): self.directory = dataDir.dirize('Todo') if not os.path.exists(self.directory): os.mkdir(self.directory) self.dbs = {} def _getDb(self, uid): dbfile = os.path.join(self.directory, str(uid)) if uid not in self.dbs: self.dbs[uid] = dbi.DB(dbfile, Record=TodoRecord) return self.dbs[uid] def close(self): for db in self.dbs.values(): db.close() def get(self, uid, tid): db = self._getDb(uid) return db.get(tid) def getTodos(self, uid): db = self._getDb(uid) L = [R for R in db.select(lambda r: r.active)] if not L: raise dbi.NoRecordError return L def add(self, priority, now, uid, task): db = self._getDb(uid) return db.add(TodoRecord(priority=priority, at=now, task=task, active=True)) def remove(self, uid, tid): db = self._getDb(uid) t = db.get(tid) t.active = False db.set(tid, t) def select(self, uid, criteria): db = self._getDb(uid) def match(todo): for p in criteria: if not p(todo.task): return False return True todos = db.select(lambda t: match(t)) if not todos: raise dbi.NoRecordError return todos def setpriority(self, uid, tid, priority): db = self._getDb(uid) t = db.get(tid) t.priority = priority db.set(tid, t) def change(self, uid, tid, replacer): db = self._getDb(uid) t = db.get(tid) t.task = replacer(t.task) db.set(tid, t) class Todo(callbacks.Plugin): """This plugin allows you to create your own personal to-do list on the bot.""" def __init__(self, irc): self.__parent = super(Todo, self) self.__parent.__init__(irc) self.db = FlatTodoDb() def die(self): self.__parent.die() self.db.close() def _shrink(self, s): return utils.str.ellipsisify(s, 50) @internationalizeDocstring def todo(self, irc, msg, args, user, taskid): """[<username>] [<task id>] Retrieves a task for the given task id. If no task id is given, it will return a list of task ids that that user has added to their todo list. """ try: u = ircdb.users.getUser(msg.prefix) except KeyError: u = None if u != user and not self.registryValue('allowThirdpartyReader'): irc.error(_('You are not allowed to see other users todo-list.')) return # List the active tasks for the given user if not taskid: try: tasks = self.db.getTodos(user.id) utils.sortBy(operator.attrgetter('priority'), tasks) tasks = [format(_('#%i: %s'), t.id, self._shrink(t.task)) for t in tasks] Todo = 'Todo' if len(tasks) != 1: Todo = 'Todos' irc.reply(format(_('%s for %s: %L'), Todo, user.name, tasks)) except dbi.NoRecordError: if u != user: irc.reply(_('That user has no tasks in their todo list.')) else: irc.reply(_('You have no tasks in your todo list.')) return # Reply with the user's task else: try: t = self.db.get(user.id, taskid) if t.active: active = _('Active') else: active = _('Inactive') if t.priority: t.task += format(_(', priority: %i'), t.priority) at = time.strftime(conf.supybot.reply.format.time(), time.localtime(t.at)) s = format(_('%s todo for %s: %s (Added at %s)'), active, user.name, t.task, at) irc.reply(s) except dbi.NoRecordError: irc.errorInvalid(_('task id'), taskid) todo = wrap(todo, [first('otherUser', 'user'), additional(('id', 'task'))]) @internationalizeDocstring def add(self, irc, msg, args, user, optlist, text, now): """[--priority=<num>] <text> Adds <text> as a task in your own personal todo list. The optional priority argument allows you to set a task as a high or low priority. Any integer is valid. """ priority = 0 for (option, arg) in optlist: if option == 'priority': priority = arg todoId = self.db.add(priority, now, user.id, text) irc.replySuccess(format(_('(Todo #%i added)'), todoId)) add = wrap(add, ['user', getopts({'priority': ('int', 'priority')}), 'text', 'now']) @internationalizeDocstring def remove(self, irc, msg, args, user, tasks): """<task id> [<task id> ...] Removes <task id> from your personal todo list. """ invalid = [] for taskid in tasks: try: self.db.get(user.id, taskid) except dbi.NoRecordError: invalid.append(taskid) if invalid and len(invalid) == 1: irc.error(format(_('Task %i could not be removed either because ' 'that id doesn\'t exist or it has been removed ' 'already.'), invalid[0])) elif invalid: irc.error(format(_('No tasks were removed because the following ' 'tasks could not be removed: %L.'), invalid)) else: for taskid in tasks: self.db.remove(user.id, taskid) irc.replySuccess() remove = wrap(remove, ['user', many(('id', 'task'))]) @internationalizeDocstring def search(self, irc, msg, args, user, optlist, globs): """[--{regexp} <value>] [<glob> <glob> ...] Searches your todos for tasks matching <glob>. If --regexp is given, its associated value is taken as a regexp and matched against the tasks. """ if not optlist and not globs: raise callbacks.ArgumentError criteria = [] for (option, arg) in optlist: if option == 'regexp': criteria.append(lambda s: regexp_wrapper(s, reobj=arg, timeout=0.1, plugin_name=self.name(), fcn_name='search')) for glob in globs: glob = utils.python.glob2re(glob) criteria.append(re.compile(glob).search) try: tasks = self.db.select(user.id, criteria) L = [format('#%i: %s', t.id, self._shrink(t.task)) for t in tasks] irc.reply(format('%L', L)) except dbi.NoRecordError: irc.reply(_('No tasks matched that query.')) search = wrap(search, ['user', getopts({'regexp': 'regexpMatcher'}), any('glob')]) @internationalizeDocstring def setpriority(self, irc, msg, args, user, id, priority): """<id> <priority> Sets the priority of the todo with the given id to the specified value. """ try: self.db.setpriority(user.id, id, priority) irc.replySuccess() except dbi.NoRecordError: irc.errorInvalid(_('task id'), id) setpriority = wrap(setpriority, ['user', ('id', 'task'), ('int', 'priority')]) @internationalizeDocstring def change(self, irc, msg, args, user, id, replacer): """<task id> <regexp> Modify the task with the given id using the supplied regexp. """ try: self.db.change(user.id, id, replacer) irc.replySuccess() except dbi.NoRecordError: irc.errorInvalid(_('task id'), id) change = wrap(change, ['user', ('id', 'task'), 'regexpReplacer']) Class = Todo # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Todo/test.py������������������������������������������������������������0000644�0001750�0001750�00000014722�13634634532�017244� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Daniel DiPaolo # 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 TodoTestCase(PluginTestCase): plugins = ('Todo', 'User', 'Config') _user1 = 'foo!bar@baz' _user2 = 'bar!foo@baz' def setUp(self): PluginTestCase.setUp(self) # Create a valid user to use self.prefix = self._user2 self.assertNotError('register testy oom') self.prefix = self._user1 self.assertNotError('register tester moo') def testTodo(self): # Should not error, but no tasks yet. self.assertNotError('todo') self.assertRegexp('todo', 'You have no tasks') # Add a task self.assertNotError('todo add wash my car') self.assertRegexp('todo', '#1: wash my car') # Check that task self.assertRegexp('todo 1', r'Todo for tester: wash my car \(Added .*?\)') # Check that it lists all my tasks when given my name self.assertResponse('todo tester', 'Todo for tester: #1: wash my car') # Check pluralization self.assertNotError('todo add moo') self.assertRegexp('todo tester', 'Todos for tester: #1: wash my car and #2: moo') # Check error self.assertError('todo asfas') self.assertRegexp('todo asfas', 'Error: \'asfas\' is not a valid task') # Check priority sorting self.assertNotError('todo setpriority 1 100') self.assertNotError('todo setpriority 2 10') self.assertRegexp('todo', '#2: moo and #1: wash my car') # Check permissions self.prefix = self._user2 self.assertError('todo tester') self.assertNotRegexp('todo tester', 'task id') self.prefix = self._user1 self.assertNotError('todo tester') self.assertNotError('config plugins.Todo.allowThirdpartyReader True') self.prefix = self._user2 self.assertNotError('todo tester') self.prefix = self._user1 self.assertNotError('todo tester') def testAddtodo(self): self.assertNotError('todo add code a new plugin') self.assertNotError('todo add --priority=1000 fix all bugs') def testRemovetodo(self): self.nick = 'testy' self.prefix = self._user2 self.assertNotError('todo add do something') self.assertNotError('todo add do something else') self.assertNotError('todo add do something again') self.assertNotError('todo remove 1') self.assertNotError('todo 1') self.nick = 'tester' self.prefix = self._user1 self.assertNotError('todo add make something') self.assertNotError('todo add make something else') self.assertNotError('todo add make something again') self.assertNotError('todo remove 1 3') self.assertRegexp('todo 1', r'Inactive') self.assertRegexp('todo 3', r'Inactive') self.assertNotError('todo') def testSearchtodo(self): self.assertNotError('todo add task number one') self.assertRegexp('todo search task*', '#1: task number one') self.assertRegexp('todo search number', '#1: task number one') self.assertNotError('todo add task number two is much longer than' ' task number one') self.assertRegexp('todo search task*', '#1: task number one and #2: task number two is ' 'much longer than task number...') self.assertError('todo search --regexp s/bustedregex') self.assertRegexp('todo search --regexp m/task/', '#1: task number one and #2: task number two is ' 'much longer than task number...') def testSetPriority(self): self.assertNotError('todo add --priority=1 moo') self.assertRegexp('todo 1', r'moo, priority: 1 \(Added at .*?\)') self.assertNotError('setpriority 1 50') self.assertRegexp('todo 1', r'moo, priority: 50 \(Added at .*?\)') self.assertNotError('setpriority 1 0') self.assertRegexp('todo 1', r'moo \(Added at .*?\)') def testChangeTodo(self): self.assertNotError('todo add moo') self.assertError('todo change 1 asdfas') self.assertError('todo change 1 m/asdfaf//') self.assertNotError('todo change 1 s/moo/foo/') self.assertRegexp('todo 1', r'Todo for tester: foo \(Added .*?\)') def testActiveInactiveTodo(self): self.assertNotError('todo add foo') self.assertNotError('todo add bar') self.assertRegexp('todo 1', 'Active') self.assertRegexp('todo 2', 'Active') self.assertNotError('todo remove 1') self.assertRegexp('todo 1', 'Inactive') self.assertRegexp('todo 2', 'Active') self.assertNotError('todo remove 2') self.assertRegexp('todo 2', 'Inactive') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������limnoria-2020.03.17/plugins/Topic/������������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016064� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Topic/__init__.py�������������������������������������������������������0000644�0001750�0001750�00000004753�13634634532�020200� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Provides commands for manipulating channel topics. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = { supybot.authors.stepnem: ['persistence support'] } from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������limnoria-2020.03.17/plugins/Topic/config.py���������������������������������������������������������0000644�0001750�0001750�00000011174�13634634532�017701� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Topic') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Topic', True) class TopicFormat(registry.TemplatedString): "Value must include $topic, otherwise the actual topic would be left out." requiredTemplates = ['topic'] Topic = conf.registerPlugin('Topic') conf.registerChannelValue(Topic, 'separator', registry.StringSurroundedBySpaces('|', _("""Determines what separator is used between individually added topics in the channel topic."""))) conf.registerChannelValue(Topic, 'format', TopicFormat('$topic', _("""Determines what format is used to add topics in the topic. All the standard substitutes apply, in addition to "$topic" for the topic itself."""))) conf.registerChannelValue(Topic, 'recognizeTopiclen', registry.Boolean(True, _("""Determines whether the bot will recognize the TOPICLEN value sent to it by the server and thus refuse to send TOPICs longer than the TOPICLEN. These topics are likely to be truncated by the server anyway, so this defaults to True."""))) conf.registerChannelValue(Topic, 'default', registry.String('', _("""Determines what the default topic for the channel is. This is used by the default command to set this topic."""))) conf.registerChannelValue(Topic, 'setOnJoin', registry.Boolean(True, _("""Determines whether the bot will automatically set the topic on join if it is empty."""))) conf.registerChannelValue(Topic, 'alwaysSetOnJoin', registry.Boolean(False, _("""Determines whether the bot will set the topic every time it joins, or only if the topic is empty. Requires 'config plugins.topic.setOnJoin' to be set to True."""))) conf.registerGroup(Topic, 'undo') 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 capabilities required (if any) to make any topic changes, (everything except for read-only operations). Use 'channel,capab' for channel-level capabilities. Note that absence of an explicit anticapability means user has capability."""))) conf.registerChannelValue(Topic, 'allowSeparatorinTopics', registry.Boolean(True, _("""Determines whether the bot will allow topics containing the defined separator to be used. You may want to disable this if you are signing all topics by nick (see the 'format' option for ways to adjust this)."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Topic/locales/����������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017506� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Topic/locales/fi.po�����������������������������������������������������0000644�0001750�0001750�00000043422�13634634532�020443� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Topic plugin in Limnoria. # Copyright (C) 2011,2014 # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2012. # msgid "" msgstr "" "Project-Id-Version: Topic plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 13:29+EET\n" "PO-Revision-Date: 2014-12-20 13:45+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: Finnish <>\n" "Language: fi\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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.10\n" #: config.py:45 msgid "" "Value must include $topic, otherwise the actual topic would be left out." msgstr "Asetusarvon täytyy sisältää $topic tai muuten itse aihe jätetään pois." #: config.py:50 msgid "" "Determines what separator is\n" " used between individually added topics in the channel topic." msgstr "" "Määrittää mitä erotinta käytetään\n" " erikseen lisättyjen aiheiden välissä kanavan aiheessa." #: config.py:53 msgid "" "Determines what format is used to add\n" " topics in the topic. All the standard substitutes apply, in addition " "to\n" " \"$topic\" for the topic itself." msgstr "" "Määrittää mitä muotoa käytetään, kun lisätään aiheita aiheeseen. \n" " Kaikki peru muunnokset ovat voimassa, \n" " \"$topic\" tarkoittaa aihetta itseään." #: config.py:57 msgid "" "Determines whether the bot will recognize the\n" " TOPICLEN value sent to it by the server and thus refuse to send TOPICs\n" " longer than the TOPICLEN. These topics are likely to be truncated by " "the\n" " server anyway, so this defaults to True." msgstr "" "Määrittää tunnistaako botti TOPICLEN-arvon, jonka palvelin on lähettänyt " "sille\n" " ja näin kieltäytyy asettamasta pidempiä aiheita, kuin TOPICLEN. \n" " Kyseiset aiheet tulisivat muutenkin palvelimen lyhentämiksi, joten\n" " tämän asetusarvo on oletuksena True." #: config.py:62 msgid "" "Determines what the default topic for the channel\n" " is. This is used by the default command to set this topic." msgstr "" "Määrittää mikä on oletus aihe kanavalle. \n" " 'Default' komento käyttää tätä asettaakseen aiheen." #: config.py:65 #, fuzzy msgid "" "Determines whether the bot will automatically\n" " set the topic on join if it is empty." msgstr "" "Määrittää asettaako botti aiheen aina liittyessään vai vain, kun aihe on " "tyhjä." #: config.py:68 #, fuzzy msgid "" "Determines whether the bot will set the topic\n" " every time it joins, or only if the topic is empty. Requires 'config\n" " plugins.topic.setOnJoin' to be set to True." msgstr "" "Määrittää asettaako botti aiheen aina liittyessään vai vain, kun aihe on " "tyhjä." #: config.py:73 msgid "" "Determines the number of previous\n" " topics to keep around in case the undo command is called." msgstr "" "Määrittää edellisten aiheiden määrän, jotka säilytetään siltä varalta, että\n" " 'undo' komentoa käytetään." #: config.py:76 msgid "" "Determines the\n" " capabilities required (if any) to make any topic changes,\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" " capability." msgstr "" "Määrittää\n" " valtuudet, jotka (jos mitkään) vaaditaan aiheen vaihtamiseen,\n" " (=kaikki, paitsi vain-luku komennot). Käytä valtuutta 'channel," "valtuus' \n" " kanava-tason valtuuksia varten.\n" " Huomaa, että anti-valtuuden poissaolo tarkoittaa, että käyttäjällä on\n" " valtuus." #: plugin.py:57 msgid "I'm not currently in %s." msgstr "En juuri nyt ole kanavalla %s." #: plugin.py:61 msgid "I can't change the topic, I'm not (half)opped and %s is +t." msgstr "" "En voi vaihtaa aihetta, koska en ole kyseisellä kanavalla puolioperaattori " "ja kanavalla %s on tila +t." #: plugin.py:68 msgid "The topic must not include %q." msgstr "%q ei saa olla aiheessa." #: plugin.py:79 msgid "topic number" msgstr "aiheen numero" #: plugin.py:92 msgid "There are no topics in %s." msgstr "Kanavalla %s ei ole aiheita." #: plugin.py:114 msgid "" "This plugin allows you to use many topic-related functions,\n" " such as Add, Undo, and Remove." msgstr "" "Tämä plugini sallii monien aiheeseen liittyvien functioiden käytön, kuten " "lisäämisen (add),\n" " kumoamisen (undo) ja poistamisen (remove)." #: plugin.py:202 msgid "" "That topic is too long for this server (maximum length: %i; this topic: %i)." msgstr "" "Määrittämäsi aihe on liian pitkä tälle palvelimelle (maksimi pituus on %i; " "tämän aiheen pituus on %i)." #: plugin.py:221 msgid "" "Check if the user has any of the required capabilities to manage\n" " the channel topic.\n" "\n" " The list of required capabilities is in requireManageCapability\n" " channel config.\n" "\n" " Also allow if the user is a chanop. Since they can change the topic\n" " manually anyway.\n" " " msgstr "" "Tarkista onko käyttäjällä valtuudet, jotka on vaadittu\n" " kanavan aiheen hallintaan.\n" "\n" " Lista vaadituista valtuuksista on kanava-asetusarvossa " "requireManageCapability\n" "\n" " Salli myös jos käyttäjä on kanavaoperaattori, koska hän voisi " "vaihtaa aiheen\n" " muutenkin manuaalisesti.\n" " " #: plugin.py:278 msgid "" "[<channel>]\n" "\n" " Returns the topic for <channel>. <channel> is only necessary if " "the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>]\n" "\n" " Palauttaa <kanavan> aiheen. <Kanava> on vaadittu vain ellei viestiä " "lähetetä\n" " kanavalla itsellään.\n" " " #: plugin.py:289 msgid "" "[<channel>] <topic>\n" "\n" " Adds <topic> to the topics for <channel>. <channel> is only " "necessary\n" " if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] <aihe>\n" "\n" " Lisää <aiheen> <kanavan> aiheisiin. <Kanava> on vaadittu vain, " "ellei\n" " viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:304 msgid "" "[<channel>] <topic>\n" "\n" " Adds <topic> to the topics for <channel>. If the topic is too long\n" " for the server, topics will be popped until there is enough room.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<kanava>] <aihe>\n" "\n" " Lisää <aiheen> <kanavan> aiheisiin. Jos aihe on liian pitkä\n" " palvelimelle, aihetta kutistetaan kunnes sille on tarpeeksi tilaa.\n" " <Kanava> on vaadittu vain, ellei viestiä lähetetä kanavalla\n" " itsellään.\n" " " #: plugin.py:321 msgid "" "[<channel>] <number> <topic>\n" "\n" " Replaces topic <number> with <topic>.\n" " " msgstr "" "[<kanava>] <numero> <aihe>\n" "\n" " Korvaa aiheen <numero> <aiheella>.\n" " " #: plugin.py:335 msgid "" "[<channel>] <topic>\n" "\n" " Adds <topic> to the topics for <channel> at the beginning of the " "topics\n" " currently on <channel>. <channel> is only necessary if the message\n" " isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] <aihe>\n" "\n" " Lisää <aiheen> <kanavan> aiheisiin \n" " <kanavalla> silloisten aiheiden alkuun. <Kanava> on vaadittu vain, " "jos viestiä ei lähetetä \n" " kanavalla itsellään.\n" " " #: plugin.py:351 msgid "" "[<channel>]\n" "\n" " Shuffles the topics in <channel>. <channel> is only necessary if " "the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>]\n" "\n" " Sekoittaa aiheet <kanavalla>. <Kanava> on vaadittu vain, jos " "viestiä ei lähetetä \n" " kanavalla itsellään.\n" " " #: plugin.py:361 msgid "I can't shuffle 1 or fewer topics." msgstr "Yhtä tai vähempää aihetta ei voida sekoittaa." #: plugin.py:373 msgid "" "[<channel>] <number> [<number> ...]\n" "\n" " Reorders the topics from <channel> in the order of the specified\n" " <number> arguments. <number> is a one-based index into the topics.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<kanava>] <numero> [<numero> ...]\n" "\n" " Järjestää aiheet <kanavalla> järjestyksessä, joka on määritetty\n" " <numero> parametreillä. <Numero> on yksi-indexinen aiheisiin.\n" " <Kanava> on vaadittu vain jos viestiä ei lähetetä kanavalla \n" " itsellään.\n" " " #: plugin.py:386 msgid "I cannot reorder 1 or fewer topics." msgstr "En voi uudelleen järjestää yhtä tai vähempää aihetta." #: plugin.py:388 msgid "All topic numbers must be specified." msgstr "Kaikki numerot täytyy määrittää." #: plugin.py:390 msgid "Duplicate topic numbers cannot be specified." msgstr "Aiheen numeroiden kaksoiskappaleita ei voida määrittää." #: plugin.py:398 msgid "" "[<channel>]\n" "\n" " Returns a list of the topics in <channel>, prefixed by their " "indexes.\n" " Mostly useful for topic reordering. <channel> is only necessary if " "the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>]\n" "\n" " Palauttaa listan aiheista <kanavalla>, etuliitettyinä " "indekseihinsä.\n" " Enimmäkseen hyödyllinen aiheen uudelleenjärjestämisessä. <Kanava> " "on vaadittu vain jos viestiä ei lähetetä \n" " kanavalla itsellään.\n" " " #: plugin.py:407 msgid "%i: %s" msgstr "%i: %s" #: plugin.py:414 #, fuzzy msgid "" "[<channel>] <number>\n" "\n" " Returns topic number <number> from <channel>. <number> is a one-" "based\n" " index into the topics. <channel> is only necessary if the message\n" " isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] <numero>\n" "\n" " Palauttaa aiheen <numero> <kanavalta>. <Numero> on yksi-indexinen\n" " aiheissa. <Kanava> on vaadittu vain jos viestiä ei lähetetä " "kanavalla\n" " itsellään.\n" " " #: plugin.py:426 #, fuzzy msgid "" "[<channel>] <number> <regexp>\n" "\n" " Changes the topic number <number> on <channel> according to the " "regular\n" " expression <regexp>. <number> is the one-based index into the " "topics;\n" " <regexp> is a regular expression of the form\n" " s/regexp/replacement/flags. <channel> is only necessary if the " "message\n" " isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] <numero> <säännöllinen lauseke>\n" "\n" " Vaihtaa aiheen <numero> <kanavalla> <säännöllisen lausekkeen> " "mukaan.\n" " <numero> on yksi-indexinen aiheissa;\n" " <säännöllinen lauseke> on säännöllinen lauseke muodossa\n" " s/säännöllinen lauseke/korvaus/liput. <Kanava> on vaadittu vain jos " "viestiä ei lähetetä\n" " kanavalla itsellään.\n" " " #: plugin.py:444 msgid "" "[<channel>] [<number>] <topic>\n" "\n" " Sets the topic <number> to be <text>. If no <number> is given, " "this\n" " sets the entire topic. <channel> is only necessary if the message\n" " isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] [<numero>] <aihe>\n" "\n" " Asettaa aiheen <numero> <tekstiksi>. Jos <numeroa> ei ole annettu, " "tämä\n" " asettaa koko aiheen. <Kanava> on vaadittu vain, ellei viestiä " "lähetetä\n" " kanavalla itsellään.\n" " " #: plugin.py:465 msgid "" "[<channel>] <number>\n" "\n" " Removes topic <number> from the topic for <channel> Topics are\n" " numbered starting from 1; you can also use negative indexes to " "refer\n" " to topics starting the from the end of the topic. <channel> is " "only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>] <numero>\n" "\n" " Poistaa aiheen <numero> <kanavan> aiheesta. Aiheet on numeroitu\n" " alkaen numerosta 1; voit käyttää negatiivisia lukuja saadaksesi ne " "viittaamaan\n" " aiheisiin, jotka alkavat lopusta. <Kanava> on vaadittu\n" " vain, jos viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:482 msgid "" "[<channel>]\n" "\n" " Locks the topic (sets the mode +t) in <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>]\n" "\n" " Lukitsee aiheen (asettaa tilan +t) <kanavalla>. <Kanava> on " "vaadittu vain, ellei\n" " viestiä lähetetä kanavalla itsellään.\n" " " #: plugin.py:492 msgid "lock the topic" msgstr "lukitse aihe" #: plugin.py:496 msgid "" "[<channel>]\n" "\n" " Unlocks the topic (sets the mode -t) in <channel>. <channel> is " "only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<channel>]\n" "\n" " Avaa aiheen (asettaa tilan -t) <kanavalla>. <Kanava> on vaadittu " "vain, \n" " ellei viestiä lähetetä kanavalla itsellään.\n" " " #: plugin.py:506 msgid "unlock the topic" msgstr "avaa aiheen" #: plugin.py:510 msgid "" "[<channel>]\n" "\n" " Restores the topic to the last topic set by the bot. <channel> is " "only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>]\n" "\n" " Palauttaa aiheen viimeiseksi aiheeksi, jonka botti on asettanut. " "<Kanava> on vaadittu\n" " vain, ellei viestiä lähetetä kanavalla itsellään.\n" " " #: plugin.py:523 plugin.py:548 msgid "I haven't yet set the topic in %s." msgstr "En ole vielä asettanut aihetta kanavalla %s." #: plugin.py:531 #, fuzzy msgid "" "[<channel>]\n" " Refreshes current topic set by anyone. Restores topic if empty.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<kanava>]\n" "\n" " Palauttaa aiheen viimeiseksi aiheeksi, jonka botti on asettanut. " "<Kanava> on vaadittu\n" " vain, ellei viestiä lähetetä kanavalla itsellään.\n" " " #: plugin.py:556 #, fuzzy msgid "" "[<channel>]\n" "\n" " Restores the topic to the one previous to the last topic command " "that\n" " set it. <channel> is only necessary if the message isn't sent in " "the\n" " channel itself.\n" " " msgstr "" "[<kanava>]\n" "\n" " Palauttaa aiheen yhdeksi edellisistä aiheista, jotka \"last\" " "komento on asettanut \n" " siksi. <Kanava> on vaadittu vain, ellei viestiä lähetetä " "kanavalla\n" " itsellään.\n" " " #: plugin.py:570 msgid "There are no more undos for %s." msgstr "Kanavalle %s ei ole enempää kumouksia." #: plugin.py:575 msgid "" "[<channel>]\n" "\n" " Undoes the last undo. <channel> is only necessary if the message " "isn't\n" " sent in the channel itself.\n" " " msgstr "" "[<kanava>]\n" "\n" " Kumoaa viimeisen kumouksen. <Kanava> on vaadittu vain jos viestiä " "ei lähetetä\n" " kanavalla itsellään.\n" " " #: plugin.py:587 #, fuzzy msgid "There are no redos for %s." msgstr "Kanavalle %s ei ole enempää uudelleentekoja." #: plugin.py:592 msgid "" "[<channel>] <first topic number> <second topic number>\n" "\n" " Swaps the order of the first topic number and the second topic " "number.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<kanava>] <ensimmäisen aiheen numero> <toisen aiheen numero>\n" "\n" " Vaihtaa ensimmäisen ja toisen aiheen paikat.\n" " <Kanava> on vaadittu vain, ellei viestiä lähetetä kanavalla itsellään." #: plugin.py:603 msgid "I refuse to swap the same topic with itself." msgstr "Kieltäydyn vaihtamasta aiheen paikkaa sen itsensä kanssa." #: plugin.py:613 msgid "" "[<channel>]\n" "\n" " Saves the topic in <channel> to be restored with 'topic default'\n" " later. <channel> is only necessary if the message isn't sent in\n" " the channel itself.\n" " " msgstr "" "[<kanava>]\n" "\n" " Tallentaa aiheen <kanavalla>, jotta se voidaan palauttaa 'topic default'—" "komennolla\n" " myöhemmin. <Kanava> on vaadittu vain, ellei viestiä lähetetä kanavalla " "itsellään." #: plugin.py:632 msgid "" "[<channel>]\n" "\n" " Sets the topic in <channel> to the default topic for <channel>. " "The\n" " default topic for a channel may be configured via the configuration\n" " variable supybot.plugins.Topic.default.\n" " " msgstr "" "[<kanava>]\n" "\n" " Asettaa <kanavan> aiheen <kanavan> oletusaiheeksi. Kanavan oletusaihe " "voidaan\n" " määrittää asetuksen supybot.plugins.Topic.default arvolla.\n" " " #: plugin.py:645 msgid "There is no default topic configured for %s." msgstr "Kanavalle %s ei ole määritetty oletusaihetta." #: plugin.py:651 msgid "" "[<channel>] <separator>\n" "\n" " Sets the topic separator for <channel> to <separator> Converts the\n" " current topic appropriately.\n" " " msgstr "" "[<kanava>] <eroittaja>\n" "\n" " Asettaa <kanavan> erottimen <erottimeksi>, muuntaa\n" " nykyisen aiheen sen mukaisesti.\n" " " #~ msgid "" #~ "[<kanava>] <ensinmäinen aiheen numero> <toinen aiheen numero>\n" #~ "\n" #~ " Vaihtaa ensinmäisen ja toisen aiheen numeron paikkoja.\n" #~ " <Kanava> on vaadittu vain, jos viestiä ei lähetetä kanavalla\n" #~ " itsellään.\n" #~ " " #~ msgstr "" #~ "[<kanava>] <ensinmäinen aiheen numero> <toinen aiheen numero>\n" #~ "\n" #~ " Vaihtaa ensinmäisen ja toisen aiheen numeron paikkoja.\n" #~ " <Kanava> on vaadittu vain, jos viestiä ei lähetetä kanavalla\n" #~ " itsellään.\n" #~ " " ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Topic/locales/fr.po�����������������������������������������������������0000644�0001750�0001750�00000036421�13634634532�020455� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-01-21 22:32+CET\n" "PO-Revision-Date: 2014-07-05 00:09+0200\n" "Last-Translator: \n" "Language-Team: Limnoria <progval@gmail.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" "Language: fr\n" #: config.py:45 msgid "" "Value must include $topic, otherwise the actual topic would be left out." msgstr "" "La valeur doit inclure $topic, sinon, le topic actuel serait laissé tel quel." #: config.py:50 msgid "" "Determines what separator is\n" " used between individually added topics in the channel topic." msgstr "" "Détermine quel séparateur est utilisé entre les différents topics dans le " "topic du canal." #: config.py:53 msgid "" "Determines what format is used to add\n" " topics in the topic. All the standard substitutes apply, in addition " "to\n" " \"$topic\" for the topic itself." msgstr "" "Détermine quel format est utilisé pour ajouter des topics dans le topic. " "Tous les substituts standard s'appliquent, en plus de \"$topic\" pour le " "topic lui-même." #: config.py:57 msgid "" "Determines whether the bot will recognize the\n" " TOPICLEN value sent to it by the server and thus refuse to send TOPICs\n" " longer than the TOPICLEN. These topics are likely to be truncated by " "the\n" " server anyway, so this defaults to True." msgstr "" "Détermine si le bot reconnaitra la valeur TOPICLEN envoyée par le serveur et " "refusera d'envoyer des topics plus longs que TOPICLEN. De toutes manière, " "les topics trop longs seront tronqués par le serveur, don cç vaut par défaut " "True." #: config.py:62 msgid "" "Determines what the default topic for the channel\n" " is. This is used by the default command to set this topic." msgstr "" "Détermine quel est le topic par défaut du canal. C'est utilisé par la " "commande 'default'." #: config.py:65 msgid "" "Determines whether the bot will set the topic\n" " every time it joins, or only if the topic is empty." msgstr "" "Détermine si le bot définira le topic à chaque fois qu’il rejoint le salon, " "ou seulement si le topic est vide." #: config.py:69 msgid "" "Determines the number of previous\n" " 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:72 msgid "" "Determines the\n" " capabilities required (if any) to make any topic changes,\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" " capability." msgstr "" "Détermine les capacités 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:57 msgid "I'm not currently in %s." msgstr "Je ne suis pas actuellement sur %s." #: plugin.py:61 msgid "I can't change the topic, I'm not (half)opped and %s is +t." msgstr "Je ne peux changer le topic, je ne suis pas halfopé, et %s a le mode +t." #: plugin.py:68 msgid "The topic must not include %q." msgstr "Le topic ne doit pas inclure %q." #: plugin.py:79 msgid "topic number" msgstr "numéro de topic" #: plugin.py:92 msgid "There are no topics in %s." msgstr "Il n'y a pas de topic sur %s." #: 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:219 msgid "" "Check if the user has any of the required capabilities to manage\n" " the channel topic.\n" "\n" " The list of required capabilities is in requireManageCapability\n" " channel config.\n" "\n" " Also allow if the user is a chanop. Since they can change the topic\n" " manually anyway.\n" " " msgstr "." #: plugin.py:276 msgid "" "[<channel>]\n" "\n" " Returns the topic for <channel>. <channel> is only necessary if " "the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>]\n" "\n" "Retourne le topic du <canal>. <canal> n'est nécessaire que si le message " "n'est pas envoyé sur le canal lui-même." #: plugin.py:287 msgid "" "[<channel>] <topic>\n" "\n" " Adds <topic> to the topics for <channel>. <channel> is only " "necessary\n" " if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] <topic>\n" "\n" "Ajoute le <topic> aux topics du <canal>. <canal> n'est nécessaire que si le " "message n'est pas envoyé sur le canal lui-même." #: plugin.py:302 msgid "" "[<channel>] <topic>\n" "\n" " Adds <topic> to the topics for <channel>. If the topic is too long\n" " for the server, topics will be popped until there is enough room.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<canal>] <topic>\n" "\n" "Ajoute le topic <topic> aux topics du <canal>. 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. <canal> n'est nécessaire que si le message n'est pas " "envoyé sur le canal lui-même." #: plugin.py:319 msgid "" "[<channel>] <number> <topic>\n" "\n" " Replaces topic <number> with <topic>.\n" " " msgstr "" "[<canal>] <nombre> <topic>\n" "\n" "Remplace le topic <nombre> par le <topic>." #: plugin.py:333 msgid "" "[<channel>] <topic>\n" "\n" " Adds <topic> to the topics for <channel> at the beginning of the " "topics\n" " currently on <channel>. <channel> is only necessary if the message\n" " isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] <topic>\n" "\n" "Ajoute le <topic> aux topics du <canal>, au début des topics actuellement " "sur le <canal>. <canal> n'est nécessaire que si le message n'est pas envoyé " "sur le canal lui-même." #: plugin.py:349 msgid "" "[<channel>]\n" "\n" " Shuffles the topics in <channel>. <channel> is only necessary if " "the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>]\n" "\n" "Mélance les topics sur le <canal>. <canal> n'est nécessaire que si le " "message n'est pas envoyé sur le canal lui-même." #: plugin.py:359 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:371 msgid "" "[<channel>] <number> [<number> ...]\n" "\n" " Reorders the topics from <channel> in the order of the specified\n" " <number> arguments. <number> is a one-based index into the topics.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<canal>] <nombre> [<nombre> ...]\n" "\n" "Remet les topics du <canal> dans l'ordre spécifié par les arguments " "<nombre>. <nombre> est un index dans les topics. <canal> n'est nécessaire " "que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:384 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:386 msgid "All topic numbers must be specified." msgstr "Tous les nombres de topics doivent être spécifiés." #: plugin.py:388 msgid "Duplicate topic numbers cannot be specified." msgstr "Les numéros de topics ne peuvent être en double." #: plugin.py:396 msgid "" "[<channel>]\n" "\n" " Returns a list of the topics in <channel>, prefixed by their " "indexes.\n" " Mostly useful for topic reordering. <channel> is only necessary if " "the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>]\n" "\n" "Retourne la liste des topics sur le <canal>, préfixés par leur index. " "Généralement utile pour réordonner les topics. <canal> n'est nécessaire que " "si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:405 msgid "%i: %s" msgstr "%i : %s" #: plugin.py:412 msgid "" "[<channel>] <number>\n" "\n" " Returns topic number <number> from <channel>. <number> is a one-" "based\n" " index into the topics. <channel> is only necessary if the message\n" " isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] <nombre>\n" "\n" "Retourne le topic numéro <nombre> du canal. <nombre> est un index dans les " "topics. <canal> n'est nécessaire que si le message n'est pas envoyé sur le " "canal lui-même." #: plugin.py:424 msgid "" "[<channel>] <number> <regexp>\n" "\n" " Changes the topic number <number> on <channel> according to the " "regular\n" " expression <regexp>. <number> is the one-based index into the " "topics;\n" " <regexp> is a regular expression of the form\n" " s/regexp/replacement/flags. <channel> is only necessary if the " "message\n" " isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] <nombre> <regexp>\n" "\n" "Change le topic <nombre> sur le <canal>, en accord avec l'expression " "régulière <regexp>. <nombre> est un index parmis les topics ; <regexp> est " "une expression régulière de la forme s/regexp/replacement/flags. <canal> " "n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:442 msgid "" "[<channel>] [<number>] <topic>\n" "\n" " Sets the topic <number> to be <text>. If no <number> is given, " "this\n" " sets the entire topic. <channel> is only necessary if the message\n" " isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] [<nombre>] <topic>\n" "\n" "Définit le topic <nombre>. Si le <nombre> n'est pas donné, il s'agit du " "topic entier. <canal> n'est nécessaire que si le message n'est pas envoyé " "sur le canal lui-même." #: plugin.py:463 msgid "" "[<channel>] <number>\n" "\n" " Removes topic <number> from the topic for <channel> Topics are\n" " numbered starting from 1; you can also use negative indexes to " "refer\n" " to topics starting the from the end of the topic. <channel> is " "only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>] <nombre>\n" "\n" "Supprime le topic <nombre> des topics du <canal>. Les topics sont numérotés " "à partir de 1 ; vous pouvez également utiliser des indexs négatifs pour " "compter à partir de la fin. <canal> n'est nécessaire que si le message n'est " "pas envoyé sur le canal lui-même." #: plugin.py:480 msgid "" "[<channel>]\n" "\n" " Locks the topic (sets the mode +t) in <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>]\n" "\n" "Verrouille le topic (défini le mode +t) sur le <canal>. <canal> n'est " "nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:490 msgid "lock the topic" msgstr "verrouiller le topic" #: plugin.py:494 msgid "" "[<channel>]\n" "\n" " Unlocks the topic (sets the mode -t) in <channel>. <channel> is " "only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>]\n" "\n" "Déverrouille le topic (défini le mode -t) sur le <canal>. <canal> n'est " "nécessaire que si le message n'est pas envoyé sur le canal lui-même." #: plugin.py:504 msgid "unlock the topic" msgstr "déverrouiller le topic" #: plugin.py:508 msgid "" "[<channel>]\n" "\n" " Restores the topic to the last topic set by the bot. <channel> is " "only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>]\n" "\n" "Restaure le topic tel qu'il était la dernière fois que le bot l'a défini. " "<canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-" "même." #: plugin.py:519 msgid "I haven't yet set the topic in %s." msgstr "Je n'ai encore jamais définit le topic sur %s." #: plugin.py:527 msgid "" "[<channel>]\n" "\n" " Restores the topic to the one previous to the last topic command " "that\n" " set it. <channel> is only necessary if the message isn't sent in " "the\n" " channel itself.\n" " " msgstr "" "[<canal>]\n" "\n" "Restaure le topic à l'état dans lequel il était avant la dernière " "utilisation de la commande 'topic. <canal> n'est nécessaire que si le " "message n'est pas envoyé sur le canal lui-même." #: plugin.py:541 msgid "There are no more undos for %s." msgstr "Il n'y a plus rien à défaire sur %s" #: plugin.py:546 msgid "" "[<channel>]\n" "\n" " Undoes the last undo. <channel> is only necessary if the message " "isn't\n" " sent in the channel itself.\n" " " msgstr "" "[<canal>]\n" "\n" "Défait le dernier 'undo'. <canal> n'est nécessaire que si le message n'est " "pas envoyé sur le canal lui-même." #: plugin.py:558 msgid "There are no redos for %s." msgstr "Il n'y a plus rien à refaire sur %s" #: plugin.py:563 msgid "" "[<channel>] <first topic number> <second topic number>\n" "\n" " Swaps the order of the first topic number and the second topic " "number.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<canal>]\n" "\n" "<nombre du premier topic> <nombre du deuxième topic> Inverse les deux topics " "donnés. <canal> n'est nécessaire que si le message n'est pas envoyé sur le " "canal lui-même." #: plugin.py:574 msgid "I refuse to swap the same topic with itself." msgstr "Je refuse d'échanger un topic avec lui-même." #: plugin.py:584 msgid "" "[<channel>]\n" "\n" " Saves the topic in <channel> to be restored with 'topic default'\n" " later. <channel> is only necessary if the message isn't sent in\n" " the channel itself.\n" " " msgstr "" "[<canal>]\n" "\n" "Enregistre le topic du <canal> pour être restauré avec 'topic default' par la " "suite. <canal> n'est nécessaire que si vous n'exécutez pas la commande dans " "le canal lui-même." #: plugin.py:603 msgid "" "[<channel>]\n" "\n" " Sets the topic in <channel> to the default topic for <channel>. " "The\n" " default topic for a channel may be configured via the configuration\n" " variable supybot.plugins.Topic.default.\n" " " msgstr "" "[<canal>]\n" "\n" "Définit le topic du <canal> pour correspondre à celui par défaut défini pour " "le <canal>. Le topic par défaut pour un canal peut être configuré via la " "variable supybot.plugins.Topic.default. <canal> n'est nécessaire que si le " "message n'est pas envoyé sur le canal lui-même." #: plugin.py:616 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:622 msgid "" "[<channel>] <separator>\n" "\n" " Sets the topic separator for <channel> to <separator> Converts the\n" " current topic appropriately.\n" " " msgstr "" "[<canal>] <separateur>\n" "\n" "Définir le séparateur de topic du <canal> pour être <séparateur>. Convertit " "le topic actuel de manière appropriée. <canal> n'est nécessaire que si le " "message n'est pas envoyé sur le canal lui-même." �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Topic/locales/it.po�����������������������������������������������������0000644�0001750�0001750�00000036304�13634634532�020462� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2014-07-05 00:09+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:45 #, docstring msgid "Value must include $topic, otherwise the actual topic would be left out." msgstr "Il valore deve includere $topic, altrimenti l'attuale topic non sarà modificato." #: config.py:50 msgid "" "Determines what separator is\n" " used between individually added topics in the channel topic." msgstr "Determina quale separatore utilizzare tra i diversi argomenti del topic del canale." #: config.py:53 msgid "" "Determines what format is used to add\n" " topics in the topic. All the standard substitutes apply, in addition to\n" " \"$topic\" for the topic itself." msgstr "" "Determina quale formato utilizzare per aggiungere argomenti al topic. In aggiunta a\n" " \"$topic\" per il topic stesso, sono applicati tutti gli standard di sostituzione." #: config.py:57 msgid "" "Determines whether the bot will recognize the\n" " TOPICLEN value sent to it by the server and thus refuse to send TOPICs\n" " longer than the TOPICLEN. These topics are likely to be truncated by the\n" " server anyway, so this defaults to True." msgstr "" "Determina se il bot riconoscerà il valore di TOPICLEN inviato dal server e rifiuterà\n" " di inviare topic di lunghezza maggiore. I topic troppo lunghi saranno comunque\n" " troncati dal server, per cui è impostato a True in modo predefinito." #: config.py:62 msgid "" "Determines what the default topic for the channel\n" " is. This is used by the default command to set this topic." msgstr "" "Determina quale sia il topic predefinito del canale. È utilizzato dal comando \"default\"." #: config.py:66 msgid "" "Determines the number of previous\n" " topics to keep around in case the undo command is called." msgstr "" "Determina il numero di topic precedenti da ricordare in caso si utilizzi il comando \"undo\"." #: config.py:69 msgid "" "Determines the\n" " capabilities required (if any) to make any topic changes,\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" " capability." msgstr "" "Determina le capacità richieste (eventuali) per le modifiche al topic (qualsiasi\n" " operazione tranne la lettura). Utilizzare \"canale,capacità\" per le capacità del singolo\n" " canale. L'assenza di un'esplicita anti-capacità significa che l'utente può usare i comandi." #: plugin.py:57 msgid "I'm not currently in %s." msgstr "Attualmente non sono in %s." #: plugin.py:61 msgid "I can't change the topic, I'm not(half)opped and %s is +t." msgstr "Non posso cambiare il topic, non sono halfop e %s ha il mode +t." #: plugin.py:68 msgid "The topic must not include %q." msgstr "Il topic non deve includere %q." #: plugin.py:79 msgid "topic number" msgstr "numero dell'argomento" #: plugin.py:92 msgid "There are no topics in %s." msgstr "Non ci sono argomenti in %s." #: plugin.py:200 msgid "That topic is too long for this server (maximum length: %i; this topic: %i)." msgstr "Il topic è troppo lungo per il server (lunghezza massima: %i; questo: %i)." #: plugin.py:213 #, docstring msgid "" "Check if the user has any of the required capabilities to manage\n" " the channel topic.\n" "\n" " The list of required capabilities is in requireManageCapability\n" " channel config.\n" "\n" " Also allow if the user is a chanop. Since they can change the topic\n" " manually anyway.\n" " " msgstr "" "Controlla che l'utente abbia una delle capacità richieste per gestire il topic del canale.\n" "\n" " L'elenco delle capacità è nella variabile di configurazione requireManageCapability.\n" "\n" " Autorizza anche un operatore del canale, dal momento che potrebbe comunque farlo manualmente.\n" " " #: plugin.py:265 #, docstring msgid "" "[<channel>]\n" "\n" " Returns the topic for <channel>. <channel> is only necessary if the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>]\n" "\n" " Restituisce il topic di <canale>. <canale> è necessario solo se il messaggio\n" " non viene inviato nel canale stesso.\n" " " #: plugin.py:276 #, docstring msgid "" "[<channel>] <topic>\n" "\n" " Adds <topic> to the topics for <channel>. <channel> is only necessary\n" " if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] <argomento>\n" "\n" " Aggiunge <argomento> al topic di <canale>. <canale> è necessario\n" " solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:291 #, docstring msgid "" "[<channel>] <topic>\n" "\n" " Adds <topic> to the topics for <channel>. If the topic is too long\n" " for the server, topics will be popped until there is enough room.\n" " <channel> is only necessary if the message isn't sent in the channel\n" " itself.\n" " " msgstr "" "[<canale>] <argomento>\n" "\n" " Aggiunge <argomento> al topic di <canale>. Se il topic è troppo lungo\n" " per il server, gli argomenti saranno tagliati per rientrare nel limite.\n" " <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:308 #, docstring msgid "" "[<channel>] <number> <topic>\n" "\n" " Replaces topic <number> with <topic>.\n" " " msgstr "" "[<canale>] <numero> <argomento>\n" "\n" " Sostituisce argomento <numero> con <argomento>.\n" " " #: plugin.py:322 #, docstring msgid "" "[<channel>] <topic>\n" "\n" " Adds <topic> to the topics for <channel> at the beginning of the topics\n" " currently on <channel>. <channel> is only necessary if the message\n" " isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] <argomento>\n" "\n" " Aggiunge <argomento> al topic di <canale> all'inizio dello stesso. <canale>\n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:338 #, docstring msgid "" "[<channel>]\n" "\n" " Shuffles the topics in <channel>. <channel> is only necessary if the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>]\n" "\n" " Mescola gli argomenti del topic di <canale>. <canale> è necessario\n" " solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:348 msgid "I can't shuffle 1 or fewer topics." msgstr "Non posso mescolare uno o pochi argomenti." #: plugin.py:360 #, docstring msgid "" "[<channel>] <number> [<number> ...]\n" "\n" " Reorders the topics from <channel> in the order of the specified\n" " <number> arguments. <number> is a one-based index into the topics.\n" " <channel> is only necessary if the message isn't sent in the channel\n" " itself.\n" " " msgstr "" "[<canale>] <numero> [<numero> ...]\n" "\n" " Riordina gli argomenti del topic di <canale> nell'ordine specificato\n" " da <numero>, dove <numero> è un indice di argomenti. <canale> è\n" " necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:373 msgid "I cannot reorder 1 or fewer topics." msgstr "Non posso riordinare uno o pochi argomenti." #: plugin.py:375 msgid "All topic numbers must be specified." msgstr "Bisogna specificare tutti i numeri degli argomenti." #: plugin.py:377 msgid "Duplicate topic numbers cannot be specified." msgstr "Impossibile specificare numeri di argomenti doppi." #: plugin.py:385 #, docstring msgid "" "[<channel>]\n" "\n" " Returns a list of the topics in <channel>, prefixed by their indexes.\n" " Mostly useful for topic reordering. <channel> is only necessary if the\n" " message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>]\n" "\n" " Riporta un elenco degli argomenti di <canale>, con il proprio indice\n" " come prefisso; essenzialmente utile per riordinare gli argomenti. <canale>\n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:394 msgid "%i: %s" msgstr "%i: %s" #: plugin.py:401 #, docstring msgid "" "[<channel>] <number>\n" "\n" " Returns topic number <number> from <channel>. <number> is a one-based\n" " index into the topics. <channel> is only necessary if the message\n" " isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] <numero>\n" "\n" " Restituisce l'argomento <numero> di <canale>, dove <numero> è un indice di argomenti.\n" " <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:416 #, docstring msgid "" "[<channel>] <number> <regexp>\n" "\n" " Changes the topic number <number> on <channel> according to the regular\n" " expression <regexp>. <number> is the one-based index into the topics;\n" " <regexp> is a regular expression of the form\n" " s/regexp/replacement/flags. <channel> is only necessary if the message\n" " isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] <numero> <regexp>\n" "\n" " Modifica l'argomento <numero> di <canale> in base a <regexp>. <numero>\n" " è un indice di argomenti e <regexp> un'espressine regolare nella forma\n" " s/regexp/sostituzione/flag. <canale> è necessario solo se il messaggio\n" " non viene inviato nel canale stesso.\n" " " #: plugin.py:434 #, docstring msgid "" "[<channel>] [<number>] <topic>\n" "\n" " Sets the topic <number> to be <text>. If no <number> is given, this\n" " sets the entire topic. <channel> is only necessary if the message\n" " isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] [<numero>] <argomento>\n" "\n" " Definisce l'argomento <numero>. Se <numero> non è specificato imposta l'intero topic.\n" " <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:455 #, docstring msgid "" "[<channel>] <number>\n" "\n" " Removes topic <number> from the topic for <channel> Topics are\n" " numbered starting from 1; you can also use negative indexes to refer\n" " to topics starting the from the end of the topic. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>] <numero>\n" "\n" " Rimuove l'argomento <numero> dal topic di <canale>; la numerazione\n" ". degli argomenti parte da 1, è possibile definire un valore negativo\n" " per gli inidici iniziando a contare dal fondo. <canale> è necessario\n" " solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:472 #, docstring msgid "" "[<channel>]\n" "\n" " Locks the topic (sets the mode +t) in <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>]\n" "\n" " Blocca il topic (imposta il mode +t) in <canale>. <canale> è\n" " necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:482 msgid "lock the topic" msgstr "bloccare il topic" #: plugin.py:486 #, docstring msgid "" "[<channel>]\n" "\n" " Unlocks the topic (sets the mode -t) in <channel>. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>]\n" "\n" " Sblocca il topic (imposta il mode -t) in <canale>. <canale> è\n" " necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:496 msgid "unlock the topic" msgstr "sbloccare il topic" #: plugin.py:500 #, docstring msgid "" "[<channel>]\n" "\n" " Restores the topic to the last topic set by the bot. <channel> is only\n" " necessary if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>]\n" "\n" " Ripristina il topic all'ultimo impostato dal bot. <canale> è\n" " necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:511 msgid "I haven't yet set the topic in %s." msgstr "Non ho ancora impostato il topic in %s." #: plugin.py:519 #, docstring msgid "" "[<channel>]\n" "\n" " Restores the topic to the one previous to the last topic command that\n" " set it. <channel> is only necessary if the message isn't sent in the\n" " channel itself.\n" " " msgstr "" "[<canale>]\n" "\n" " Ripristina il topic a quello precedente all'ultimo impostato dal comando \"topic\".\n" " <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:533 msgid "There are no more undos for %s." msgstr "Non ci sono più annullamenti per %s." #: plugin.py:538 #, docstring msgid "" "[<channel>]\n" "\n" " Undoes the last undo. <channel> is only necessary if the message isn't\n" " sent in the channel itself.\n" " " msgstr "" "[<canalel>]\n" "\n" " Annulla l'ultimo ripristino. <canale> è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:550 msgid "There are no redos for %s." msgstr "Non ci sono ripristini per %s." #: plugin.py:555 #, docstring msgid "" "[<channel>] <first topic number> <second topic number>\n" "\n" " Swaps the order of the first topic number and the second topic number.\n" " <channel> is only necessary if the message isn't sent in the channel\n" " itself.\n" " " msgstr "" "[<canale>] <numero primo argomento> <numero secondo argomento>\n" "\n" " Scambia l'ordine del numero del primo argomento con il secondo. <canale>\n" " è necessario solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:566 msgid "I refuse to swap the same topic with itself." msgstr "Mi rifiuto di scambiare l'argomento con lo stesso." #: plugin.py:576 #, docstring msgid "" "[<channel>]\n" "\n" " Sets the topic in <channel> to the default topic for <channel>. The\n" " default topic for a channel may be configured via the configuration\n" " variable supybot.plugins.Topic.default.\n" " " msgstr "" "[<canale>]\n" "\n" " Imposta il topic in <canale> a quello predefinito. Il topic predefinito\n" " per un canale può essere configurato tramite la variabile di configurazione\n" " supybot.plugins.Topic.default.\n" " " #: plugin.py:589 msgid "There is no default topic configured for %s." msgstr "Non c'è un topic predefinito configurato per %s." #: plugin.py:595 #, docstring msgid "" "[<channel>] <separator>\n" "\n" " Sets the topic separator for <channel> to <separator> Converts the\n" " current topic appropriately.\n" " " msgstr "" "[<canale>] <separatore>\n" "\n" " Imposta il separatore per gli argomenti del topic di <canale>.\n" " Converte l'attuale topic di conseguenza.\n" " " ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Topic/plugin.py���������������������������������������������������������0000644�0001750�0001750�00000061026�13634634532�017733� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import os import re import random import shutil import tempfile import supybot.conf as conf import supybot.ircdb as ircdb import supybot.utils as utils import supybot.world as world from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization _ = PluginInternationalization('Topic') import supybot.ircdb as ircdb import supybot.utils.minisix as minisix pickle = minisix.pickle def canChangeTopic(irc, msg, args, state): assert not state.channel callConverter('channel', irc, msg, args, state) callConverter('inChannel', irc, msg, args, state) if state.channel not in irc.state.channels: state.error(format(_('I\'m not currently in %s.'), state.channel), Raise=True) c = irc.state.channels[state.channel] if 't' in c.modes and not c.isHalfopPlus(irc.nick): state.error(format(_('I can\'t change the topic, I\'m not (half)opped ' 'and %s is +t.'), state.channel), Raise=True) def getTopic(irc, msg, args, state, format=True): separator = state.cb.registryValue('separator', state.channel, irc.network) if separator in args[0] and not \ state.cb.registryValue('allowSeparatorinTopics', state.channel, irc.network): state.errorInvalid('topic', args[0], format(_('The topic must not include %q.'), separator)) topic = args.pop(0) if format: env = {'topic': topic} formatter = state.cb.registryValue('format', state.channel, irc.network) topic = ircutils.standardSubstitute(irc, msg, formatter, env) state.args.append(topic) def getTopicNumber(irc, msg, args, state): def error(s): state.errorInvalid(_('topic number'), s) try: n = int(args[0]) if not n: raise ValueError except ValueError: error(args[0]) if n > 0: n -= 1 topic = irc.state.getTopic(state.channel) separator = state.cb.registryValue('separator', state.channel, irc.network) topics = splitTopic(topic, separator) if not topics: state.error(format(_('There are no topics in %s.'), state.channel), Raise=True) try: topics[n] except IndexError: error(args[0]) del args[0] while n < 0: n += len(topics) state.args.append(n) addConverter('topic', getTopic) addConverter('topicNumber', getTopicNumber) addConverter('canChangeTopic', canChangeTopic) def splitTopic(topic, separator): return list(filter(None, topic.split(separator))) datadir = conf.supybot.directories.data() filename = conf.supybot.directories.data.dirize('Topic.pickle') class Topic(callbacks.Plugin): """This plugin allows you to use many topic-related functions, such as Add, Undo, and Remove.""" def __init__(self, irc): self.__parent = super(Topic, self) self.__parent.__init__(irc) self.undos = ircutils.IrcDict() self.redos = ircutils.IrcDict() self.lastTopics = ircutils.IrcDict() self.watchingFor332 = ircutils.IrcSet() try: pkl = open(filename, 'rb') try: self.undos = pickle.load(pkl) self.redos = pickle.load(pkl) self.lastTopics = pickle.load(pkl) self.watchingFor332 = pickle.load(pkl) except Exception as e: self.log.debug('Unable to load pickled data: %s', e) pkl.close() except IOError as e: self.log.debug('Unable to open pickle file: %s', e) world.flushers.append(self._flush) def die(self): world.flushers.remove(self._flush) self.__parent.die() def _flush(self): try: pklfd, tempfn = tempfile.mkstemp(suffix='topic', dir=datadir) pkl = os.fdopen(pklfd, 'wb') try: pickle.dump(self.undos, pkl) pickle.dump(self.redos, pkl) pickle.dump(self.lastTopics, pkl) pickle.dump(self.watchingFor332, pkl) except Exception as e: self.log.warning('Unable to store pickled data: %s', e) pkl.close() shutil.move(tempfn, filename) except (IOError, shutil.Error) as e: self.log.warning('File error: %s', e) def _splitTopic(self, irc, channel): topic = irc.state.getTopic(channel) separator = self.registryValue('separator', channel, irc.network) return splitTopic(topic, separator) def _joinTopic(self, irc, channel, topics): separator = self.registryValue('separator', channel, irc.network) return separator.join(topics) def _addUndo(self, irc, channel, topics): stack = self.undos.setdefault(channel, []) stack.append(topics) maxLen = self.registryValue('undo.max', channel, irc.network) del stack[:len(stack) - maxLen] def _addRedo(self, irc, channel, topics): stack = self.redos.setdefault(channel, []) stack.append(topics) maxLen = self.registryValue('undo.max', channel, irc.network) del stack[:len(stack) - maxLen] def _getUndo(self, channel): try: return self.undos[channel].pop() except (KeyError, IndexError): return None def _getRedo(self, channel): try: return self.redos[channel].pop() except (KeyError, IndexError): return None def _formatTopics(self, irc, channel, topics, fit=False): topics = [s for s in topics if s and not s.isspace()] self.lastTopics[channel] = topics newTopic = self._joinTopic(irc, channel, topics) try: maxLen = irc.state.supported['topiclen'] if fit: while len(newTopic) > maxLen: topics.pop(0) self.lastTopics[channel] = topics newTopic = self._joinTopic(irc, channel, topics) elif len(newTopic) > maxLen: if self.registryValue('recognizeTopiclen', channel, irc.network): irc.error(format(_('That topic is too long for this ' 'server (maximum length: %i; this topic: ' '%i).'), maxLen, len(newTopic)), Raise=True) except KeyError: pass return newTopic def _sendTopics(self, irc, channel, topics=None, isDo=False, fit=False): if isinstance(topics, list) or isinstance(topics, tuple): assert topics is not None topics = self._formatTopics(irc, channel, topics, fit) self._addUndo(irc, channel, topics) if not isDo and channel in self.redos: del self.redos[channel] irc.queueMsg(ircmsgs.topic(channel, topics)) irc.noReply() def _checkManageCapabilities(self, irc, msg, channel): """Check if the user has any of the required capabilities to manage the channel topic. The list of required capabilities is in requireManageCapability channel config. Also allow if the user is a chanop. Since they can change the topic 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: return True capabilities = self.registryValue('requireManageCapability', channel, irc.network) 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): return capabilities = self.registryValue('requireManageCapability', channel, irc.network) irc.errorNoCapability(capabilities, Raise=True) else: return def doJoin(self, irc, msg): if ircutils.strEqual(msg.nick, irc.nick): # We're joining a channel, let's watch for the topic. self.watchingFor332.add(msg.args[0]) def do315(self, irc, msg): # Try to restore the topic when not set yet. channel = msg.args[1] c = irc.state.channels.get(channel) if c is None or not self.registryValue('setOnJoin', channel, irc.network): return if irc.nick not in c.ops and 't' in c.modes: self.log.debug('Not trying to restore topic in %s. I\'m not opped ' 'and %s is +t.', channel, channel) return try: topics = self.lastTopics[channel] except KeyError: self.log.debug('No topic to auto-restore in %s.', channel) else: newTopic = self._formatTopics(irc, channel, topics) if c.topic == '' or (c.topic != newTopic and self.registryValue('alwaysSetOnJoin', channel, irc.network)): self._sendTopics(irc, channel, newTopic) def do332(self, irc, msg): if msg.args[1] in self.watchingFor332: self.watchingFor332.remove(msg.args[1]) # Store an undo for the topic when we join a channel. This allows # us to undo the first topic change that takes place in a channel. self._addUndo(irc, msg.args[1], [msg.args[2]]) def topic(self, irc, msg, args, channel): """[<channel>] Returns the topic for <channel>. <channel> is only necessary if the message isn't sent in the channel itself. """ topic = irc.state.channels[channel].topic irc.reply(topic) topic = wrap(topic, ['inChannel']) def add(self, irc, msg, args, channel, topic): """[<channel>] <topic> Adds <topic> to the topics for <channel>. <channel> is only necessary if the message isn't sent in the channel itself. """ self._checkManageCapabilities(irc, msg, channel) topics = self._splitTopic(irc, channel) topics.append(topic) self._sendTopics(irc, channel, topics) add = wrap(add, ['canChangeTopic', rest('topic')]) def fit(self, irc, msg, args, channel, topic): """[<channel>] <topic> Adds <topic> to the topics for <channel>. If the topic is too long for the server, topics will be popped until there is enough room. <channel> is only necessary if the message isn't sent in the channel itself. """ self._checkManageCapabilities(irc, msg, channel) topics = self._splitTopic(irc, channel) topics.append(topic) self._sendTopics(irc, channel, topics, fit=True) fit = wrap(fit, ['canChangeTopic', rest('topic')]) def replace(self, irc, msg, args, channel, i, topic): """[<channel>] <number> <topic> Replaces topic <number> with <topic>. """ self._checkManageCapabilities(irc, msg, channel) topics = self._splitTopic(irc, channel) topics[i] = topic self._sendTopics(irc, channel, topics) replace = wrap(replace, ['canChangeTopic', 'topicNumber', rest('topic')]) def insert(self, irc, msg, args, channel, topic): """[<channel>] <topic> Adds <topic> to the topics for <channel> at the beginning of the topics currently on <channel>. <channel> is only necessary if the message isn't sent in the channel itself. """ self._checkManageCapabilities(irc, msg, channel) topics = self._splitTopic(irc, channel) topics.insert(0, topic) self._sendTopics(irc, channel, topics) insert = wrap(insert, ['canChangeTopic', rest('topic')]) def shuffle(self, irc, msg, args, channel): """[<channel>] Shuffles the topics in <channel>. <channel> is only necessary if the message isn't sent in the channel itself. """ self._checkManageCapabilities(irc, msg, channel) topics = self._splitTopic(irc, channel) if len(topics) == 0 or len(topics) == 1: irc.error(_('I can\'t shuffle 1 or fewer topics.'), Raise=True) elif len(topics) == 2: topics.reverse() else: original = topics[:] while topics == original: random.shuffle(topics) self._sendTopics(irc, channel, topics) shuffle = wrap(shuffle, ['canChangeTopic']) def reorder(self, irc, msg, args, channel, numbers): """[<channel>] <number> [<number> ...] Reorders the topics from <channel> in the order of the specified <number> arguments. <number> is a one-based index into the topics. <channel> is only necessary if the message isn't sent in the channel itself. """ self._checkManageCapabilities(irc, msg, channel) topics = self._splitTopic(irc, channel) num = len(topics) if num == 0 or num == 1: irc.error(_('I cannot reorder 1 or fewer topics.'), Raise=True) if len(numbers) != num: irc.error(_('All topic numbers must be specified.'), Raise=True) if sorted(numbers) != list(range(num)): irc.error(_('Duplicate topic numbers cannot be specified.')) return newtopics = [topics[i] for i in numbers] self._sendTopics(irc, channel, newtopics) reorder = wrap(reorder, ['canChangeTopic', many('topicNumber')]) def list(self, irc, msg, args, channel): """[<channel>] Returns a list of the topics in <channel>, prefixed by their indexes. Mostly useful for topic reordering. <channel> is only necessary if the message isn't sent in the channel itself. """ topics = self._splitTopic(irc, channel) L = [] for (i, t) in enumerate(topics): L.append(format(_('%i: %s'), i + 1, utils.str.ellipsisify(t, 30))) s = utils.str.commaAndify(L) irc.reply(s) list = wrap(list, ['inChannel']) def get(self, irc, msg, args, channel, number): """[<channel>] <number> Returns topic number <number> from <channel>. <number> is a one-based index into the topics. <channel> is only necessary if the message isn't sent in the channel itself. """ topics = self._splitTopic(irc, channel) irc.reply(topics[number]) get = wrap(get, ['inChannel', 'topicNumber']) def change(self, irc, msg, args, channel, number, replacer): """[<channel>] <number> <regexp> Changes the topic number <number> on <channel> according to the regular expression <regexp>. <number> is the one-based index into the topics; <regexp> is a regular expression of the form s/regexp/replacement/flags. <channel> is only necessary if the message isn't sent in the channel itself. """ self._checkManageCapabilities(irc, msg, channel) topics = self._splitTopic(irc, channel) topics[number] = replacer(topics[number]) self._sendTopics(irc, channel, topics) change = wrap(change, ['canChangeTopic', 'topicNumber', 'regexpReplacer']) def set(self, irc, msg, args, channel, number, topic): """[<channel>] [<number>] <topic> Sets the topic <number> to be <text>. If no <number> is given, this sets the entire topic. <channel> is only necessary if the message isn't sent in the channel itself. """ self._checkManageCapabilities(irc, msg, channel) if number is not None: topics = self._splitTopic(irc, channel) topics[number] = topic else: topics = [topic] self._sendTopics(irc, channel, topics) set = wrap(set, ['canChangeTopic', optional('topicNumber'), rest(('topic', False))]) def remove(self, irc, msg, args, channel, numbers): """[<channel>] <number1> [<number2> <number3>...] Removes topics <numbers> from the topic for <channel> Topics are numbered starting from 1; you can also use negative indexes to refer to topics starting the from the end of the topic. <channel> is only necessary if the message isn't sent in the channel itself. """ self._checkManageCapabilities(irc, msg, channel) topics = self._splitTopic(irc, channel) numbers = set(numbers) for n in numbers: # Equivalent of marking the topic for deletion; there's no # simple, easy way of removing multiple items from a list. # pop() will shift the indices after every run. topics[n] = '' topics = [topic for topic in topics if topic != ''] self._sendTopics(irc, channel, topics) remove = wrap(remove, ['canChangeTopic', many('topicNumber')]) def lock(self, irc, msg, args, channel): """[<channel>] Locks the topic (sets the mode +t) in <channel>. <channel> is only necessary if the message isn't sent in the channel itself. """ self._checkManageCapabilities(irc, msg, channel) irc.queueMsg(ircmsgs.mode(channel, '+t')) irc.noReply() lock = wrap(lock, ['channel', ('haveHalfop+', _('lock the topic'))]) def unlock(self, irc, msg, args, channel): """[<channel>] Unlocks the topic (sets the mode -t) in <channel>. <channel> is only necessary if the message isn't sent in the channel itself. """ self._checkManageCapabilities(irc, msg, channel) irc.queueMsg(ircmsgs.mode(channel, '-t')) irc.noReply() unlock = wrap(unlock, ['channel', ('haveHalfop+', _('unlock the topic'))]) def restore(self, irc, msg, args, channel): """[<channel>] Restores the topic to the last topic set by the bot. <channel> is only necessary if the message isn't sent in the channel itself. """ self._checkManageCapabilities(irc, msg, channel) try: topics = self.lastTopics[channel] if not topics: raise KeyError except KeyError: irc.error(format(_('I haven\'t yet set the topic in %s.'), channel)) return self._sendTopics(irc, channel, topics) restore = wrap(restore, ['canChangeTopic']) def refresh(self, irc, msg, args, channel): """[<channel>] Refreshes current topic set by anyone. Restores topic if empty. <channel> is only necessary if the message isn't sent in the channel itself. """ self._checkManageCapabilities(irc, msg, channel) topic = irc.state.channels[channel].topic if topic: self._sendTopics(irc, channel, topic) return try: topics = self.lastTopics[channel] if not topics: raise KeyError except KeyError: irc.error(format(_('I haven\'t yet set the topic in %s.'), channel)) return self._sendTopics(irc, channel, topics) refresh = wrap(refresh, ['canChangeTopic']) def undo(self, irc, msg, args, channel): """[<channel>] Restores the topic to the one previous to the last topic command that set it. <channel> is only necessary if the message isn't sent in the channel itself. """ self._checkManageCapabilities(irc, msg, channel) self._addRedo(irc, channel, self._getUndo(channel)) # current topic. topics = self._getUndo(channel) # This is the topic list we want. if topics is not None: self._sendTopics(irc, channel, topics, isDo=True) else: irc.error(format(_('There are no more undos for %s.'), channel)) undo = wrap(undo, ['canChangetopic']) def redo(self, irc, msg, args, channel): """[<channel>] Undoes the last undo. <channel> is only necessary if the message isn't sent in the channel itself. """ self._checkManageCapabilities(irc, msg, channel) topics = self._getRedo(channel) if topics is not None: self._sendTopics(irc, channel, topics, isDo=True) else: irc.error(format(_('There are no redos for %s.'), channel)) redo = wrap(redo, ['canChangeTopic']) def swap(self, irc, msg, args, channel, first, second): """[<channel>] <first topic number> <second topic number> Swaps the order of the first topic number and the second topic number. <channel> is only necessary if the message isn't sent in the channel itself. """ self._checkManageCapabilities(irc, msg, channel) topics = self._splitTopic(irc, channel) if first == second: irc.error(_('I refuse to swap the same topic with itself.')) return t = topics[first] topics[first] = topics[second] topics[second] = t self._sendTopics(irc, channel, topics) swap = wrap(swap, ['canChangeTopic', 'topicNumber', 'topicNumber']) def save(self, irc, msg, args, channel): """[<channel>] Saves the topic in <channel> to be restored with 'topic default' later. <channel> is only necessary if the message isn't sent in the channel itself. """ self._checkManageCapabilities(irc, msg, channel) topic = irc.state.getTopic(channel) if topic: self.setRegistryValue('default', value=topic, channel=channel) else: self.setRegistryValue('default', value='', channel=channel) irc.replySuccess() save = wrap(save, ['channel', 'inChannel']) def default(self, irc, msg, args, channel): """[<channel>] Sets the topic in <channel> to the default topic for <channel>. The default topic for a channel may be configured via the configuration variable supybot.plugins.Topic.default. """ self._checkManageCapabilities(irc, msg, channel) topic = self.registryValue('default', channel, irc.network) if topic: self._sendTopics(irc, channel, [topic]) else: irc.error(format(_('There is no default topic configured for %s.'), channel)) default = wrap(default, ['canChangeTopic']) def separator(self, irc, msg, args, channel, separator): """[<channel>] <separator> Sets the topic separator for <channel> to <separator> Converts the current topic appropriately. """ self._checkManageCapabilities(irc, msg, channel) topics = self._splitTopic(irc, channel) self.setRegistryValue('separator', separator, channel) self._sendTopics(irc, channel, topics) separator = wrap(separator, ['canChangeTopic', 'something']) Class = Topic # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Topic/test.py�����������������������������������������������������������0000644�0001750�0001750�00000031356�13634634532�017417� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * class TopicTestCase(ChannelPluginTestCase): plugins = ('Topic','User',) def testRemove(self): self.assertError('topic remove 1') _ = self.getMsg('topic add foo') _ = self.getMsg('topic add bar') _ = self.getMsg('topic add baz') self.assertError('topic remove 0') self.assertNotError('topic remove 3') self.assertNotError('topic remove 2') self.assertNotError('topic remove 1') self.assertError('topic remove 1') def testRemoveMultiple(self): self.assertError('topic remove 1 2') _ = self.getMsg('topic add foo') _ = self.getMsg('topic add bar') _ = self.getMsg('topic add baz') _ = self.getMsg('topic add derp') _ = self.getMsg('topic add cheese') self.assertNotError('topic remove 1 2') self.assertNotError('topic remove -1 1') self.assertError('topic remove -99 1') def testReplace(self): _ = self.getMsg('topic add foo') _ = self.getMsg('topic add bar') _ = self.getMsg('topic add baz') self.assertRegexp('topic replace 1 oof', 'oof.*bar.*baz') self.assertRegexp('topic replace -1 zab', 'oof.*bar.*zab') self.assertRegexp('topic replace 2 lorem ipsum', 'oof.*lorem ipsum.*zab') self.assertRegexp('topic replace 2 rab', 'oof.*rab.*zab') def testGet(self): self.assertError('topic get 1') _ = self.getMsg('topic add foo') _ = self.getMsg('topic add bar') _ = self.getMsg('topic add baz') self.assertRegexp('topic get 1', '^foo') self.assertError('topic get 0') def testAdd(self): self.assertError('topic add #floorgle') m = self.getMsg('topic add foo') self.assertEqual(m.command, 'TOPIC') self.assertEqual(m.args[0], self.channel) self.assertEqual(m.args[1], 'foo') m = self.getMsg('topic add bar') self.assertEqual(m.command, 'TOPIC') self.assertEqual(m.args[0], self.channel) self.assertEqual(m.args[1], 'foo | bar') 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('') self.assertNotError('topic add foo') finally: 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') m = self.getMsg('topic insert bar') self.assertEqual(m.args[1], 'bar | foo') def testChange(self): _ = self.getMsg('topic add foo') _ = self.getMsg('topic add bar') _ = self.getMsg('topic add baz') self.assertRegexp('topic change -1 s/baz/biff/', r'foo.*bar.*biff') self.assertRegexp('topic change 2 s/bar/baz/', r'foo.*baz.*biff') self.assertRegexp('topic change 1 s/foo/bar/', r'bar.*baz.*biff') self.assertRegexp('topic change -2 s/baz/bazz/', r'bar.*bazz.*biff') self.assertError('topic change 0 s/baz/biff/') def testConfig(self): try: original = conf.supybot.plugins.Topic.separator() conf.supybot.plugins.Topic.separator.setValue(' <==> ') _ = self.getMsg('topic add foo') m = self.getMsg('topic add bar') self.assertTrue('<==>' in m.args[1]) finally: conf.supybot.plugins.Topic.separator.setValue(original) def testReorder(self): _ = self.getMsg('topic add foo') _ = self.getMsg('topic add bar') _ = self.getMsg('topic add baz') self.assertRegexp('topic reorder 2 1 3', r'bar.*foo.*baz') self.assertRegexp('topic reorder 3 -2 1', r'baz.*foo.*bar') self.assertError('topic reorder 0 1 2') self.assertError('topic reorder 1 -2 2') self.assertError('topic reorder 1 2') self.assertError('topic reorder 2 3 4') self.assertError('topic reorder 1 2 2') self.assertError('topic reorder 1 1 2 3') _ = self.getMsg('topic remove 1') _ = self.getMsg('topic remove 1') self.assertError('topic reorder 1') _ = self.getMsg('topic remove 1') self.assertError('topic reorder 0') def testList(self): _ = self.getMsg('topic add foo') self.assertRegexp('topic list', '1: foo') _ = self.getMsg('topic add bar') self.assertRegexp('topic list', '1: foo.*2: bar') _ = self.getMsg('topic add baz') self.assertRegexp('topic list', '1: foo.* 2: bar.* and 3: baz') def testSet(self): _ = self.getMsg('topic add foo') self.assertRegexp('topic set -1 bar', 'bar') self.assertNotRegexp('topic set -1 baz', 'bar') self.assertResponse('topic set foo bar baz', 'foo bar baz') # Catch a bug we had where setting topic 1 would reset the whole topic orig = conf.supybot.plugins.Topic.format() sep = conf.supybot.plugins.Topic.separator() try: conf.supybot.plugins.Topic.format.setValue('$topic') self.assertResponse('topic add baz', 'foo bar baz%sbaz' % sep) self.assertResponse('topic set 1 bar', 'bar%sbaz' % sep) finally: conf.supybot.plugins.Topic.format.setValue(orig) def testRestore(self): self.getMsg('topic set foo') self.assertResponse('topic restore', 'foo') self.getMsg('topic remove 1') restoreError = 'Error: I haven\'t yet set the topic in #test.' self.assertResponse('topic restore', restoreError) def testRefresh(self): self.getMsg('topic set foo') self.assertResponse('topic refresh', 'foo') self.getMsg('topic remove 1') refreshError = 'Error: I haven\'t yet set the topic in #test.' self.assertResponse('topic refresh', refreshError) def testUndo(self): try: original = conf.supybot.plugins.Topic.format() conf.supybot.plugins.Topic.format.setValue('$topic') self.assertResponse('topic set ""', '') self.assertResponse('topic add foo', 'foo') self.assertResponse('topic add bar', 'foo | bar') self.assertResponse('topic add baz', 'foo | bar | baz') self.assertResponse('topic undo', 'foo | bar') self.assertResponse('topic undo', 'foo') self.assertResponse('topic undo', '') finally: conf.supybot.plugins.Topic.format.setValue(original) def testUndoRedo(self): try: original = conf.supybot.plugins.Topic.format() conf.supybot.plugins.Topic.format.setValue('$topic') self.assertResponse('topic set ""', '') self.assertResponse('topic add foo', 'foo') self.assertResponse('topic add bar', 'foo | bar') self.assertResponse('topic add baz', 'foo | bar | baz') self.assertResponse('topic undo', 'foo | bar') self.assertResponse('topic undo', 'foo') self.assertResponse('topic undo', '') self.assertResponse('topic redo', 'foo') self.assertResponse('topic redo', 'foo | bar') self.assertResponse('topic redo', 'foo | bar | baz') self.assertResponse('topic undo', 'foo | bar') self.assertResponse('topic undo', 'foo') self.assertResponse('topic redo', 'foo | bar') self.assertResponse('topic undo', 'foo') self.assertResponse('topic redo', 'foo | bar') finally: conf.supybot.plugins.Topic.format.setValue(original) def testSwap(self): original = conf.supybot.plugins.Topic.format() try: conf.supybot.plugins.Topic.format.setValue('$topic') self.assertResponse('topic set ""', '') self.assertResponse('topic add foo', 'foo') self.assertResponse('topic add bar', 'foo | bar') self.assertResponse('topic add baz', 'foo | bar | baz') self.assertResponse('topic swap 1 2', 'bar | foo | baz') self.assertResponse('topic swap 1 -1', 'baz | foo | bar') self.assertError('topic swap -1 -1') self.assertError('topic swap 2 -2') self.assertError('topic swap 1 -3') self.assertError('topic swap -2 2') self.assertError('topic swap -3 1') finally: conf.supybot.plugins.Topic.format.setValue(original) def testDefault(self): self.assertError('topic default') try: original = conf.supybot.plugins.Topic.default() conf.supybot.plugins.Topic.default.setValue('foo bar baz') self.assertResponse('topic default', 'foo bar baz') finally: conf.supybot.plugins.Topic.default.setValue(original) def testTopic(self): original = conf.supybot.plugins.Topic.format() try: conf.supybot.plugins.Topic.format.setValue('$topic') self.assertError('topic addd') # Error to send too many args. self.assertResponse('topic add foo', 'foo') self.assertResponse('topic add bar', 'foo | bar') self.assertResponse('topic', 'foo | bar') finally: conf.supybot.plugins.Topic.format.setValue(original) def testSeparator(self): original = conf.supybot.plugins.Topic.format() try: conf.supybot.plugins.Topic.format.setValue('$topic') self.assertResponse('topic add foo', 'foo') self.assertResponse('topic add bar', 'foo | bar') self.assertResponse('topic add baz', 'foo | bar | baz') self.assertResponse('topic separator ::', 'foo :: bar :: baz') self.assertResponse('topic separator ||', 'foo || bar || baz') self.assertResponse('topic separator |', 'foo | bar | baz') finally: conf.supybot.plugins.Topic.format.setValue(original) def testFit(self): original = conf.supybot.plugins.Topic.format() try: conf.supybot.plugins.Topic.format.setValue('$topic') self.irc.state.supported['TOPICLEN'] = 20 self.assertResponse('topic fit foo', 'foo') self.assertResponse('topic fit bar', 'foo | bar') self.assertResponse('topic fit baz', 'foo | bar | baz') self.assertResponse('topic fit qux', 'bar | baz | qux') finally: conf.supybot.plugins.Topic.format.setValue(original) self.irc.state.supported.pop('TOPICLEN', None) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/URL/��������������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�015450� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/URL/__init__.py���������������������������������������������������������0000644�0001750�0001750�00000005071�13634634532�017556� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Keeps track of URLs posted to a channel, along with relevant context. Allows searching for URLs and returning random URLs. Also provides statistics on the URLs in the database. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/URL/config.py�����������������������������������������������������������0000644�0001750�0001750�00000005031�13634634532�017260� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('URL') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('URL', True) URL = conf.registerPlugin('URL') conf.registerChannelValue(URL, 'nonSnarfingRegexp', registry.Regexp(None, _("""Determines what URLs are not to be snarfed and stored in the database for the channel; URLs matching the given regexp will not be snarfed. Give the empty string if you have no URLs that you'd like to exclude from being snarfed."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/URL/locales/������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017072� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/URL/locales/fi.po�������������������������������������������������������0000644�0001750�0001750�00000006533�13634634532�020031� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# URL plugin in Limnoria. # Copyright (C) 2011 Limnoria # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: URL plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 14:04+EET\n" "PO-Revision-Date: 2014-12-20 14:41+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.10\n" #: config.py:45 msgid "" "Determines what URLs are not to be snarfed and\n" " stored in the database for the channel; URLs matching the given regexp " "will\n" " not be snarfed. Give the empty string if you have no URLs that you'd " "like\n" " to exclude from being snarfed." msgstr "" "Määrittää, mitkä URL-osoitteet eivät tule kaapatuiksi eivätkä\n" " tallennetuiksi tietokantaan; URL-osoitteet, jotka täsmäävät annettuun " "säännölliseen lausekkeeseen eivät tule\n" " kaapatuiksi. Anna tyhjä säännöllinen lauseke, mikäli haluat ettei yhtään " "URL-osoitetta \n" " jätetä kaappaamatta." #: plugin.py:65 msgid "" "This plugin records how many URLs have been mentioned in\n" " a channel and what the last URL was." msgstr "" "Tämä plugini tallentaa kuinka monta URL-osoitetta on mainittu kanavalla\n" " ja mikä viimeisin URL oli." #: plugin.py:91 msgid "" "[<channel>]\n" "\n" " Returns the number of URLs in the URL database. <channel> is only\n" " required if the message isn't sent in the channel itself.\n" " " msgstr "" "[<kanava>]\n" "\n" " Palauttaa tietokannassa olevien URL-osoitteiden määrän. <Kanava> on " "vaadittu vain, jos\n" " viestiä ei lähetetä kanavalla itsellään.\n" " " #: plugin.py:98 msgid "I have %n in my database." msgstr "Minulla on %n tietokannassani." #: plugin.py:103 msgid "" "[<channel>] [--{from,with,without,near,proto} <value>] [--nolimit]\n" "\n" " Gives the last URL matching the given criteria. --from is from " "whom\n" " the URL came; --proto is the protocol the URL used; --with is " "something\n" " inside the URL; --without is something that should not be in the " "URL;\n" " --near is something in the same message as the URL. If --nolimit " "is\n" " given, returns all the URLs that are found to just the URL.\n" " <channel> is only necessary if the message isn't sent in the " "channel\n" " itself.\n" " " msgstr "" "[<kanava>] [--{from,with,without,near,proto} <arvo>] [--nolimit]\n" "\n" " Antaa viimeisen URL-osoitteen, joka täsmää annettuihin " "kriteereihin. --from on keneltä\n" " URL-osoite tuli; --proto on protokolla, jota URL-osoite käytti; --" "with on jokin\n" " URL-osoitteen sisällä; --without on jotakin, jonka ei pitäisi olla " "URL-osoitteessa;\n" " --near on jotakin samassa viestissä, kuin URL-osoite. Jos --nolimit " "on\n" " annettu, palauttaa kaikki täsmäävät URL-osoitteet, jotka löydetään.\n" " <Kanava> on vaadittu vain, jos viestiä ei lähetetä kanavalla\n" " itsellään.\n" " " #: plugin.py:145 msgid "No URLs matched that criteria." msgstr "Yksikään URL-osoite ei täsmännyt noihin kriteereihin." ���������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/URL/locales/fr.po�������������������������������������������������������0000644�0001750�0001750�00000005226�13634634532�020040� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-08-10 11:27+CEST\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\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" #: config.py:45 msgid "" "Determines what URLs are not to be snarfed and\n" " stored in the database for the channel; URLs matching the given regexp will\n" " not be snarfed. Give the empty string if you have no URLs that you'd like\n" " to exclude from being snarfed." msgstr "Détermine quelles URLs sont écoutées et stockées dans la base de canal ; les URLs correspondant à l'expression régulière ne seront pas écoutées. Donnez une chaîne vide si vous ne voulez exclure aucune URL" #: plugin.py:89 msgid "" "[<channel>]\n" "\n" " Returns the number of URLs in the URL database. <channel> is only\n" " required if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canal>]\n" "\n" "Retourne le nombre d'URLs dans ma base de données d'URLs. <canal> n'est nécessaire que si le message n'est pas envoyé sur la canal lui-même." #: plugin.py:96 msgid "I have %n in my database." msgstr "J'ai %n dans ma base de données." #: plugin.py:101 #, fuzzy msgid "" "[<channel>] [--{from,with,without,near,proto} <value>] [--nolimit]\n" "\n" " Gives the last URL matching the given criteria. --from is from whom\n" " the URL came; --proto is the protocol the URL used; --with is something\n" " inside the URL; --without is something that should not be in the URL;\n" " --near is something in the same message as the URL. If --nolimit is\n" " given, returns all the URLs that are found to just the URL.\n" " <channel> is only necessary if the message isn't sent in the channel\n" " itself.\n" " " msgstr "" "[<canal>] [--{from,with,without,near,proto} <valeur>] [--nolimit]\n" "\n" "Donne la dernière URL correspondant aux critères. --from correspond à l'utilisateur ayant posé l'URL ; --proto est le protocole utilisé pour l'URL ; --with est quelque chose dans l'URL ; --without est le contraire de --with --near est quelque chose dans le même message que l'URL ; si --nolimit est donné, retourne toutes les URLs trouvées, et non une seule. <canal> n'est nécessaire que si la commande n'est pas envoyée sur le canal lui-même." #: plugin.py:143 msgid "No URLs matched that criteria." msgstr "Aucune URL ne correspond à ces critères." ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/URL/locales/it.po�������������������������������������������������������0000644�0001750�0001750�00000005202�13634634532�020037� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-08-10 02:43+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:45 msgid "" "Determines what URLs are not to be snarfed and\n" " stored in the database for the channel; URLs matching the given regexp will\n" " not be snarfed. Give the empty string if you have no URLs that you'd like\n" " to exclude from being snarfed." msgstr "" "Determina quali URL non vanno intercettati e memorizzati nel database del canale;\n" " quelli che corrispondono alla regexp fornita non verranno coinvolti.\n" " Se non vuoi escludere alcun URL, aggiungi una stringa vuota.\n" #: plugin.py:89 #, docstring msgid "" "[<channel>]\n" "\n" " Returns the number of URLs in the URL database. <channel> is only\n" " required if the message isn't sent in the channel itself.\n" " " msgstr "" "[<canale>]\n" "\n" " Riporta il numero di URL nel database. <canale> è richiesto\n" " solo se il messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:96 msgid "I have %n in my database." msgstr "Ho %n nel mio database." #: plugin.py:101 #, docstring msgid "" "[<channel>] [--{from,with,without,near,proto} <value>] [--nolimit]\n" "\n" " Gives the last URL matching the given criteria. --from is from whom\n" " the URL came; --proto is the protocol the URL used; --with is something\n" " inside the URL; --without is something that should not be in the URL;\n" " --near is something in the same message as the URL. If --nolimit is\n" " given, returns all the URLs that are found to just the URL.\n" " <channel> is only necessary if the message isn't sent in the channel\n" " itself.\n" " " msgstr "" "[<canale>] [--{from,with,without,near,proto} <valore>] [--nolimit]\n" "\n" " Fornisce l'ultimo URL che corrisponde al criterio specificato. --from equivale\n" " a chi ha inserito l'URL; --proto è il protocollo dell'URL usato; --with è\n" " qualcosa all'interno dell'URL, mentre --without è qualcosa non presente;\n" " --near qualcosa nell'URL stesso. Se --nolimit è specificato, riporta\n" " tutti gli URL equivalenti a URL trovati. <canale> è necessario solo se il\n" " messaggio non viene inviato nel canale stesso.\n" " " #: plugin.py:143 msgid "No URLs matched that criteria." msgstr "Nessun URL corrisponde a questo criterio." ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/URL/plugin.py�����������������������������������������������������������0000644�0001750�0001750�00000014754�13634634532�017325� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2010, James McCoy # 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.dbi as dbi import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.utils.minisix as minisix import supybot.plugins as plugins import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('URL') class UrlRecord(dbi.Record): __fields__ = [ ('url', eval), ('by', eval), ('near', eval), ('at', eval), ] class DbiUrlDB(plugins.DbiChannelDB): class DB(dbi.DB): Record = UrlRecord def add(self, url, msg): record = self.Record(url=url, by=msg.nick, near=msg.args[1], at=msg.receivedAt) super(self.__class__, self).add(record) def urls(self, p): L = list(self.select(p)) L.reverse() return L URLDB = plugins.DB('URL', {'flat': DbiUrlDB}) class URL(callbacks.Plugin): """This plugin records how many URLs have been mentioned in a channel and what the last URL was.""" def __init__(self, irc): self.__parent = super(URL, self) self.__parent.__init__(irc) self.db = URLDB() def doPrivmsg(self, irc, msg): if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg): return if msg.channel: if ircmsgs.isAction(msg): text = ircmsgs.unAction(msg) else: text = msg.args[1] for url in utils.web.urlRe.findall(text): r = self.registryValue('nonSnarfingRegexp', msg.channel, irc.network) if r and r.search(url): self.log.debug('Skipping adding %u to db.', url) continue self.log.debug('Adding %u to db.', url) self.db.add(msg.channel, url, msg) @internationalizeDocstring def stats(self, irc, msg, args, channel): """[<channel>] Returns the number of URLs in the URL database. <channel> is only required if the message isn't sent in the channel itself. """ self.db.vacuum(channel) count = self.db.size(channel) irc.reply(format(_('I have %n in my database.'), (count, 'URL'))) stats = wrap(stats, ['channeldb']) @internationalizeDocstring def last(self, irc, msg, args, channel, optlist): """[<channel>] [--{from,with,without,near,proto} <value>] [--nolimit] Gives the last URL matching the given criteria. --from is from whom the URL came; --proto is the protocol the URL used; --with is something inside the URL; --without is something that should not be in the URL; --near is something in the same message as the URL. If --nolimit is given, returns all the URLs that are found to just the URL. <channel> is only necessary if the message isn't sent in the channel itself. """ predicates = [] f = None nolimit = False for (option, arg) in optlist: if isinstance(arg, minisix.string_types): arg = arg.lower() if option == 'nolimit': nolimit = True elif option == 'from': def f(record, arg=arg): return ircutils.strEqual(record.by, arg) elif option == 'with': def f(record, arg=arg): return arg in record.url.lower() elif option == 'without': def f(record, arg=arg): return arg not in record.url.lower() elif option == 'proto': def f(record, arg=arg): return record.url.lower().startswith(arg) elif option == 'near': def f(record, arg=arg): return arg in record.near.lower() if f is not None: predicates.append(f) def predicate(record): for predicate in predicates: if not predicate(record): return False return True urls = [record.url for record in self.db.urls(channel, predicate)] if not urls: irc.reply(_('No URLs matched that criteria.')) else: if nolimit: urls = [format('%u', url) for url in urls] s = ', '.join(urls) else: # We should optimize this with another URLDB method eventually. s = urls[0] irc.reply(s) last = wrap(last, ['channeldb', getopts({'from': 'something', 'with': 'something', 'near': 'something', 'proto': 'something', 'nolimit': '', 'without': 'something',})]) Class = URL # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������limnoria-2020.03.17/plugins/URL/test.py�������������������������������������������������������������0000644�0001750�0001750�00000010145�13634634532�016774� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * urls = """ http://www.ureg.ohio-state.edu/courses/book3.asp http://wwwsearch.sourceforge.net/ClientForm/ http://slashdot.org/comments.pl?sid=75443&cid=6747654 http://baseball-almanac.com/rb_menu.shtml http://www.linuxquestions.org/questions/showthread.php?postid=442905#post442905 http://games.slashdot.org/comments.pl?sid=76027&cid=6785588' http://games.slashdot.org/comments.pl?sid=76027&cid=6785588 http://www.census.gov/ftp/pub/tiger/tms/gazetteer/zcta5.zip http://slashdot.org/~Strike http://lambda.weblogs.com/xml/rss.xml' http://lambda.weblogs.com/xml/rss.xml http://www.sourcereview.net/forum/index.php?showforum=8 http://www.sourcereview.net/forum/index.php?showtopic=291 http://www.sourcereview.net/forum/index.php?showtopic=291&st=0&#entry1778 http://dhcp065-024-059-168.columbus.rr.com:81/~jfincher/old-supybot.tar.gz http://www.sourcereview.net/forum/index.php? http://www.joelonsoftware.com/articles/BuildingCommunitieswithSo.html http://gameknot.com/stats.pl?ddipaolo http://slashdot.org/slashdot.rss http://gameknot.com/chess.pl?bd=1038943 http://codecentral.sleepwalkers.org/ http://gameknot.com/chess.pl?bd=1037471&r=327 http://dhcp065-024-059-168.columbus.rr.com:81/~jfincher/angryman.py https://sourceforge.net/projects/pyrelaychecker/ http://gameknot.com/tsignup.pl """.strip().splitlines() class URLTestCase(ChannelPluginTestCase): plugins = ('URL',) def test(self): counter = 0 #self.assertNotError('url random') for url in urls: self.assertRegexp('url stats', str(counter)) self.feedMsg(url) counter += 1 self.assertRegexp('url stats', str(counter)) self.assertRegexp('url last', re.escape(urls[-1])) self.assertRegexp('url last --proto https', re.escape(urls[-2])) self.assertRegexp('url last --with gameknot.com', re.escape(urls[-1])) self.assertRegexp('url last --with dhcp', re.escape(urls[-3])) self.assertRegexp('url last --from alsdkjf', '^No') self.assertRegexp('url last --without game', 'sourceforge') #self.assertNotError('url random') def testDefaultNotFancy(self): self.feedMsg(urls[0]) self.assertResponse('url last', urls[0]) def testStripsColors(self): self.feedMsg('\x031foo \x034' + urls[0]) self.assertResponse('url last', urls[0]) def testAction(self): self.irc.feedMsg(ircmsgs.action(self.channel, urls[1])) self.assertNotRegexp('url last', '\\x01') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Unix/�������������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�015731� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Unix/__init__.py��������������������������������������������������������0000644�0001750�0001750�00000005101�13634634532�020031� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Provides commands available only on Unix. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # 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/Unix/download' from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Unix/config.py����������������������������������������������������������0000644�0001750�0001750�00000014100�13634634532�017536� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.utils as utils import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Unix') from . import plugin progstats = plugin.progstats def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself as an advanced # user or not. You should effect your configuration by manipulating the # registry as appropriate. from supybot.questions import output, expect, anything, something, yn conf.registerPlugin('Unix', True) output(_("""The "progstats" command can reveal potentially sensitive information about your machine. Here's an example of its output: %s\n""") % progstats()) if yn(_('Would you like to disable this command for non-owner users?'), default=True): conf.supybot.commands.disabled().add('Unix.progstats') class NonOptionString(registry.String): errormsg = _('Value must be a string not starting with a dash (-), not %r.') def __init__(self, *args, **kwargs): self.__parent = super(NonOptionString, self) self.__parent.__init__(*args, **kwargs) def setValue(self, v): if v.startswith('-'): self.error(v) else: self.__parent.setValue(v) class SpaceSeparatedListOfNonOptionStrings(registry.SpaceSeparatedListOfStrings): Value = NonOptionString Unix = conf.registerPlugin('Unix') conf.registerGroup(Unix, 'fortune') conf.registerGlobalValue(Unix.fortune, 'command', registry.String(utils.findBinaryInPath('fortune') or '', _("""Determines what command will be called for the fortune command."""))) conf.registerChannelValue(Unix.fortune, 'short', registry.Boolean(True, _("""Determines whether only short fortunes will be used if possible. This sends the -s option to the fortune program."""))) conf.registerChannelValue(Unix.fortune, 'equal', registry.Boolean(True, _("""Determines whether fortune will give equal weight to the different fortune databases. If false, then larger databases will be given more weight. This sends the -e option to the fortune program."""))) conf.registerChannelValue(Unix.fortune, 'offensive', registry.Boolean(False, _("""Determines whether fortune will retrieve offensive fortunes along with the normal fortunes. This sends the -a option to the fortune program."""))) conf.registerChannelValue(Unix.fortune, 'files', SpaceSeparatedListOfNonOptionStrings([], _("""Determines what specific file (if any) will be used with the fortune command; if none is given, the system-wide default will be used. Do note that this fortune file must be placed with the rest of your system's fortune files."""))) conf.registerGroup(Unix, 'spell') conf.registerGlobalValue(Unix.spell, 'command', registry.String(utils.findBinaryInPath('aspell') or utils.findBinaryInPath('ispell') or '', _("""Determines what command will be called for the spell command."""))) conf.registerGlobalValue(Unix.spell, 'language', registry.String('en', _("""Determines what aspell dictionary will be used for spell checking."""))) conf.registerGroup(Unix, 'wtf') 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.""")) conf.registerGlobalValue(Unix.ping, 'defaultCount', registry.PositiveInteger(5, """Determines what ping and ping6 counts (-c) will default to.""")) conf.registerGroup(Unix, 'ping6') conf.registerGlobalValue(Unix.ping6, 'command', registry.String(utils.findBinaryInPath('ping6') or '', """Determines what command will be called for the ping6 command.""")) conf.registerGroup(Unix, 'sysuptime') conf.registerGlobalValue(Unix.sysuptime, 'command', registry.String(utils.findBinaryInPath('uptime') or '', """Determines what command will be called for the uptime command.""")) conf.registerGroup(Unix, 'sysuname') conf.registerGlobalValue(Unix.sysuname, 'command', registry.String(utils.findBinaryInPath('uname') or '', """Determines what command will be called for the uname command.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Unix/locales/�����������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017353� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Unix/locales/fi.po������������������������������������������������������0000644�0001750�0001750�00000032205�13634634532�020305� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Unix plugin in Limnoria # Copyright (C) 2011 Limnoria # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: Unix plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 11:59+EET\n" "PO-Revision-Date: 2014-12-20 13:10+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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-Generator: Poedit 1.6.10\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: config.py:47 msgid "" "The \"progstats\" command can reveal potentially sensitive\n" " information about your machine. Here's an example of its " "output:\n" "\n" " %s\n" msgstr "" "\"Progstats\" komento voi paljastaa mahdollisesti henkilökohtaista tietoa " "tietokoneestasi.\n" " Tässä on näyte sen ulostulosta:\n" "\n" " %s\n" #: config.py:51 msgid "Would you like to disable this command for non-owner users?" msgstr "" "Haluaisitko poistaa tämän komennon käytöstä muille käyttäjille, kuin " "omistajille?" #: config.py:59 msgid "" "Determines\n" " what command will be called for the fortune command." msgstr "" "Määrittää minkä komennon 'fortune'\n" " komento kutsuu." #: config.py:62 msgid "" "Determines whether only short fortunes will be\n" " used if possible. This sends the -s option to the fortune program." msgstr "" "Määrittää käytetäänkö vain lyhyitä ennustuksia, jos se on mahdollista.\n" " Tämä lähettää fortune ohjelmalle -s asetuksen." #: config.py:65 msgid "" "Determines whether fortune will give equal\n" " weight to the different fortune databases. If false, then larger\n" " databases will be given more weight. This sends the -e option to the\n" " fortune program." msgstr "" "Määrittää antaako 'fortune'\n" " yhtäpaljon painoa erilaisille ennustustietokannoille. Jos tämä asetus " "on 'false', niin\n" " suuremmille tietokannoille annetaan enemmän painoa. Tämä lähettää -e " "asetuksen\n" " ennustus ohjelmalle." #: config.py:70 msgid "" "Determines whether fortune will retrieve\n" " offensive fortunes along with the normal fortunes. This sends the -a\n" " option to the fortune program." msgstr "" "Määrittää hakeeko 'fortune' myös loukkaavia ennustuksia tavallisten\n" " ennustusten lisäksi. Tämä lähettää -a\n" " asetuksen ennustus ohjelmalle." #: config.py:74 msgid "" "Determines what specific file\n" " (if any) will be used with the fortune command; if none is given, the\n" " system-wide default will be used. Do note that this fortune file must " "be\n" " placed with the rest of your system's fortune files." msgstr "" "Määrittää mitä tiettyä tietokantaa\n" " (jos mitään) 'fortune' käyttää; jos yhtään ei ole käytetty, \n" " järjestelmän laajuista oletusta käytetään. Huomaa, että tämän " "ennustustiedoston täytyy olla\n" " sijoitettuna muiden järjestelmän ennustustiedostojen kanssa." #: config.py:82 msgid "" "Determines\n" " what command will be called for the spell command." msgstr "" "Määrittää minkä komennon 'spell'\n" " komento kutsuu." #: config.py:85 msgid "" "Determines what aspell dictionary will be used\n" " for spell checking." msgstr "" "Määrittää mitä aspell sanakirjaa käytetään\n" " oikeinkirjoituksen tarkistukseen." #: config.py:90 msgid "" "Determines what\n" " command will be called for the wtf command." msgstr "" "Määrittää minkä komennon\n" " 'wtf' komento kutsuu." #: plugin.py:74 msgid "Provides Utilities for Unix-like systems." msgstr "Tarjoaa työkaluja Unixin kaltaisille järjestelmille." #: plugin.py:78 msgid "" "<error number or code>\n" "\n" " Returns the number of an errno code, or the errno code of a number.\n" " " msgstr "" "<virhenumero tai koodi>\n" "\n" " Palauttaa virhenumeron , tai virhekoodin virhenumeron.\n" " " #: plugin.py:90 msgid "I can't find the errno number for that code." msgstr "En voi löytää virhenumeroa tuolle koodille." #: plugin.py:93 msgid "(unknown)" msgstr "(tuntematon)" #: plugin.py:94 msgid "%s (#%i): %s" msgstr "%s (#%i): %s" #: plugin.py:99 msgid "" "takes no arguments\n" "\n" " Returns various unix-y information on the running supybot process.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Palauttaa muutamia unixmaisia tietoja suoritettavasta supybot " "prosessista.\n" " " #: plugin.py:107 msgid "" "takes no arguments\n" "\n" " Returns the current pid of the process for this Supybot.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Palauttaa tämän Supybot prosessin nykyisen pidin.\n" " " #: plugin.py:117 msgid "" "<password> [<salt>]\n" "\n" " Returns the resulting of doing a crypt() on <password>. If <salt> " "is\n" " not given, uses a random salt. If running on a glibc2 system,\n" " prepending '$1$' to your salt will cause crypt to return an MD5sum\n" " based crypt rather than the standard DES based crypt.\n" " " msgstr "" "<salasana> [<suola>]\n" "\n" " Palauttaa crypt():in tuloksen <salasanaan>. Jos <suola> ei ole\n" " annettu, satunnaista suolaa käytetään. Jos suoritetaan glibc2 " "järjestelmällä,\n" " '$1$' lisääminen kryptaukseesi aiheuttaa MD5 summaan perustuvan " "kryptauksen, mielummin kuin\n" " normaalin DES pohjaisen kryptin.\n" " " #: plugin.py:136 msgid "" "<word>\n" "\n" " Returns the result of passing <word> to aspell/ispell. The results\n" " shown are sorted from best to worst in terms of being a likely " "match\n" " for the spelling of <word>.\n" " " msgstr "" "<sana>\n" "\n" " Palauttaa <sanan> lähetyksen aspell/ispell ohjelmaan. Palautuvat " "tulokset\n" " näytetään järjestyksessä parhaasta huonompaan sillä perusteella, " "kuinka todennäköisesti ne ovat oikein kirjoitettuja\n" " <sanoja>.\n" " " #: plugin.py:145 msgid "" "The spell checking command is not configured. If one is installed, " "reconfigure supybot.plugins.Unix.spell.command appropriately." msgstr "" "Oikeinkirjoituksen tarkistusohjelma ei ole säädetty. Jos sellainen on " "asennttu, säädä supybot.plugins.Unix.spell.command sopivaksi." #: plugin.py:151 msgid "<word> must begin with an alphabet character." msgstr "<Sanan> täytyy alkaa aakkosellisella merkillä." #: plugin.py:173 msgid "No results found." msgstr "Tuloksia ei löytynyt." #: plugin.py:184 msgid "%q may be spelled correctly." msgstr "%q saattaa olla kirjoitettu oikein." #: plugin.py:186 msgid "I could not find an alternate spelling for %q" msgstr "En löytänyt vaihtoehtoista kirjoitustapaa sanalle %q" #: plugin.py:190 msgid "Possible spellings for %q: %L." msgstr "Mahdolliset kirjoitustavat sanalle %q ovat: %L." #: plugin.py:193 msgid "Something unexpected was seen in the [ai]spell output." msgstr "Jotakin odottamatonta nähtiin [ai]spellin ulostulossa." #: plugin.py:199 msgid "" "takes no arguments\n" "\n" " Returns a fortune from the *nix fortune program.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Palauttaa ennustuksen *nix ennustusohjelmalta.\n" " " #: plugin.py:221 msgid "It seems the configured fortune command was not available." msgstr "Näyttää siltä, että määritetty ennustusohjelma ei ollut saatavilla." #: plugin.py:234 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 "" "Ennustuskomento ei ole määritetty. Jos fortune on asennettu tähän " "järjestelmään, määritä uudelleen asetusarvo supybot.plugins.Unix.fortune." "command oikein." #: plugin.py:241 msgid "" "[is] <something>\n" "\n" " Returns wtf <something> is. 'wtf' is a *nix command that first\n" " appeared in NetBSD 1.5. In most *nices, it's available in some " "sort\n" " of 'bsdgames' package.\n" " " msgstr "" "[is] <jokin>\n" "\n" " Palauttaa mikä ihme <jokin> on. 'wtf' on *nix komento, joka " "ilmestyi ensin\n" " NetBSD 1.5 käyttöjärjestelmässä. Suurimmassa osassa *nixeistä, se " "on saatavilla jonkinlaisessa\n" " 'bsdgames' paketissa.\n" " " #: plugin.py:257 msgid "It seems the configured wtf command was not available." msgstr "Vaikuttaa siltä, ettei määritetty wtf komento ollut saatavilla." #: plugin.py:266 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 "" "Wtf komento ei ole määritetty. Jos se on asennettu tähän järjestelmään, " "määritä supybot.plugins.Unix.wtf.command asetusarvo oikein." #: plugin.py:332 msgid "" "takes no arguments\n" "\n" " Returns the uptime from the system the bot is running on.\n" " " msgstr "" "ei ota parametrejän\n" " Palauttaa järjestelmän, jolla botti on ylläoloajan.\n" " " #: plugin.py:361 msgid "" "takes no arguments\n" "\n" " Returns the uname -a from the system the bot is running on.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Palauttaa komennon \"uname -a\" ulostulon järjestelmästä, jossa botti " "on.\n" " " #: plugin.py:390 msgid "" "<command to call with any arguments>\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 "" "<komento kutsuttavaksi millä tahansa parametreillä> \n" " Kutsuu minkä tahansa komennon, joka on saatavilla järjestelmässä " "palauttaen sen ulostulon.\n" " Vaatii owner-valtuuden.\n" " Huomaa että, koska tämä komento on rajoitettu omistajalle, se ei " "tee\n" " minkäänlaista järjellisyystarkistusta sisäänmenoon/ulostuloon. Joten " "on oma tehtäväsi varmistaa, ettet suorita mitään, mikä sotkee kanavaasi, tai " "laittaa koneesi polvilleen. \n" " " #: plugin.py:420 msgid "" "<command to call with any arguments>\n" " Calls any command available on the system using the shell\n" " specified by the SHELL environment variable, and returns its\n" " 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 "" "<komento kutsuttavaksi millä tahansa patametreillä> \n" " Kutsuu minkä tahansa komennon, joka on saatavilla järjestelmässä " "käyttäen SHELL ympäristömuuttujaa, ja palauttaa sen ulostulon.\n" " Vaatii owner-valtuuden.\n" " Huomaa, että, koska tämä komento on rajoitettu omistajalle, se ei " "tee\n" " minkäänlaista järjellisyystarkistusta sisäänmenoon/ulostuloon. Joten " "on oma tehtäväsi varmistaa, ettet suorita mitään, mikä sotkee kanavaasi tai " "joka\n" " laittaa koneesi\n" " polvilleen. \n" " " #~ msgid "" #~ "[--c <count>] [--i <interval>] [--t <ttl>] [--W <timeout>] <host or ip>\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 <määrä>] [--i <kesto>] [--t <ttl>] [--W <aikakatkaisu>] <isäntä tai " #~ "ip>\n" #~ " Lähettää ICMP kaiutuspyynnön määritettyyn isäntään.\n" #~ " Parametrin täsmäävät niihin, jotka on määritetty ohjekirjasivulla " #~ "ping(8). --c on\n" #~ " rajoitettu kymmeneen tai vähempään (oletus on 5). --i on " #~ "rajoitettu viiteen\n" #~ " tai vähempään. --W on rajoitettu kymmeneen tai vähempään.\n" #~ " " #~ msgid "" #~ "[--c <count>] [--i <interval>] [--t <ttl>] [--W <timeout>] <host or ip>\n" #~ " Sends an ICMP echo request to the specified host.\n" #~ " The arguments correspond with those listed in ping6(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 <määrä>] [--i <kesto>] [--t <ttl>] [--W <aikakatkaisu>] <isäntä tai " #~ "ip>\n" #~ " Lähettää ICMP kaiutuspyynnön määritettyyn isäntään.\n" #~ " Parametrin täsmäävät niihin, jotka on määritetty ohjekirjasivulla " #~ "ping(8). --c on\n" #~ " rajoitettu kymmeneen tai vähempään (oletus on 5). --i on " #~ "rajoitettu viiteen\n" #~ " tai vähempään. --W on rajoitettu kymmeneen tai vähempään.\n" #~ " " �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Unix/locales/fr.po������������������������������������������������������0000644�0001750�0001750�00000024323�13634634532�020320� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Valentin Lorentz <progval@progval.net>, 2012. msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2012-04-15 18:47+EEST\n" "PO-Revision-Date: 2012-07-29 11:58+0100\n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: French <kde-i18n-doc@kde.org>\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-Generator: Lokalize 1.2\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: config.py:47 msgid "" "The \"progstats\" command can reveal potentially sensitive\n" " information about your machine. Here's an example of its output:\n" "\n" " %s\n" msgstr "La commande \"progstats\" peut révéler des informations potentiellement sensibles à propos de votre machine. Voici un exemple de sa sortie : %s" #: config.py:51 msgid "Would you like to disable this command for non-owner users?" msgstr "Voulez-vous désactiver cette commande pour les utilisateurs non-owners ?" #: config.py:59 msgid "" "Determines\n" " what command will be called for the fortune command." msgstr "Détermine quelle commande doit être appelée par la commande 'fortune'" #: config.py:62 msgid "" "Determines whether only short fortunes will be\n" " used if possible. This sends the -s option to the fortune program." msgstr "Détermine si seules les fortunes courtes seront utilisées si possible. Ceci envoie l'option -s au programme fortune." #: config.py:65 msgid "" "Determines whether fortune will give equal\n" " weight to the different fortune databases. If false, then larger\n" " databases will be given more weight. This sends the -e option to the\n" " fortune program." msgstr "Détermine si les fortunes auront le même poids quel que soit la base de données. Si c'est False, alors les plus grandes bases de données auront plus de poids. Ceci envoie l'option -e au programme fortune." #: config.py:70 msgid "" "Determines whether fortune will retrieve\n" " offensive fortunes along with the normal fortunes. This sends the -a\n" " option to the fortune program." msgstr "Détermine si 'fortune' récupérera des fortunes offensibles, en plus des fortunes normales. Ceci envoie l'option -a au programme 'fortune'." #: config.py:74 msgid "" "Determines what specific file\n" " (if any) will be used with the fortune command; if none is given, the\n" " system-wide default will be used. Do note that this fortune file must be\n" " placed with the rest of your system's fortune files." msgstr "Détermine quel fichier spécifique (si il y en a un) est utilisé par la commande 'fortune' ; si aucune n'est donné, le réglage par défaut du système sera utilisé. Notez que ce fichier doit être placé avec le reste de vos fichiers de fortunes." #: config.py:82 msgid "" "Determines\n" " what command will be called for the spell command." msgstr "Détermine quelle commande sera appelée par la commande 'spell'." #: config.py:85 msgid "" "Determines what aspell dictionary will be used\n" " for spell checking." msgstr "Détermine quelle commande sera appelée par la commande 'spell'." #: config.py:90 msgid "" "Determines what\n" " command will be called for the wtf command." msgstr "Détermine quelle commande sera appelée par la commande 'wtf'." #: plugin.py:75 msgid "" "<error number or code>\n" "\n" " Returns the number of an errno code, or the errno code of a number.\n" " " msgstr "" "<numéro d'erreur|code>\n" "\n" "Retourne le code du numéro d'erreur, ou le numéro d'erreur du code." #: 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:90 msgid "(unknown)" msgstr "(inconnu)" #: plugin.py:91 msgid "%s (#%i): %s" msgstr "%s (#%i) : %s" #: plugin.py:96 msgid "" "takes no arguments\n" "\n" " Returns various unix-y information on the running supybot process.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourn différentes informations unix-y à propos du processus de supybot." #: plugin.py:104 msgid "" "takes no arguments\n" "\n" " Returns the current pid of the process for this Supybot.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne le pid du processus de Supybot." #: plugin.py:114 msgid "" "<password> [<salt>]\n" "\n" " Returns the resulting of doing a crypt() on <password>. If <salt> is\n" " not given, uses a random salt. If running on a glibc2 system,\n" " prepending '$1$' to your salt will cause crypt to return an MD5sum\n" " based crypt rather than the standard DES based crypt.\n" " " msgstr "" "<mot de passe> [<sel>]\n" "\n" "Retourne le résultat d'un crypt() sur le <mot de passe>. Si le <sel> 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:133 msgid "" "<word>\n" "\n" " Returns the result of passing <word> to aspell/ispell. The results\n" " shown are sorted from best to worst in terms of being a likely match\n" " for the spelling of <word>.\n" " " msgstr "" "<mot>\n" "\n" "Retourne le résultat du parsage du <mot> avec aspell/ispell. Les résultats affichés sont triés du meilleur au pire en fonction de comment ils correspondent au <mot>." #: 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:148 msgid "<word> must begin with an alphabet character." msgstr "<mo> doit commencer par une lettre de l'alphabet." #: plugin.py:170 msgid "No results found." msgstr "Aucun résultat trouvé." #: plugin.py:181 msgid "%q may be spelled correctly." msgstr "%q semble être orthographié correctement." #: plugin.py:183 msgid "I could not find an alternate spelling for %q" msgstr "Je ne peux pas trouver d'orthographe alternative pour %q" #: plugin.py:187 msgid "Possible spellings for %q: %L." msgstr "Orthographes possibles pour %q : %L" #: plugin.py:190 msgid "Something unexpected was seen in the [ai]spell output." msgstr "Quelque chose d'imprévu a été trouvé dans la sortie de [ai]spell." #: plugin.py:196 msgid "" "takes no arguments\n" "\n" " Returns a fortune from the *nix fortune program.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne une fortune depuis le programme fortune de *nix." #: plugin.py:217 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:226 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:233 msgid "" "[is] <something>\n" "\n" " Returns wtf <something> is. 'wtf' is a *nix command that first\n" " appeared in NetBSD 1.5. In most *nices, it's available in some sort\n" " of 'bsdgames' package.\n" " " msgstr "" "[is] <acronyme>\n" "\n" "Retourne ce que l'<acronyme> 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:248 msgid "It seems the configured wtf command was not available." msgstr "Il semble que la commande wtf ne soit pas disponible." #: plugin.py:257 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:265 msgid "" "[--c <count>] [--i <interval>] [--t <ttl>] [--W <timeout>] <host or ip>\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 <compte>] [--i <intervalle>] [--t <ttl>] [--W <timeout>] <hôte ou ip>\n" "Envoie une requête ICMP echo à l'<hôte> 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:317 msgid "" "takes no arguments\n" "\n" " Returns the uptime from the system the bot is running on.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne l'uptime du système sur lequel fonctionne le bot." #: plugin.py:345 msgid "" "takes no arguments\n" "\n" " Returns the uname -a from the system the bot is running on.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne le \"uname -a\" du système sur lequel le bot fonctionne." #: plugin.py:373 msgid "" "<command to call with any arguments> \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 "" "<commande à appeller avec ses argument>\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." �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Unix/locales/it.po������������������������������������������������������0000644�0001750�0001750�00000024654�13634634532�020334� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2012-01-02 20:09+0100\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:47 msgid "" "The \"progstats\" command can reveal potentially sensitive\n" " information about your machine. Here's an example of its output:\n" "\n" " %s\n" msgstr "" "Il comando \"progstats\" può potenzialmente rivelare informazioni sensibili\n" " riguardo la tua macchina. Ecco un esempio del suo output:\n" "\n" " %s\n" #: config.py:51 msgid "Would you like to disable this command for non-owner users?" msgstr "Vuoi disabilitare questo comando per gli utenti non proprietari (owner)?" #: config.py:59 msgid "" "Determines\n" " what command will be called for the fortune command." msgstr "Determina quale comando verrà richiamato tramite \"fortune\"." #: config.py:62 msgid "" "Determines whether only short fortunes will be\n" " used if possible. This sends the -s option to the fortune program." msgstr "Determina se verranno utilizzati solo fortune brevi. Questo invia l'opzione -s al programma fortune." #: config.py:65 msgid "" "Determines whether fortune will give equal\n" " weight to the different fortune databases. If false, then larger\n" " databases will be given more weight. This sends the -e option to the\n" " fortune program." msgstr "" "Determines se fortune darà uguale peso ai diversi database; se impostato a False,\n" " quelli più grossi avranno la priorità. Questo invia l'opzione -e al programma fortune." #: config.py:70 msgid "" "Determines whether fortune will retrieve\n" " offensive fortunes along with the normal fortunes. This sends the -a\n" " option to the fortune program." msgstr "" "Determina se fortune recupererà citazioni offensive insieme alle normali.\n" " Questo invia l'opzione -a al programma fortune." #: config.py:74 msgid "" "Determines what specific file\n" " (if any) will be used with the fortune command; if none is given, the\n" " system-wide default will be used. Do note that this fortune file must be\n" " placed with the rest of your system's fortune files." msgstr "" "Determina quale file specifico (eventuale) verrà usato con il comando fortune,\n" " se non è specificato utilizzerà il predefinito a livello di sistema. Nota che\n" " questo file va collocato insieme agli altri file di fortune presenti nel sistema.\n" #: config.py:82 msgid "" "Determines\n" " what command will be called for the spell command." msgstr "Determina quale comando verrà richiamato tramite \"spell\"." #: config.py:85 msgid "" "Determines what aspell dictionary will be used\n" " for spell checking." msgstr "" "Determina quale dizionario di aspell verrà utilizzato per il controllo ortografico." #: config.py:90 msgid "" "Determines what\n" " command will be called for the wtf command." msgstr "Determina quale comando verrà richiamato tramite \"wtf\"." #: plugin.py:75 #, docstring msgid "" "<error number or code>\n" "\n" " Returns the number of an errno code, or the errno code of a number.\n" " " msgstr "" "<numero errore o codice>\n" "\n" " Restituisce il numero di un codice di errore (errno) o il codice di errore di un numero.\n" " " #: plugin.py:87 msgid "I can't find the errno number for that code." msgstr "Non trovo un numero di errore per quel codice." #: plugin.py:90 msgid "(unknown)" msgstr "(sconosciuto)" #: plugin.py:91 msgid "%s (#%i): %s" msgstr "%s (#%i): %s" #: plugin.py:96 #, docstring msgid "" "takes no arguments\n" "\n" " Returns various unix-y information on the running supybot process.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Riporta varie informazioni unix-y sul processo supybot in esecuzione.\n" " " #: plugin.py:104 #, docstring msgid "" "takes no arguments\n" "\n" " Returns the current pid of the process for this Supybot.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Riporta l'attuale PID del processo per questo Supybot.\n" " " #: plugin.py:114 #, docstring msgid "" "<password> [<salt>]\n" "\n" " Returns the resulting of doing a crypt() on <password>. If <salt> is\n" " not given, uses a random salt. If running on a glibc2 system,\n" " prepending '$1$' to your salt will cause crypt to return an MD5sum\n" " based crypt rather than the standard DES based crypt.\n" " " msgstr "" "<password> [<sale>]\n" "\n" " Riporta il risultato di crypt() su <password>. Se <sale> non è specificato,\n" " ne utilizza uno casuale. Se eseguito su un sistema glibc2, preporre '$1$' al\n" " sale restituirà una cifratura basata su MD5sum piuttosto che la standard DES.\n" " " #: plugin.py:133 #, docstring msgid "" "<word>\n" "\n" " Returns the result of passing <word> to aspell/ispell. The results\n" " shown are sorted from best to worst in terms of being a likely match\n" " for the spelling of <word>.\n" " " msgstr "" "<parola>\n" "\n" " Riporta il risultato di passare <parola> ad aspell/ispell. I risultati\n" " mostrati sono ordinati dal migliore al peggiore in termini di essere una\n" " potenziale ortografia corretta di <parola>.\n" " " #: plugin.py:142 msgid "The spell checking command is not configured. If one is installed, reconfigure supybot.plugins.Unix.spell.command appropriately." msgstr "Il comando di correzione ortografica non è configurato. Se ve n'è uno installato, configurare la variabile supybot.plugins.Unix.spell.command in modo appropriato." #: plugin.py:148 msgid "<word> must begin with an alphabet character." msgstr "<parola> deve iniziare con un carattere dell'alfabeto." #: plugin.py:170 msgid "No results found." msgstr "Nessun risultato trovato." #: plugin.py:181 msgid "%q may be spelled correctly." msgstr "%q sembra scritto correttamente." #: plugin.py:183 msgid "I could not find an alternate spelling for %q" msgstr "Impossibile trovare un'ortografia alternativa per %q" #: plugin.py:187 msgid "Possible spellings for %q: %L." msgstr "Ortografia possibile per %q: %L." #: plugin.py:190 msgid "Something unexpected was seen in the [ai]spell output." msgstr "È stato trovato qualcosa di inaspettato nell'output di [ai]spell." #: plugin.py:196 #, docstring msgid "" "takes no arguments\n" "\n" " Returns a fortune from the *nix fortune program.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Restituisce un biscottino della fortuna dal programma *nix fortune.\n" " " #: plugin.py:217 msgid "It seems the configured fortune command was not available." msgstr "Sembra che il comando fortune configurato non sia disponibile." #: plugin.py:226 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 "Il comando fortune non è configurato. Se ve n'è uno installato, configurare la variabile supybot.plugins.Unix.fortune.command in modo appropriato." #: plugin.py:233 #, docstring msgid "" "[is] <something>\n" "\n" " Returns wtf <something> is. 'wtf' is a *nix command that first\n" " appeared in NetBSD 1.5. In most *nices, it's available in some sort\n" " of 'bsdgames' package.\n" " " msgstr "" "[is] <acronimo>\n" "\n" " Restituisce il significato di <acronimo>. \"wtf\" è un comando *nix apparso la prima volta\n" " in NetBSD 1.5. Nella maggior parte delle macchine *nix è disponibile nel pacchetto \"bsdgames\".\n" " " #: plugin.py:248 msgid "It seems the configured wtf command was not available." msgstr "Sembra che il comando wtf configurato non sia disponibile." #: plugin.py:257 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 "Il comando wtf non è configurato. Se ve n'è uno installato, configurare la variabile supybot.plugins.Unix.wtf.command in modo appropriato." #: plugin.py:265 #, docstring msgid "" "[--c <count>] [--i <interval>] [--t <ttl>] [--W <timeout>] <host or ip>\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 <conteggio>] [--i <intervallo>] [--t <ttl>] [--W <timeout>] <host o ip>\n" " Invia una pacchetto ICMP di tipo echo request all'host specificato. Gli\n" " argomenti corrispondono a quelli del comando ping(8). --c è limitato a 10\n" " pacchetti (il default è 5). --i è limitato a 5. --W è limitato a 10.\n" " " #: plugin.py:317 #, docstring msgid "" "takes no arguments\n" "\n" " Returns the uptime from the system the bot is running on.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Riporta l'uptime del sistema su cui è in esecuzione il bot.\n" " " #: plugin.py:345 #, docstring msgid "" "takes no arguments\n" "\n" " Returns the uname -a from the system the bot is running on.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Riporta il risultato di 'uname -a' dal sistema su cui è in esecuzione il bot.\n" " " #: plugin.py:373 #, docstring msgid "" "<command to call with any arguments> \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 "" "<comando da richiamare con qualsiasi argomento> \n" " Richiama qualsiai comando disponibile sul sistema e restituisce il suo\n" " output; richiede la capacità owner. Essendo limitato al proprietario,\n" " questo comando non fa alcun controllo su input e output; per cui sta a\n" " te assicurarti di non eseguire nulla che possa inviare molto testo in\n" " canale (flood) o che metta in ginocchio la tua macchina.\n" " " ������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Unix/plugin.py����������������������������������������������������������0000644�0001750�0001750�00000047044�13634634532�017604� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2008-2010, James McCoy # 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 os import re import pwd import sys import crypt import errno import random import select import struct import subprocess import shlex import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.utils.minisix as minisix import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.registry as registry import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Unix') def checkAllowShell(irc): if not conf.supybot.commands.allowShell(): irc.error(_('This command is not available, because ' 'supybot.commands.allowShell is False.'), Raise=True) _progstats_endline_remover = utils.str.MultipleRemover('\r\n') def progstats(): pw = pwd.getpwuid(os.getuid()) response = format('Process ID %i running as user %q and as group %q ' 'from directory %q with the command line %q. ' 'Running on Python %s.', os.getpid(), pw[0], pw[3], os.getcwd(), ' '.join(sys.argv), _progstats_endline_remover(sys.version)) return response class TimeoutError(IOError): pass def pipeReadline(fd, timeout=2): (r, _, _) = select.select([fd], [], [], timeout) if r: return r[0].readline() else: raise TimeoutError class Unix(callbacks.Plugin): """Provides Utilities for Unix-like systems.""" threaded = True @internationalizeDocstring def errno(self, irc, msg, args, s): """<error number or code> Returns the number of an errno code, or the errno code of a number. """ try: i = int(s) name = errno.errorcode[i] except ValueError: name = s.upper() try: i = getattr(errno, name) except AttributeError: irc.reply(_('I can\'t find the errno number for that code.')) return except KeyError: name = _('(unknown)') irc.reply(format(_('%s (#%i): %s'), name, i, os.strerror(i))) errno = wrap(errno, ['something']) @internationalizeDocstring def progstats(self, irc, msg, args): """takes no arguments Returns various unix-y information on the running supybot process. """ irc.reply(progstats()) @internationalizeDocstring def pid(self, irc, msg, args): """takes no arguments Returns the current pid of the process for this Supybot. """ irc.reply(format('%i', os.getpid()), private=True) pid = wrap(pid, [('checkCapability', 'owner')]) _cryptre = re.compile(b'[./0-9A-Za-z]') @internationalizeDocstring def crypt(self, irc, msg, args, password, salt): """<password> [<salt>] Returns the resulting of doing a crypt() on <password>. If <salt> is not given, uses a random salt. If running on a glibc2 system, prepending '$1$' to your salt will cause crypt to return an MD5sum based crypt rather than the standard DES based crypt. """ def makeSalt(): s = b'\x00' while self._cryptre.sub(b'', s) != b'': s = struct.pack('<h', random.randrange(-(2**15), 2**15)) return s if not salt: salt = makeSalt().decode() irc.reply(crypt.crypt(password, salt)) crypt = wrap(crypt, ['something', additional('something')]) @internationalizeDocstring def spell(self, irc, msg, args, word): """<word> Returns the result of passing <word> to aspell/ispell. The results shown are sorted from best to worst in terms of being a likely match for the spelling of <word>. """ # We are only checking the first word spellCmd = self.registryValue('spell.command') if not spellCmd: irc.error(_('The spell checking command is not configured. If one ' 'is installed, reconfigure ' 'supybot.plugins.Unix.spell.command appropriately.'), Raise=True) spellLang = self.registryValue('spell.language') or 'en' if word and not word[0].isalpha(): irc.error(_('<word> must begin with an alphabet character.')) return try: inst = subprocess.Popen([spellCmd, '-l', spellLang, '-a'], close_fds=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) except OSError as e: irc.error(e, Raise=True) ret = inst.poll() if ret is not None: s = inst.stderr.readline().decode('utf8') if not s: s = inst.stdout.readline().decode('utf8') s = s.rstrip('\r\n') s = s.lstrip('Error: ') irc.error(s, Raise=True) (out, err) = inst.communicate(word.encode()) inst.wait() lines = [x.decode('utf8') for x in out.splitlines() if x] lines.pop(0) # Banner if not lines: irc.error(_('No results found.'), Raise=True) line = lines.pop(0) line2 = '' if lines: line2 = lines.pop(0) # parse the output # aspell will sometimes list spelling suggestions after a '*' or '+' # line for complex words. if line[0] in '*+' and line2: line = line2 if line[0] in '*+': resp = format(_('%q may be spelled correctly.'), word) elif line[0] == '#': resp = format(_('I could not find an alternate spelling for %q'), word) elif line[0] == '&': matches = line.split(':')[1].strip() resp = format(_('Possible spellings for %q: %L.'), word, matches.split(', ')) else: resp = _('Something unexpected was seen in the [ai]spell output.') irc.reply(resp) spell = thread(wrap(spell, ['something'])) @internationalizeDocstring def fortune(self, irc, msg, args): """takes no arguments Returns a fortune from the *nix fortune program. """ channel = msg.channel network = irc.network fortuneCmd = self.registryValue('fortune.command') if fortuneCmd: args = [fortuneCmd] if self.registryValue('fortune.short', channel, network): args.append('-s') if self.registryValue('fortune.equal', channel, network): args.append('-e') if self.registryValue('fortune.offensive', channel, network): args.append('-a') args.extend(self.registryValue('fortune.files', channel, network)) try: with open(os.devnull) as null: inst = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=null) except OSError as e: irc.error(_('It seems the configured fortune command was ' 'not available.'), Raise=True) (out, err) = inst.communicate() inst.wait() if minisix.PY3: lines = [i.decode('utf-8').rstrip() for i in out.splitlines()] lines = list(map(str, lines)) else: lines = out.splitlines() lines = list(map(str.rstrip, lines)) lines = filter(None, lines) irc.replies(lines, joiner=' ') else: irc.error(_('The fortune command is not configured. If fortune is ' 'installed on this system, reconfigure the ' 'supybot.plugins.Unix.fortune.command configuration ' 'variable appropriately.')) @internationalizeDocstring def wtf(self, irc, msg, args, foo, something): """[is] <something> Returns wtf <something> is. 'wtf' is a *nix command that first appeared in NetBSD 1.5. In most *nices, it's available in some sort of 'bsdgames' package. """ wtfCmd = self.registryValue('wtf.command') if wtfCmd: something = something.rstrip('?') try: with open(os.devnull, 'r+') as null: inst = subprocess.Popen([wtfCmd, something], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=null) except OSError: irc.error(_('It seems the configured wtf command was not ' 'available.'), Raise=True) (out, foo) = inst.communicate() inst.wait() if out: response = out.decode('utf8').splitlines()[0].strip() response = utils.str.normalizeWhitespace(response) irc.reply(response) else: irc.error(_('The wtf command is not configured. If it is installed ' 'on this system, reconfigure the ' 'supybot.plugins.Unix.wtf.command configuration ' 'variable appropriately.')) wtf = thread(wrap(wtf, [optional(('literal', ['is'])), 'something'])) def _make_ping(command): def f(self, irc, msg, args, optlist, host): """[--c <count>] [--i <interval>] [--t <ttl>] [--W <timeout>] [--4|--6] <host or ip> 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. --4 and --6 can be used if and only if the system has a unified ping command. """ pingCmd = self.registryValue(registry.join([command, 'command'])) if not pingCmd: irc.error('The ping command is not configured. If one ' 'is installed, reconfigure ' 'supybot.plugins.Unix.%s.command appropriately.' % command, Raise=True) 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 == 'W' and val > 10: val = 10 args.append('-%s' % opt) if opt not in ('4', '6'): args.append(str(val)) if '-c' not in args: args.append('-c') args.append(str(self.registryValue('ping.defaultCount'))) args.append(host) try: with open(os.devnull) as null: inst = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=null) except OSError as e: irc.error('It seems the configured ping command was ' 'not available (%s).' % e, Raise=True) result = inst.communicate() if result[1]: # stderr irc.error(' '.join(result[1].decode('utf8').split())) else: response = result[0].decode('utf8').split("\n"); 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:])) f.__name__ = command _hostExpr = re.compile(r'^[a-z0-9][a-z0-9\.-]*[a-z0-9]$', re.I) return thread(wrap(f, [getopts({'c':'positiveInt','i':'float', 't':'positiveInt','W':'positiveInt', '4':'', '6':''}), first('ip', ('matches', _hostExpr, 'Invalid hostname'))])) ping = _make_ping('ping') ping6 = _make_ping('ping6') def sysuptime(self, irc, msg, args): """takes no arguments Returns the uptime from the system the bot is running on. """ uptimeCmd = self.registryValue('sysuptime.command') if uptimeCmd: args = [uptimeCmd] try: with open(os.devnull) as null: inst = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=null) except OSError as e: irc.error('It seems the configured uptime command was ' 'not available.', Raise=True) (out, err) = inst.communicate() inst.wait() lines = out.splitlines() lines = [x.decode('utf8').rstrip() for x in lines] lines = filter(None, lines) irc.replies(lines, joiner=' ') else: irc.error('The uptime command is not configured. If uptime is ' 'installed on this system, reconfigure the ' 'supybot.plugins.Unix.sysuptime.command configuration ' 'variable appropriately.') def sysuname(self, irc, msg, args): """takes no arguments Returns the uname -a from the system the bot is running on. """ unameCmd = self.registryValue('sysuname.command') if unameCmd: args = [unameCmd, '-a'] try: with open(os.devnull) as null: inst = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=null) except OSError as e: irc.error('It seems the configured uptime command was ' 'not available.', Raise=True) (out, err) = inst.communicate() inst.wait() lines = out.splitlines() lines = [x.decode('utf8').rstrip() for x in lines] lines = filter(None, lines) irc.replies(lines, joiner=' ') else: irc.error('The uname command is not configured. If uname is ' 'installed on this system, reconfigure the ' 'supybot.plugins.Unix.sysuname.command configuration ' 'variable appropriately.') def call(self, irc, msg, args, text): """<command to call with any arguments> 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. """ checkAllowShell(irc) self.log.info('Unix: running command "%s" for %s/%s', text, msg.nick, irc.network) args = shlex.split(text) try: with open(os.devnull) as null: inst = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=null) except OSError as 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].decode('utf8').split())) if result[0]: # stdout response = result[0].decode('utf8').splitlines() response = [l for l in response if l] irc.replies(response) call = thread(wrap(call, ["owner", "text"])) def shell(self, irc, msg, args, text): """<command to call with any arguments> Calls any command available on the system using the shell specified by the SHELL environment variable, 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. """ checkAllowShell(irc) self.log.info('Unix: running command "%s" for %s/%s', text, msg.nick, irc.network) try: with open(os.devnull) as null: inst = subprocess.Popen(text, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=null) except OSError as e: irc.error('It seems the shell (%s) was not available (%s)' % (os.getenv('SHELL'), e), Raise=True) result = inst.communicate() if result[1]: # stderr irc.error(' '.join(result[1].decode('utf8').split())) if result[0]: # stdout response = result[0].decode('utf8').splitlines() response = [l for l in response if l] irc.replies(response) shell = thread(wrap(shell, ["owner", "text"])) Class = Unix # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Unix/test.py������������������������������������������������������������0000644�0001750�0001750�00000017014�13634634532�017257� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import os import socket from supybot.test import * try: from unittest import skip, skipIf except ImportError: def skipUnlessSpell(f): return None def skipUnlessFortune(f): return None def skipUnlessPing(f): return None def skipUnlessPing6(f): return None else: skipUnlessSpell = skipIf(utils.findBinaryInPath('aspell') is None and \ utils.findBinaryInPath('ispell') is None, 'aspell/ispell not available.') skipUnlessFortune = skipIf(utils.findBinaryInPath('fortune') is None, 'fortune not available.') if network: skipUnlessPing = skipIf( utils.findBinaryInPath('ping') is None or not setuid, 'ping not available.') if socket.has_ipv6: skipUnlessPing6 = skipIf( utils.findBinaryInPath('ping6') is None or not setuid, 'ping6 not available.') else: skipUnlessPing6 = skip( 'IPv6 not available.') else: skipUnlessPing = skip( 'network not available.') skipUnlessPing6 = skip( 'network not available.') class UnixConfigTestCase(ChannelPluginTestCase): plugins = ('Unix',) def testFortuneFiles(self): self.assertNotError('config channel plugins.Unix.fortune.files ' 'foo bar') self.assertRegexp('config channel plugins.Unix.fortune.files ' '"-foo bar"', 'Error:.*dash.*not u?\'-foo\'') # The u is for Python 2 self.assertNotError('config channel plugins.Unix.fortune.files ""') if os.name == 'posix': class UnixTestCase(PluginTestCase): plugins = ('Unix',) @skipUnlessSpell def testSpell(self): self.assertRegexp('spell Strike', '(correctly|Possible spellings)') # ispell won't find any results. aspell will make some # suggestions. self.assertRegexp('spell z0opadfnaf83nflafl230kasdf023hflasdf', 'not find|Possible spellings') self.assertNotError('spell Strizzike') self.assertError('spell foo bar baz') self.assertError('spell -') self.assertError('spell .') self.assertError('spell ?') self.assertNotError('spell whereever') self.assertNotRegexp('spell foo', 'whatever') def testErrno(self): self.assertRegexp('errno 12', '^ENOMEM') self.assertRegexp('errno ENOMEM', '#12') def testProgstats(self): self.assertNotError('progstats') def testCrypt(self): self.assertNotError('crypt jemfinch') @skipUnlessFortune def testFortune(self): self.assertNotError('fortune') @skipUnlessPing def testPing(self): self.assertNotError('unix ping 127.0.0.1') self.assertError('unix ping') self.assertError('unix ping -localhost') self.assertError('unix ping local%host') @skipUnlessPing 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') @skipUnlessPing 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') @skipUnlessPing 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') @skipUnlessPing 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') @skipUnlessPing6 def testPing6(self): self.assertNotError('unix ping6 ::1') self.assertError('unix ping6') self.assertError('unix ping6 -localhost') self.assertError('unix ping6 local%host') @skipUnlessPing6 def testPing6Count(self): self.assertNotError('unix ping6 --c 1 ::1') self.assertError('unix ping6 --c a ::1') self.assertRegexp('unix ping6 --c 11 ::1','10 packets', timeout=12) self.assertRegexp('unix ping6 ::1','5 packets') @skipUnlessPing6 def testPing6Interval(self): self.assertNotError('unix ping6 --i 1 --c 1 ::1') self.assertError('unix ping6 --i a --c 1 ::1') # Super-user privileged interval setting self.assertError('unix ping6 --i 0.1 --c 1 ::1') @skipUnlessPing6 def testPing6Ttl(self): self.assertNotError('unix ping6 --t 64 --c 1 ::1') self.assertError('unix ping6 --t a --c 1 ::1') @skipUnlessPing6 def testPing6Wait(self): self.assertNotError('unix ping6 --W 1 --c 1 ::1') self.assertError('unix ping6 --W a --c 1 ::1') def testCall(self): self.assertNotError('unix call /bin/ls /') self.assertRegexp('unix call /bin/ls /', 'boot, .*dev, ') self.assertError('unix call /usr/bin/nosuchcommandaoeuaoeu') def testShellForbidden(self): self.assertNotError('unix call /bin/ls /') with conf.supybot.commands.allowShell.context(False): self.assertRegexp('unix call /bin/ls /', 'Error:.*not available.*supybot.commands.allowShell') def testUptime(self): self.assertNotError('unix sysuptime') def testUname(self): self.assertNotError('unix sysuname') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/User/�������������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�015724� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/User/__init__.py��������������������������������������������������������0000644�0001750�0001750�00000004407�13634634532�020034� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Provides commands useful to users in general. This plugin is loaded by default. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload reload(plugin) # In case we're being reloaded. if world.testing: from . import test Class = plugin.Class configure = config.configure ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/User/config.py����������������������������������������������������������0000644�0001750�0001750�00000005174�13634634532�017544� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('User') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('User', True) User = conf.registerPlugin('User') conf.registerChannelValue(User, 'listInPrivate', registry.Boolean(True, _("""Determines whether the output of 'user list' will be sent in private. This prevents mass-highlights of people who use their nick as their bot username."""))) conf.registerGlobalValue(User, 'customWhoamiError', registry.String("", _("""Determines what message the bot sends when a user isn't identified or recognized."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/User/locales/�����������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017346� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/User/locales/de.po������������������������������������������������������0000644�0001750�0001750�00000032605�13634634532�020276� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2011-12-23 13:11+CET\n" "PO-Revision-Date: 2012-04-27 15:48+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: German <fbesser@gmail.com>\n" "Language: de\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" #: plugin.py:49 msgid "" "[--capability=<capability>] [<glob>]\n" "\n" " Returns the valid registered usernames matching <glob>. If <glob> is\n" " not given, returns all registered usernames.\n" " " msgstr "" "[--capability=<Fähigkeit>] [<glob>]\n" "\n" "Gibt die zulässigen registrierten Nutzernamen aus, die auf <glob> zutreffen. Fall <glob> nicht angegeben wird, werden alle registrierten Benutzernamen ausgegeben." #: plugin.py:80 msgid "There are no matching registered users." msgstr "Kein passender registrierter Benutzer gefunden." #: plugin.py:82 msgid "There are no registered users." msgstr "Es gibt keine registrierten Benutzer." #: plugin.py:88 msgid "" "<name> <password>\n" "\n" " Registers <name> with the given password <password> and the current\n" " hostmask of the person registering. You shouldn't register twice; if\n" " you're not recognized as a user but you've already registered, use the\n" " hostmask add command to add another hostmask to your already-registered\n" " user, or use the identify command to identify just for a session.\n" " This command (and all other commands that include a password) must be\n" " sent to the bot privately, not in a channel.\n" " " msgstr "" "<Name> <Passwort>\n" "\n" "Registiert <Name> mit dem angegeben Passwort <Passwort> und der momentanen Hostmaske der registierenden Person. Du solltest dich nicht doppelt registrieren; falls der Bot dich nicht als Benutzer anerkennt, obwohl du bereits registriert bist, benutze den Befehl \"hostmask add\" um eine andere Hostmaske zu deinem existierendem Benutzer hinzuzufügen, oder benutze den Befehl \"identify\" um dich nur für diese Sitzung zu identifizieren. Dieser Befehl (und alle anderen Befehle, die ein Passwort beinhalten) müssen dem Bot privat gesendet werden, nicht in einem Kanal." #: plugin.py:101 msgid "That name is already assigned to someone." msgstr "Dieser Name ist schon an jemanden anders vergeben." #: plugin.py:106 msgid "username" msgstr "Benutzername" #: plugin.py:107 msgid "Hostmasks are not valid usernames." msgstr "Hostmasken sind keine gültigen Benutzernamen." #: plugin.py:114 msgid "Your hostmask is already registered to %s" msgstr "Deine Hostmaske ist schon registiert für %s." #: plugin.py:130 msgid "" "<name> [<password>]\n" "\n" " Unregisters <name> from the user database. If the user giving this\n" " command is an owner user, the password is not necessary.\n" " " msgstr "" "<Name> [<Passwort>]\n" "\n" "Entfernt die Registierung von <Name> aus der Benutzerdatenbank. Falls der Benutzer, der diesen Befehl aufruft ein Besitzer ist, wird kein Passwort benötigt." #: plugin.py:145 msgid "This command has been disabled. You'll have to ask the owner of this bot to unregister your user." msgstr "Dieser Befehl wurde abgeschaltet. Du musst den Besiter des Bots fragen um dich vom Bot zu entfernen." #: plugin.py:158 msgid "" "<name> <new name> [<password>]\n" "\n" " Changes your current user database name to the new name given.\n" " <password> is only necessary if the user isn't recognized by hostmask.\n" " This message must be sent to the bot privately (not on a channel) since\n" " it may contain a password.\n" " " msgstr "" "<Name> <Neuer Name> [<Passwort>]\n" "\n" "Ändert deinen momentanen Namen in der Benutzerdatenbank in den neuen Namen. <Passwort> ist nur nötig, falls der Benutzer anhand der Hostmaske erkannt wird. Diese Nachricht muss dem Bot privat übermittelt werden (nicht in einem Kanal), da sie möglicherweise ein Passwort enthält." #: plugin.py:167 msgid "%q is already registered." msgstr "%q ist schon registriert." #: plugin.py:181 msgid "" "[<name>] <old password> <new password>\n" "\n" " Sets the new password for the user specified by <name> to <new\n" " password>. Obviously this message must be sent to the bot\n" " privately (not in a channel). If the requesting user is an owner\n" " user (and the user whose password is being changed isn't that same\n" " owner user), then <old password> needn't be correct.\n" " " msgstr "" "[<Name>] <altes Passwort> <neues Passwort>\n" "\n" "Setzt das Passwort für den Benutzer <Name> auf <neues Passwort>. Es ist ja wohl klar, dass das dem Bot privat gesendet werden muss (nicht in einem Kanal). Fall der anfordernde Benutzer ein Besitzer ist (und der Benutzer, dessen Passwort geändert wird nicht der gleiche Besitzer ist), muss <altes Passwort> nicht korrekt sein." #: plugin.py:209 #, fuzzy msgid "" "<password> [<True|False>]\n" "\n" " Sets the secure flag on the user of the person sending the message.\n" " Requires that the person's hostmask be in the list of hostmasks for\n" " that user in addition to the password being correct. When the\n" " secure flag is set, the user *must* identify before they can be\n" " recognized. If a specific True/False value is not given, it\n" " inverts the current value.\n" " " msgstr "" "<Passwort> [<True|False>]\n" "\n" "Setzt die Sicherheitsflagge für die Person, die diese Nachricht sendet. Setzt vorraus das die Hostmaske der person in der Liste von Hostmask für diesen Benutzer ist und außerdem muss das Passwort richtig sein. Wenn die Sicherheitsflagge gesetzt ist, *muss* der Benutzer sich identifizieren bevor er erkannt wird. Falls kein True/False Wert angegeben wird, wird der momentane Wert invertiert." #: plugin.py:224 msgid "Secure flag set to %s" msgstr "Sicherheits Flagge ist gesetzt auf %s" #: plugin.py:232 msgid "" "<hostmask|nick>\n" "\n" " Returns the username of the user specified by <hostmask> or <nick> if\n" " the user is registered.\n" " " msgstr "" "<Hostmask|Nick>\n" "\n" "Gibt den Benutzernamen des Benutzers aus, der durch <Hostmaske> oder <Nick> angegeben wird. Falls der Benutzer registrier ist." #: plugin.py:241 msgid "I haven't seen %s." msgstr "Ich habe %s nicht gesehen." #: plugin.py:246 msgid "I don't know who that is." msgstr "Ich weiß nicht wer das ist." #: plugin.py:252 msgid "" "[<nick>]\n" "\n" " Returns the hostmask of <nick>. If <nick> isn't given, return the\n" " hostmask of the person giving the command.\n" " " msgstr "" "[<Nick>]\n" "\n" "Gibt die Hostmaske von <Nick> aus. Falls <Nick> nicht angegeben wird, wird die Hostmaske der Person ausgegeben, die den Befehl gegeben hat." #: plugin.py:264 msgid "" "[<name>]\n" "\n" " Returns the hostmasks of the user specified by <name>; if <name>\n" " isn't specified, returns the hostmasks of the user calling the\n" " command.\n" " " msgstr "" "[<Name>]\n" "\n" "Gibt die Hostmaske des Benutzers <Name> aus; falls <Name> nicht angegeben ist, wird die Hostmaskes des Benutzer ausgegeben, der den Befehl aufgerufen hat." #: plugin.py:276 msgid "%s has no registered hostmasks." msgstr "%s hat keine Hostmasken gesetzt." #: plugin.py:283 msgid "You may only retrieve your own hostmasks." msgstr "Du darfst nur deine eigene Hostmaske abfragen" #: plugin.py:299 msgid "" "[<name>] [<hostmask>] [<password>]\n" "\n" " Adds the hostmask <hostmask> to the user specified by <name>. The\n" " <password> may only be required if the user is not recognized by\n" " hostmask. <password> is also not required if an owner user is\n" " giving the command on behalf of some other user. If <hostmask> is\n" " not given, it defaults to your current hostmask. If <name> is not\n" " given, it defaults to your currently identified name. This message\n" " must be sent to the bot privately (not on a channel) since it may\n" " contain a password.\n" " " msgstr "" "[<Name>] [<Hostmaske>] [<Passwort>]\n" "\n" "Fühgt die Hostmaske <Hostmaske> dem Benutzer <Name> hinzu. Das <Passwort> wird nur benötigt, falls der Benutzer nicht anhand seiner Hostmaske erkannt wird. <Passwort> wird auch nicht benötigt wenn ein Besitzer diesen Befehl für einen anderen Benutzer ausführt. Falls <Hostmaske> nicht angegben wird, wird deine momentane Hostmaske benutzt. Falls <Name> nicht angegeben ist, wird der Name benutzt über den du momentan identifiziert bist. Diese Nachricht muss dem Bot privat gesendet werden (nicht in einem Kanal), da sie ein Passwort enhalten könnte." #: plugin.py:313 msgid "hostmask" msgstr "Hostmaske" #: plugin.py:314 msgid "Make sure your hostmask includes a nick, then an exclamation point (!), then a user, then an at symbol (@), then a host. Feel free to use wildcards (* and ?, which work just like they do on the command line) in any of these parts." msgstr "Stelle sicher das deine Hostmaske einen Nicknamen, dann ein Ausrufezeichen (!), dann einen User, dann ein at Symbol (@) und dann den Host beinhaltet. Du kannst Wildcards verwenden ( * und ?, welche funktionrien wie in der Befehlszeile), in jedem dieser Abschnitte" #: plugin.py:324 #: plugin.py:347 msgid "That hostmask is already registered." msgstr "Diese Hostmaske ist schon registriert." #: plugin.py:355 msgid "" "<name> <hostmask> [<password>]\n" "\n" " Removes the hostmask <hostmask> from the record of the user\n" " specified by <name>. If the hostmask given is 'all' then all\n" " hostmasks will be removed. The <password> may only be required if\n" " the user is not recognized by their hostmask. This message must be\n" " sent to the bot privately (not on a channel) since it may contain a\n" " password.\n" " " msgstr "" "<Name> <Hostmaske> [<Passwort>]\n" "\n" "Entfernt die Hostmaske <Hostmaske> vom Benutzereintrag von <Name>. Falls die angegebene Hostmaske 'all' ist, werden alle Hostmasken entfernt. Das <Passwort> wird nur benötigt, falls der Benutzer nicht über seine Hostmaske erkannt wird. Diese Nachricht muss dem Bot privat gesendet werden (nicht in einem Kanal), da sie ein Passwort enhalten könnte." #: plugin.py:374 msgid "All hostmasks removed." msgstr "Alle Hostmasken entfernt." #: plugin.py:378 msgid "There was no such hostmask." msgstr "Es gibt keine solche Hostmaske." #: plugin.py:387 msgid "" "[<name>]\n" "\n" " Returns the capabilities of the user specified by <name>; if <name>\n" " isn't specified, returns the capabilities of the user calling the\n" " command.\n" " " msgstr "" "[<Name>]\n" "\n" "Gibt die Fähigkeiten des Benutzers <Name> aus; falls <Name> nicht angegeben wird, werden die Fähigkeiten des Benutzer ausgegeben, der diesen Befehl ausführt." #: plugin.py:407 msgid "" "<name> <password>\n" "\n" " Identifies the user as <name>. This command (and all other\n" " commands that include a password) must be sent to the bot privately,\n" " not in a channel.\n" " " msgstr "" "<Name> <Passwort>\n" "\n" "identifiziert den Benutzer als <Name>. Dieser Befehl (und alle anderen Befehle die ein Passwort beinhalten) muss an den Bot privat gesendet werden, nicht in einem Kanal." #: plugin.py:419 msgid "Your secure flag is true and your hostmask doesn't match any of your known hostmasks." msgstr "Dein Sicherheitsflag ist auf wahr gesetzt und deine Hostmaske passt zu keiner Hostmaske die zu deinen passt." #: plugin.py:429 msgid "" "takes no arguments\n" "\n" " Un-identifies you. Note that this may not result in the desired\n" " effect of causing the bot not to recognize you anymore, since you may\n" " have added hostmasks to your user that can cause the bot to continue to\n" " recognize you.\n" " " msgstr "" "hat keine Argumente\n" "\n" "Hebt deine Identifizierung auf. Beachte das dies eventuell nicht den geschwünschten Effekt erzielt, dass dich der Bot nichtmehr erkennt. Da du du möglicherweise Hostmasken zu deinem Bot hinzugefügt hast, die dazu führen das der Bot dich weitehrin erkennt." #: plugin.py:438 msgid "If you remain recognized after giving this command, you're being recognized by hostmask, rather than by password. You must remove whatever hostmask is causing you to be recognized in order not to be recognized." msgstr "Falls du weiterhin beachtet wirst nach diesem Befehl, wirst du durch deine Hostmaske beachtet, anstatt deines Passworts. Du musst die Hostmaske entfernen die dazu führt du das beachtet wirst um nicht mehr beachtet zu werden." #: plugin.py:447 msgid "" "takes no arguments\n" "\n" " Returns the name of the user calling the command.\n" " " msgstr "" "hat keine Argumente\n" "\n" "Gibt den Namen den Benutzers aus, der den Befehl aufruft." #: plugin.py:455 msgid "I don't recognize you." msgstr "Ich erkenne dich nicht" #: plugin.py:460 msgid "" "takes no arguments\n" "\n" " Returns some statistics on the user database.\n" " " msgstr "" "hat keine Argumente\n" "\n" "Gibt ein paar Statistiken über die Benutzerdatenbank zurück." #: plugin.py:478 msgid "I have %s registered users with %s registered hostmasks; %n and %n." msgstr "Ich habe %s registierte Benutzer mit %s registrierten Hostmasken; %n und %n." ���������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/User/locales/fi.po������������������������������������������������������0000644�0001750�0001750�00000050006�13634634532�020277� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# User plugin in Limnoria. # Copyright (C) 2011- 2014 Limnoria # Mikaela Suomalainen <mkaysi@outlook.com>, 2011-2014. # msgid "" msgstr "" "Project-Id-Version: User plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 11:59+EET\n" "PO-Revision-Date: 2014-12-20 12:00+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: suomi <>\n" "Language: fi\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" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.6.10\n" #: plugin.py:47 msgid "" "Provides commands for dealing with users, such as registration and\n" " authentication to the bot. This is a core Supybot plugin that should\n" " not be removed!" msgstr "" "Sisältää komennot käyttäjien hallinnointiin, kuten rekisteröimiseen ja " "botille tunnistautumiseen.\n" " Tämä on Supybotin ydin plugin, jota ei pitäisi poistaa!" #: plugin.py:56 msgid "" "[--capability=<capability>] [<glob>]\n" "\n" " Returns the valid registered usernames matching <glob>. If <glob> " "is\n" " not given, returns all registered usernames.\n" " " msgstr "" "[--capability=<valtuus>] [<glob>]\n" "\n" " Palauttaa kelvolliset rekisteröidyt käyttäjätunnukset, jotka " "täsmäävät <globiin>. Jos <glob> ei\n" " ole annettu, palauttaa kaikki rekisteröidyt käyttäjätunnukset.\n" " " #: plugin.py:71 msgid "This is a private capability. Only admins can see who has it." msgstr "" "Tämä on yksityinen valtuus. Vain ylläpitäjät voivat nähdä keillä on se." #: plugin.py:96 msgid "There are no matching registered users." msgstr "Täsmääviä rekisteröityjä käyttäjiä ei ole." #: plugin.py:98 msgid "There are no registered users." msgstr "Rekisteröityneitä käyttäjiä ei ole." #: plugin.py:104 msgid "" "<name> <password>\n" "\n" " Registers <name> with the given password <password> and the current\n" " hostmask of the person registering. You shouldn't register twice; " "if\n" " you're not recognized as a user but you've already registered, use " "the\n" " hostmask add command to add another hostmask to your already-" "registered\n" " user, or use the identify command to identify just for a session.\n" " This command (and all other commands that include a password) must " "be\n" " sent to the bot privately, not in a channel.\n" " " msgstr "" "<nimi> <salasana>\n" "\n" " Rekisteröi <nimen> annetulla <salasanalla> ja rekisteröityvän " "henkilön nykyisellä hostmaskilla.\n" " Sinun ei pitäisi rekisteröityä kahdesti; jos\n" " sinua ei tunnisteta käyttäjäksi, käytä komentoa\n" " 'hostmask add' lisätäksesi hostmaskin valmiiksi rekisteröidylle\n" " käyttäjällesi, tai käytä komentoa 'identify' tunnistautuaksesi vain " "istunnon ajaksi.\n" " Tämä komento (ja kaikki muut komennot, jotka sisältävät salasanan) " "täytyy lähettää\n" " botille yksityisesti, ei kanavalla.\n" " " #: plugin.py:117 msgid "That name is already assigned to someone." msgstr "Tuo nimi on jo liitetty johonkuhun." #: plugin.py:122 msgid "username" msgstr "käyttäjänimi" #: plugin.py:123 msgid "Hostmasks are not valid usernames." msgstr "Hostmaskit eivät ole kelvollisia käyttäjänimiä" #: plugin.py:130 msgid "Your hostmask is already registered to %s" msgstr "Sinun hostmaskisi on jo rekisteröity käyttäjälle %s." #: plugin.py:146 msgid "" "<name> [<password>]\n" "\n" " Unregisters <name> from the user database. If the user giving this\n" " command is an owner user, the password is not necessary.\n" " " msgstr "" "<nimi> [<salasana>]\n" "\n" " Poistaa <nimen> käyttäjätietokannasta. Mikäli käyttäjä, joka antaa " "tämän komennon omaa 'owner' valtuuden,\n" " salasana ei ole vaadittu.\n" " " #: plugin.py:161 msgid "" "This command has been disabled. You'll have to ask the owner of this bot to " "unregister your user." msgstr "" "Tämä komento on poistettu käytöstä. Sinun täytyy pyytää tämän botin " "omistajaa poistaaksesi käyttäjätunnuksesi." #: plugin.py:174 msgid "" "<name> <new name> [<password>]\n" "\n" " Changes your current user database name to the new name given.\n" " <password> is only necessary if the user isn't recognized by " "hostmask.\n" " This message must be sent to the bot privately (not on a channel) " "since\n" " it may contain a password.\n" " " msgstr "" "<nimi> <uusi nimi> [<salasana>]\n" "\n" " Vaihtaa nykyisen nimesi käyttäjätietokannassa annetuksi <uudeksi " "nimeksi>.\n" " <Salasana> on vaadittu vain, jos käyttäjä ei ole tunnistettu " "hostmaskilla.\n" " Tämä viesti täytyy lähettää botille yksityisesti (ei kanavalla), " "koska \n" " se saattaa sisältää salasanan.\n" " " #: plugin.py:183 msgid "%q is already registered." msgstr "%q on jo rekisteröitynyt." #: plugin.py:197 msgid "" "[<name>] <old password> <new password>\n" "\n" " Sets the new password for the user specified by <name> to <new\n" " password>. Obviously this message must be sent to the bot\n" " privately (not in a channel). If the requesting user is an " "owner\n" " user (and the user whose password is being changed isn't that " "same\n" " owner user), then <old password> needn't be correct.\n" " " msgstr "" "[<nimi>] <vanha salasana> <uusi salasana>\n" "\n" " Asettaa <nimen> määrittämän käyttäjätunnuksen salasanan " "<uudeksi\n" " salasanaksi>. Ilmiselvästi tämä viesti täytyy lähettää botille " "yksityisesti\n" " (ei kanavalla). Jos pyytävä käyttäjä omaa valtuuden 'owner'\n" " (ja käyttäjä, jonka salasanaa vaihdetaan ei ole sama \n" " 'owner' valtuuden omaava käyttäjä), silloin <vanhan salasanan> " "ei tarvitse olla oikein.\n" " " #: plugin.py:225 msgid "" "<password> [<True|False>]\n" "\n" " Sets the secure flag on the user of the person sending the " "message.\n" " Requires that the person's hostmask be in the list of hostmasks " "for\n" " that user in addition to the password being correct. When the\n" " secure flag is set, the user *must* identify before they can be\n" " recognized. If a specific True/False value is not given, it\n" " inverts the current value.\n" " " msgstr "" "<salasana> [<True|False>]\n" "\n" " Asettaa 'secure' lipun käyttäjään, joka lähettää viestin.\n" " Vaatii, että henkilön hostmask on hostmaskien listassa oikean " "salasanan.\n" " lisäksi.\n" " Kun 'secure' lippu on asetettu, käyttäjän *täytyy* tunnistautua, " "ennen kuin hänet voidaan tunnistaa.\n" " Jos True/False arvoa ei ole annettu, \n" " nykyinen arvo käännetään.\n" " " #: plugin.py:240 msgid "Secure flag set to %s" msgstr "'Secure' lippu on asetettu arvoon %s" #: plugin.py:248 msgid "" "<hostmask|nick>\n" "\n" " Returns the username of the user specified by <hostmask> or <nick> " "if\n" " the user is registered.\n" " " msgstr "" "<hostmask|nimimerkki>\n" "\n" " Palauttaa käyttäjätunnuksen, jonka määrittää <hostmask> tai " "<nimimerkki>, mikäli\n" " käyttäjä on rekisteröitynyt.\n" " " #: plugin.py:257 msgid "I haven't seen %s." msgstr "En ole nähnyt käyttäjää %s." #: plugin.py:262 msgid "I don't know who that is." msgstr "En tiedä kuka tuo on." #: plugin.py:268 msgid "" "[<nick>]\n" "\n" " Returns the hostmask of <nick>. If <nick> isn't given, return " "the\n" " hostmask of the person giving the command.\n" " " msgstr "" "[<nimimerkki>]\n" "\n" " Palauttaa <nimimerkin> hostmaskin. Jos <nimimerkki> ei ole " "annettu, palauttaa\n" " komennon antavan nimimerkin hostmaskin\n" " " #: plugin.py:280 msgid "" "[<name>]\n" "\n" " Returns the hostmasks of the user specified by <name>; if " "<name>\n" " isn't specified, returns the hostmasks of the user calling the\n" " command.\n" " " msgstr "" "[<nimi>]\n" "\n" " Palauttaa käyttäjän, jonka määrittää <nimi>, hostmaskit; jos " "<nimi>\n" " ei ole määritetty, palauttaa käyttäjän, joka antaa komennon\n" " hostmaskit.\n" " " #: plugin.py:292 msgid "%s has no registered hostmasks." msgstr "%s ei ole rekisteröinyt hostmaskeja." #: plugin.py:299 msgid "You may only retrieve your own hostmasks." msgstr "Voit saada vain omat hostmaskisi." #: plugin.py:315 msgid "" "[<name>] [<hostmask>] [<password>]\n" "\n" " Adds the hostmask <hostmask> to the user specified by <name>. " "The\n" " <password> may only be required if the user is not recognized " "by\n" " hostmask. <password> is also not required if an owner user is\n" " giving the command on behalf of some other user. If <hostmask> " "is\n" " not given, it defaults to your current hostmask. If <name> is " "not\n" " given, it defaults to your currently identified name. This " "message\n" " must be sent to the bot privately (not on a channel) since it " "may\n" " contain a password.\n" " " msgstr "" "[<nimi>] [<hostmask>] [<salasana>]\n" "\n" " Lisää <hostmaskin>, käyttäjälle jonka määrittää <nimi>. \n" " <Salasana> on vaadittu vain, jos käyttäjää ei tunnisteta\n" " hostmaskilla. <Salasana> ei myöskään ole vaadittu, mikäli " "omistaja\n" " käyttäjä tekee komennon toiselle käyttäjälle. Jos <hostmaski> " "ei\n" " ole annettu, se on oletuksena nykyinen hostmaskisi. " "Jos<käyttäjänimi> ei ole\n" " annettu, se on oletuksena nykyinen tunnistettu käyttäjänimesi. " "Tämä komento\n" " täytyy lähettää botille yksityisesti (ei kanavalla), koska se " "saattaa\n" " sisältää salasanan.\n" " " #: plugin.py:329 msgid "hostmask" msgstr "hostmask" #: plugin.py:330 msgid "" "Make sure your hostmask includes a nick, then an exclamation point (!), then " "a user, then an at symbol (@), then a host. Feel free to use wildcards (* " "and ?, which work just like they do on the command line) in any of these " "parts." msgstr "" "Varmista, että hostmaskisi sisältää nimimerkin, sitten eroitus kohdan, p(!), " "sitten käyttäjän, sitten ät symboolin (@), sitten isännän. Käytä " "jokerimerkkejä vapaasti (* ja ?, jotka toimivat samalla tavalla, kuin " "komentorivillä) missä tahansa näistä osista." #: plugin.py:340 plugin.py:361 msgid "That hostmask is already registered." msgstr "Tuo hostmaski on jo rekisteröity." #: plugin.py:371 #, fuzzy msgid "" "[<name>] [<hostmask>] [<password>]\n" "\n" " Removes the hostmask <hostmask> from the record of the user\n" " specified by <name>. If the hostmask given is 'all' then all\n" " hostmasks will be removed. The <password> may only be required " "if\n" " the user is not recognized by their hostmask. This message must " "be\n" " sent to the bot privately (not on a channel) since it may " "contain a\n" " password. If <hostmask> is\n" " not given, it defaults to your current hostmask. If <name> is " "not\n" " given, it defaults to your currently identified name.\n" " " msgstr "" "[<nimi>] [<hostmask>] [<salasana>]\n" "\n" " Poistaa <hostmaskin> <nimen> määrittämän käyttäjän tiedoista.\n" " Joa annettu hostmask on 'all', kaikki hostmaskit poistetaan.\n" " <Salasana> voidaan vaatia, mikäli käyttäjää ei ole tunnistettu hostmaskin " "perusteella.\n" " Viesti täytyy lähettää botille yksityisesti (ei kanavalla), koska se voi " "sisältää salasanan.\n" " Jos hostmaskia ei ole annettu, se on oletuksena nykyinen tunnistetu " "hostmask.\n" " Jos nimeä ei ole annettu, se on oletuksena nykyinen tunnistettu nimi.\n" " " #: plugin.py:394 msgid "All hostmasks removed." msgstr "Kaikki hostmaskit poistettu." #: plugin.py:398 msgid "There was no such hostmask." msgstr "Tuollaista hostmaskia ei ollut." #: plugin.py:411 msgid "GPG features are not enabled." msgstr "GPG toiminnot eivät ole käytössä." #: plugin.py:425 msgid "" "<key id> <key server>\n" "\n" " Add a GPG key to your account." msgstr "" "<avain id> <avain-palvelin>\n" "\n" " Lisää GPG-avaimen tunnuksellesi." #: plugin.py:429 msgid "This key is already associated with your account." msgstr "Tämä avain on jo liitetty tunnukseesi." #: plugin.py:433 msgid "%n imported, %i unchanged, %i not imported." msgstr "%n tuotu, %i muuttumaton, %i ei tuotu." #: plugin.py:434 msgid "key" msgstr "avain" #: plugin.py:445 msgid "You must give a valid key id" msgstr "Kelvollinen avain-id vaaditaan" #: plugin.py:447 msgid "You must give a valid key server" msgstr "Kelvollinen avainpalvelin vaaditaan" #: plugin.py:451 msgid "" "<fingerprint>\n" "\n" " Remove a GPG key from your account." msgstr "" "<sormenjälki>\n" "\n" " Poistaa GPG-avaimen tunnukseltasi." #: plugin.py:467 msgid "GPG key not associated with your account." msgstr "GPG-avainta ei ole liitetty tunnukseesi." #: plugin.py:472 msgid "" "takes no arguments\n" "\n" " List your GPG keys." msgstr "" "ei ota parametrejä\n" "\n" " Antaa luettelon GPG-avaimistasi." #: plugin.py:477 msgid "No key is associated with your account." msgstr "Yhtään avainta ei ole liitetty tunnukseesi." #: plugin.py:484 msgid "" "takes no arguments\n" "\n" " Send you a token that you'll have to sign with your key." msgstr "" "ei ota parametrejä\n" "\n" " Lähettää merkin, jonka allekirjoitat avaimellasi." #: plugin.py:491 msgid "" "Your token is: %s. Please sign it with your GPG key, paste it somewhere, and " "call the 'auth' command with the URL to the (raw) file containing the " "signature." msgstr "" "Avaimesi on: %s. Allekirjoita se GPG-avaimellasi, liitä jonnekin ja käytä " "'auth'-komentoa URL-osoitteella (raakaan) tiedostoon, joka sisältää " "allekirjoituksen." #: plugin.py:505 msgid "" "<url>\n" "\n" " Check the GPG signature at the <url> and authenticates you if\n" " the key used is associated to a user." msgstr "" "<URL>\n" "\n" " Tarkistaa <URL>-osoitteessa olevan GPG-allekirjoituksen ja tunnistaa sinut, " "jos\n" " avain on liitetty käyttäjään." #: plugin.py:515 msgid "Signature or token not found." msgstr "Allekirjoitusta tai merkkiä ei löydy." #: plugin.py:519 msgid "Unknown token. It may have expired before you submit it." msgstr "Tuntematon merkki. Se on voinut vanhentua ennen, kuin lähetit sen." #: plugin.py:522 msgid "Your hostname/nick changed in the process. Authentication aborted." msgstr "" "Isäntänimesi/nimimerkkisi vaihtui prosessissa. Tunnistautuminen keskeytetty." #: plugin.py:534 plugin.py:581 msgid "" "Your secure flag is true and your hostmask doesn't match any of your known " "hostmasks." msgstr "" "Sinun 'secure' lippusi on 'true' ja sinun hostmaskisi ei täsmää yhteenkään " "sinun tunnettuun hostmaskiisi." #: plugin.py:538 msgid "You are now authenticated as %s." msgstr "Olet nyt tunnistautunut käyttäjäksi %s." #: plugin.py:541 msgid "Unknown GPG key." msgstr "Tuntematon GPG-avain." #: plugin.py:543 msgid "" "Signature could not be verified. Make sure this is a valid GPG signature and " "the URL is valid." msgstr "" "Allekirjoitusta ei voitu varmistaa. Varmista, että tämä on kelvollinen GPG-" "allekirjoitus ja URL-osoite on kelvollinen." #: plugin.py:549 msgid "" "[<name>]\n" "\n" " Returns the capabilities of the user specified by <name>; if <name>\n" " isn't specified, returns the capabilities of the user calling the\n" " command.\n" " " msgstr "" "[<nimi>]\n" "\n" " Palauttaa käyttäjän, jonka määrittää <nimi> valtuudet; jos <nimi>\n" " ei ole määritetty, palauttaa käyttäjän, joka kutsuu komennonn " "valtuudet.\n" " " #: plugin.py:569 msgid "" "<name> <password>\n" "\n" " Identifies the user as <name>. This command (and all other\n" " commands that include a password) must be sent to the bot " "privately,\n" " not in a channel.\n" " " msgstr "" "<käyttäjä> <salasana>\n" "\n" " Tunnistaa käyttäjän <käyttäjäksi>. Tämä komento (ja kaikki muut " "komennot, \n" " jotka sisältävät salasanan) täytyy lähettää botille yksityisesti, \n" " ei kanavalla.\n" " " #: plugin.py:591 msgid "" "takes no arguments\n" "\n" " Un-identifies you. Note that this may not result in the desired\n" " effect of causing the bot not to recognize you anymore, since you " "may\n" " have added hostmasks to your user that can cause the bot to continue " "to\n" " recognize you.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Kirjaa sinut ulos. Huomaa, ettei tällä välltämättä ole haluttua\n" " vaikutusta, että botti lopettaa sinun tuntemisen, koska olet " "saattanut\n" " lisätä hostmaskin käyttäjälle. Tämä voi aihettaa sen, että botti\n" " tunnistaa sinut yhä.\n" " " #: plugin.py:600 msgid "" "If you remain recognized after giving this command, you're being recognized " "by hostmask, rather than by password. You must remove whatever hostmask is " "causing you to be recognized in order not to be recognized." msgstr "" "Jos pysyt tunnistettuna tämän komennon antamisen jälkeen, sinut tunnistetaan " "hostmaskin, eikä salasanan perusteella. Sinun täytyy poistaa mikä tahansa " "hostmaski, joka aiheuttaa sinun tunnistamisesi, tullaksesi " "tunnistamattomaksi." #: plugin.py:609 msgid "" "takes no arguments\n" "\n" " Returns the name of the user calling the command.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Palauttaa komennon antaneen käyttäjän tunnuksen.\n" " " #: plugin.py:617 #, fuzzy msgid "" "I don't recognize you. You can message me either of these two commands: " "\"user identify <username> <password>\" to log in or \"user register " "<username> <password>\" to register." msgstr "" "Et ole tunnistautunut. Voit lähettää minulle kumman tahansa näistä kahdesta " "komennoista: \"user identify <username> <password>\" kirjautuaksesi sisään " "tai rekisteröityäksesi \"user register <username> <password>\"." #: plugin.py:622 msgid "" "takes no arguments\n" "\n" " Returns some statistics on the user database.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Palauttaa joitakin tilastotietoja käyttäjä tietokannasta.\n" " " #: plugin.py:640 msgid "I have %s registered users with %s registered hostmasks; %n and %n." msgstr "" "Minulla on %s rekisteröityä käyttäjää %s rekisteröidyllä hostmaskilla; %n ja " "%n." #~ msgid "I don't recognize you." #~ msgstr "Minä en tunnista sinua." #~ msgid "" #~ "<name> <hostmask> [<password>]\n" #~ "\n" #~ " Removes the hostmask <hostmask> from the record of the user\n" #~ " specified by <name>. If the hostmask given is 'all' then " #~ "all\n" #~ " hostmasks will be removed. The <password> may only be " #~ "required if\n" #~ " the user is not recognized by their hostmask. This message " #~ "must be\n" #~ " sent to the bot privately (not on a channel) since it may " #~ "contain a\n" #~ " password.\n" #~ " " #~ msgstr "" #~ "<nimi> <hostmask> [<salasana>]\n" #~ "\n" #~ " Poistaa <hostmaskin> käyttäjältä, jonka määrittää\n" #~ " <nimi>. Jos annettu hostmask on 'all' (kaikki) niin, kaikki\n" #~ " hostmaskit poistetaan. <Salasana> on vaadittu\n" #~ " vain, jos käyttäjää ei tunnisteta hostmaskin perusteella. " #~ "Tämä viesti täytyy\n" #~ " lähettää botille yksisyisesti (ei kanavalla), koska se voi " #~ "sisältää\n" #~ " salasanan.\n" #~ " " ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/User/locales/fr.po������������������������������������������������������0000644�0001750�0001750�00000044360�13634634532�020316� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-01-22 07:53+CET\n" "PO-Revision-Date: 2014-07-05 00:10+0200\n" "Last-Translator: \n" "Language-Team: Limnoria <progval@gmail.com>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" "Language: fr\n" #: plugin.py:52 msgid "" "[--capability=<capability>] [<glob>]\n" "\n" " Returns the valid registered usernames matching <glob>. If <glob> " "is\n" " not given, returns all registered usernames.\n" " " msgstr "" "[--capacility=<capacité>] [<glob>]\n" "\n" "Retourne les utilisateurs enregistrés correspondant au <glob>. Si <glob> " "n'est pas donné, retourne tous les noms d'utilisateur." #: plugin.py:67 msgid "This is a private capability. Only admins can see who has it." msgstr "" "C'est une capacité privée. Seuls les admins peuvent voir qui la possède." #: plugin.py:92 msgid "There are no matching registered users." msgstr "Il n'y a pas d'utilisateur enregistré correspondant." #: plugin.py:94 msgid "There are no registered users." msgstr "Il n'y a pas d'utilisateur enregistré." #: plugin.py:100 msgid "" "<name> <password>\n" "\n" " Registers <name> with the given password <password> and the current\n" " hostmask of the person registering. You shouldn't register twice; " "if\n" " you're not recognized as a user but you've already registered, use " "the\n" " hostmask add command to add another hostmask to your already-" "registered\n" " user, or use the identify command to identify just for a session.\n" " This command (and all other commands that include a password) must " "be\n" " sent to the bot privately, not in a channel.\n" " " msgstr "" "<nom> <mot de passe>\n" "\n" "Enregistre <nom> avec le mot de passe donné, et le masque d'hôte actuel de " "la personne s'inscrivant. Vous ne devez pas vous enregistrer deux fois ; si " "vous n'êtes pas reconnu(e) comme un utilisateur(trice) alors que vous êtes " "déjà enregistré(e), utilisez la commande 'hostmask add' pour ajouter un " "masque d'hôte à votre compte, ou utilisez la commande 'identify' pour vous " "identifier, juste le temps d'une session. Cette commande (et toutes les " "autres commandes qui nécessitent un mot de passe) doivent être envoyées en " "privé, et non sur un canal." #: plugin.py:113 msgid "That name is already assigned to someone." msgstr "Ce nom est déjà utilisé par quelqu'un." #: plugin.py:118 msgid "username" msgstr "nom d'utilisateur" #: plugin.py:119 msgid "Hostmasks are not valid usernames." msgstr "Les masques d'hôte ne sont pas des noms d'utilisateur valides." #: plugin.py:126 msgid "Your hostmask is already registered to %s" msgstr "Votre masque d'hôte est déjà enregistré à %s" #: plugin.py:142 msgid "" "<name> [<password>]\n" "\n" " Unregisters <name> from the user database. If the user giving this\n" " command is an owner user, the password is not necessary.\n" " " msgstr "" "<nom> [<mot de passe>]\n" "\n" "Supprime <nom> de la base de données des utilisateurs. Si l'utilisateur " "appellant cette commande est le propriétaire, le mot de passe n'est pas " "requis." #: plugin.py:157 msgid "" "This command has been disabled. You'll have to ask the owner of this bot to " "unregister your user." msgstr "" "Cette commande a été désactivée. Vous devez contacter le propriétaire du bot " "pour vous désenregistrer." #: plugin.py:170 msgid "" "<name> <new name> [<password>]\n" "\n" " Changes your current user database name to the new name given.\n" " <password> is only necessary if the user isn't recognized by " "hostmask.\n" " This message must be sent to the bot privately (not on a channel) " "since\n" " it may contain a password.\n" " " msgstr "" "<nom> <nouveau nom> [<mot de passe>]\n" "\n" "Change votre nom actuel dans la base de données des utilisateurs. <mot de " "passe> n'est requis que si vous n'êtes pas actuellement reconnu(e) par " "masque d'hôte. Si vous avez le paramètre <mot de passe> le message doit être " "envoyé en privé (et non sur un canal)." #: plugin.py:179 msgid "%q is already registered." msgstr "%q est déjà enregistré." #: plugin.py:193 msgid "" "[<name>] <old password> <new password>\n" "\n" " Sets the new password for the user specified by <name> to <new\n" " password>. Obviously this message must be sent to the bot\n" " privately (not in a channel). If the requesting user is an " "owner\n" " user (and the user whose password is being changed isn't that " "same\n" " owner user), then <old password> needn't be correct.\n" " " msgstr "" "<nom> <ancien mot de passe> <nouveau mot de passe>\n" "\n" "Définit le nouveau mot de passe pour l'utilisateur spécifié par <name>. " "Évidemment, ce message doit être envoyé au bot en privé (= pas sur un " "canal). Si l'utilisateur est le propriétaire (et si l'utilisateur dont on " "change le mot de passe n'est pas le même propriétaire), l'<ancien mot de " "passe> ne requiert pas d'être correct." #: plugin.py:221 msgid "" "<password> [<True|False>]\n" "\n" " Sets the secure flag on the user of the person sending the " "message.\n" " Requires that the person's hostmask be in the list of hostmasks " "for\n" " that user in addition to the password being correct. When the\n" " secure flag is set, the user *must* identify before they " "can be\n" " recognized. If a specific True/False value is not given, it\n" " inverts the current value.\n" " " msgstr "" "<mot de passe> [<on|off>]\n" "\n" "Défini le flag 'secure' sur l'utilisateur envoyant le message. Requiert que " "la personne soit reconnue par masque d'hôte, en plus du fait que le mot de " "passe doit être correct. Lorsque le flag 'secure' est défini, l'utilisateur " "*doit* être identifié avant d'être reconnu. Si l'argument on/off n'est pas " "donné, l'état courant est inversé." #: plugin.py:236 msgid "Secure flag set to %s" msgstr "Flag secure déjà défini à %s" #: plugin.py:244 msgid "" "<hostmask|nick>\n" "\n" " Returns the username of the user specified by <hostmask> or <nick> " "if\n" " the user is registered.\n" " " msgstr "" "<masque d'hôte|nick>\n" "\n" "Retourne le nom d'utilisateur de la personne spécifiée par <masque d'hôte> " "ou par <nick>, si cette personne est enregistrée." #: plugin.py:253 msgid "I haven't seen %s." msgstr "Je n'ai pas vu %s." #: plugin.py:258 msgid "I don't know who that is." msgstr "Je ne sais pas qui c'est." #: plugin.py:264 msgid "" "[<nick>]\n" "\n" " Returns the hostmask of <nick>. If <nick> isn't given, return " "the\n" " hostmask of the person giving the command.\n" " " msgstr "" "[<nick>]\n" "\n" "Retourne le masque d'hôte de <nick>. Si <nick> n'est pas donné, retourne le " "masque d'hôte de la personne envoyant la commande." #: plugin.py:276 msgid "" "[<name>]\n" "\n" " Returns the hostmasks of the user specified by <name>; if " "<name>\n" " isn't specified, returns the hostmasks of the user calling the\n" " command.\n" " " msgstr "" "[<nom>]\n" "\n" "Retourne les masques d'hôte de l'utilisateur spécifié par <nom> ; si <nom> " "n'est pas spécifié, retourne le masque d'hôte de la personne appelant la " "commande." #: plugin.py:288 msgid "%s has no registered hostmasks." msgstr "%s n'a pas de masque d'hôte enregistré." #: plugin.py:295 msgid "You may only retrieve your own hostmasks." msgstr "Vous ne pouvez récupérer que vos propres masques d'hôte." #: plugin.py:311 msgid "" "[<name>] [<hostmask>] [<password>]\n" "\n" " Adds the hostmask <hostmask> to the user specified by <name>. " "The\n" " <password> may only be required if the user is not recognized " "by\n" " hostmask. <password> is also not required if an owner user is\n" " giving the command on behalf of some other user. If <hostmask> " "is\n" " not given, it defaults to your current hostmask. If <name> is " "not\n" " given, it defaults to your currently identified name. This " "message\n" " must be sent to the bot privately (not on a channel) since it " "may\n" " contain a password.\n" " " msgstr "" "[<nom>] [<masque d'hôte>] [<mot de passe>]\n" "\n" "Ajoute le <masque d'hôte> à l'utilisateur <nom>. Le <mot de passe> n'est " "requis que si l'utilisateur n'est pas reconnu par masque d'hôte. <mot de " "passe> peut également être omis si l'expéditeur est le propriétaire, et " "envoie la commande au nom de quelqu'un d'autre. Si le <masque d'hôte> n'est " "pas donné, ce sera le masque d'hôte de l'expéditeur. Si <nom> n'est pas " "donné, il s'agit par défaut de votre nom d'utilisateur. Ce message doit être " "envoyé au bot en privé (pas sur un canal) si il contient un mot de passe." #: plugin.py:325 msgid "hostmask" msgstr "masque d'hôte" #: plugin.py:326 msgid "" "Make sure your hostmask includes a nick, then an exclamation point (!), then " "a user, then an at symbol (@), then a host. Feel free to use wildcards (* " "and ?, which work just like they do on the command line) in any of these " "parts." msgstr "" "Assurez-vous que votre masque d'hôte inclue un nick, un point d'exclamation, " "une ident, un arobase, puis un hôte. Sentez-vous libre d'utiliser des jokers " "(* et ?, qui fonctionnent comme dans la ligne de commande), dans n'importe " "lequel de ces termes." #: plugin.py:336 plugin.py:359 msgid "That hostmask is already registered." msgstr "Ce masque d'hôte est déjà enregistré." #: plugin.py:367 msgid "" "[<name>] [<hostmask>] [<password>]\n" "\n" " Removes the hostmask <hostmask> from the record of the user\n" " specified by <name>. If the hostmask given is 'all' then all\n" " hostmasks will be removed. The <password> may only be required " "if\n" " the user is not recognized by their hostmask. This message must " "be\n" " sent to the bot privately (not on a channel) since it may " "contain a\n" " password. If <hostmask> is\n" " not given, it defaults to your current hostmask. If <name> is " "not\n" " given, it defaults to your currently identified name. This " "message\n" " must be sent to the bot privately (not on a channel) since it " "may\n" " contain a password.\n" "\n" " " msgstr "" "[<nom>] [<masque d'hôte>] [<mot de passe>]\n" "\n" "Supprime le <masque d'hôte> de l'utilisateur <nom>. Si le masque d’hôte " "donné est « all », alors tous les masques d’hôte seront supprimés. Le <mot " "de passe> n'est requis que si l'utilisateur n'est pas reconnu par masque " "d'hôte. <mot de passe> peut également être omis si l'expéditeur est le " "propriétaire, et envoie la commande au nom de quelqu'un d'autre. Si le " "<masque d'hôte> n'est pas donné, ce sera le masque d'hôte de l'expéditeur. " "Si <nom> n'est pas donné, il s'agit par défaut de votre nom d'utilisateur. " "Ce message doit être envoyé au bot en privé (pas sur un canal) si il " "contient un mot de passe." #: plugin.py:393 msgid "All hostmasks removed." msgstr "Tous les masques d'hôte ont été supprimés." #: plugin.py:397 msgid "There was no such hostmask." msgstr "Il n'y avait pas ce masque d'hôte." #: plugin.py:414 msgid "GPG features are not enabled." msgstr "Les fonctionnalités liées à GPG ne sont pas activées." #: plugin.py:423 msgid "" "<key id> <key server>\n" "\n" " Add a GPG key to your account." msgstr "" "<id de clé> <serveur de clé>\n" "\n" "Ajoute une clé GPG à votre compte." #: plugin.py:427 msgid "This key is already associated with your account." msgstr "Cette clé est déjà associée à votre compte." #: plugin.py:431 msgid "%n imported, %i unchanged, %i not imported." msgstr "%n importée(s), %i inchangée(s), %i non importée(s)" #: plugin.py:432 msgid "key" msgstr "clé" #: plugin.py:443 msgid "You must give a valid key id" msgstr "Vous devez donner un identifiant de clé valide" #: plugin.py:445 msgid "You must give a valid key server" msgstr "Vous devez donner un serveur de clé valide" #: plugin.py:449 msgid "" "<fingerprint>\n" "\n" " Remove a GPG key from your account." msgstr "" "<empreinte>\n" "\n" "Supprime une clé GPG de votre compte." #: plugin.py:462 msgid "GPG key not associated with your account." msgstr "La clé GPG n’est pas associée à votre compte." #: plugin.py:467 msgid "" "takes no arguments\n" "\n" " Send you a token that you'll have to sign with your key." msgstr "" "ne prend pas d’argument\n" "\n" "Vous donne un jeton que vous devez signer avec votre clé." #: plugin.py:474 msgid "" "Your token is: %s. Please sign it with your GPG key, paste it somewhere, and " "call the 'auth' command with the URL to the (raw) file containing the " "signature." msgstr "" "Votre jeton est : %s. Veuillez le signer avec votre clé GPG, le paster " "quelque part, et appeler la commande « auth » avec l’URL du fichier (brut) " "contenant la signature." #: plugin.py:488 msgid "" "<url>\n" "\n" " Check the GPG signature at the <url> and authenticates you if\n" " the key used is associated to a user." msgstr "" "<url>\n" "\n" "Vérifie que la signature GPG à l’<url> et vous authentifie si la clé est " "associée à un utilisateur." #: plugin.py:495 msgid "Signature or token not found." msgstr "Signature ou jeton non trouvé." #: plugin.py:499 msgid "Unknown token. It may have expired before you submit it." msgstr "" "Jeton inconnu. Il se peut qu’il ait expiré avant que vous ne l’ayez soumis." #: plugin.py:502 msgid "Your hostname/nick changed in the process. Authentication aborted." msgstr "" "Votre nom d’hôte/nick a changé durant le processus. Authentification annulée." #: plugin.py:513 msgid "You are now authenticated as %s." msgstr "Vous êtes maintenant identifié(e) en tant que %s." #: plugin.py:516 msgid "Unknown GPG key." msgstr "Clé GPG inconnue." #: plugin.py:518 msgid "" "Signature could not be verified. Make sure this is a valid GPG signature and " "the URL is valid." msgstr "" "La signature n’a pas pu être vérifiée. Assurez vous que c’est une signature " "GPG valide et que l’URL est valide." #: plugin.py:524 msgid "" "[<name>]\n" "\n" " Returns the capabilities of the user specified by <name>; if <name>\n" " isn't specified, returns the capabilities of the user calling the\n" " command.\n" " " msgstr "" "[<nom>]\n" "\n" "Retourne la liste des capacités de l'utilisateur spécifié par <nom> ; si " "<nom> n'est pas spécifié, retourne les capacités de l'utilisateur appelant " "la commande." #: plugin.py:544 msgid "" "<name> <password>\n" "\n" " Identifies the user as <name>. This command (and all other\n" " commands that include a password) must be sent to the bot " "privately,\n" " not in a channel.\n" " " msgstr "" "<nom> <mot de passe>\n" "\n" "Identifie l'utilisateur en tant que <nom>. Cette commande (et toutes cellent " "qui requierent un mot de passe) doivent être envoyées en privé au bot, et " "non sur un canal." #: plugin.py:556 msgid "" "Your secure flag is true and your hostmask doesn't match any of your known " "hostmasks." msgstr "" "Votre flag secure est True, et votre masque d'hôte ne correspond à aucune de " "vos masques d'hôte connus." #: plugin.py:566 msgid "" "takes no arguments\n" "\n" " Un-identifies you. Note that this may not result in the desired\n" " effect of causing the bot not to recognize you anymore, since you " "may\n" " have added hostmasks to your user that can cause the bot to continue " "to\n" " recognize you.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Vous dé-indentifie. Notez que cela ne marche pas forcément, par exemple si " "le bot vous reconnait grâce au masque d'hôte ; il continuera alors à vous " "reconnaître." #: plugin.py:575 msgid "" "If you remain recognized after giving this command, you're being recognized " "by hostmask, rather than by password. You must remove whatever hostmask is " "causing you to be recognized in order not to be recognized." msgstr "" "Si vous êtes encore reconnu(e) après avoir envoyé cette commande, c'est que " "vous êtes reconnu(e) par masque d'hôte et non par mot de passe. Vous devez " "supprimer tout masque d'hôte susceptible de vous reconnaitre, dans le but de " "ne pas être reconnu(e)." #: plugin.py:584 msgid "" "takes no arguments\n" "\n" " Returns the name of the user calling the command.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne le nom de l'utilisateur appellant la commande." #: plugin.py:592 msgid "I don't recognize you." msgstr "Je ne vous reconnais pas." #: plugin.py:597 msgid "" "takes no arguments\n" "\n" " Returns some statistics on the user database.\n" " " msgstr "" "ne prend pas d'argument\n" "\n" "Retourne des statistiques sur la base de données." #: plugin.py:615 msgid "I have %s registered users with %s registered hostmasks; %n and %n." msgstr "" "J'ai %s utilisateurs enregistrés, avec %s masques d'hôte enregistrés ; %n et " "%n." #~ msgid "" #~ "<name> <hostmask> [<password>]\n" #~ "\n" #~ " Removes the hostmask <hostmask> from the record of the user\n" #~ " specified by <name>. If the hostmask given is 'all' then " #~ "all\n" #~ " hostmasks will be removed. The <password> may only be " #~ "required if\n" #~ " the user is not recognized by their hostmask. This message " #~ "must be\n" #~ " sent to the bot privately (not on a channel) since it may " #~ "contain a\n" #~ " password.\n" #~ " " #~ msgstr "" #~ "<nom> <masque d'hôte> [<mot de passe>]\n" #~ "\n" #~ "Retire le <masque d'hôte> de la liste de ceux de l'utilisateur spécifié " #~ "par <nom>. Si le masque d'hôte est 'all', alors tous les masques d'hôtes " #~ "seront supprimés. Le <mot de passe> n'est requis que si vous n'êtes pas " #~ "reconnu(e) par masque d'hôte. Ce message doit être envoyé en privé (=pas " #~ "sur un canal) car il peut contenir un mot de passe." ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/User/locales/hu.po������������������������������������������������������0000644�0001750�0001750�00000022124�13634634532�020315� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Limnoria User plugin # Copyright (C) 2011 Limnoria # nyuszika7h <litemininyuszika@gmail.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: Limnoria User\n" "POT-Creation-Date: 2011-12-23 13:11+CET\n" "PO-Revision-Date: 2014-07-05 00:10+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: \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" #: plugin.py:49 msgid "" "[--capability=<capability>] [<glob>]\n" "\n" " Returns the valid registered usernames matching <glob>. If <glob> is\n" " not given, returns all registered usernames.\n" " " msgstr "" #: plugin.py:80 msgid "There are no matching registered users." msgstr "Nincsenek egyező regisztrált felhasználók." #: plugin.py:82 msgid "There are no registered users." msgstr "Nincsenek regisztrált felhasználók." #: plugin.py:88 msgid "" "<name> <password>\n" "\n" " Registers <name> with the given password <password> and the current\n" " hostmask of the person registering. You shouldn't register twice; if\n" " you're not recognized as a user but you've already registered, use the\n" " hostmask add command to add another hostmask to your already-registered\n" " user, or use the identify command to identify just for a session.\n" " This command (and all other commands that include a password) must be\n" " sent to the bot privately, not in a channel.\n" " " msgstr "" #: plugin.py:101 msgid "That name is already assigned to someone." msgstr "Az a név már hozzá van rendelve valakihez." #: plugin.py:106 msgid "username" msgstr "felhasználónév" #: plugin.py:107 msgid "Hostmasks are not valid usernames." msgstr "A hosztmaszkok nem érvényes felhasználónevek." #: plugin.py:114 msgid "Your hostmask is already registered to %s" msgstr "A hosztmaszkod már regisztrálva van %s-hoz." #: plugin.py:130 msgid "" "<name> [<password>]\n" "\n" " Unregisters <name> from the user database. If the user giving this\n" " command is an owner user, the password is not necessary.\n" " " msgstr "" #: plugin.py:145 msgid "This command has been disabled. You'll have to ask the owner of this bot to unregister your user." msgstr "Ez a parancs le lett tiltva. Meg kell kérned a bot tulajdonosát, hogy törölje a regisztrációdat." #: plugin.py:158 msgid "" "<name> <new name> [<password>]\n" "\n" " Changes your current user database name to the new name given.\n" " <password> is only necessary if the user isn't recognized by hostmask.\n" " This message must be sent to the bot privately (not on a channel) since\n" " it may contain a password.\n" " " msgstr "" #: plugin.py:167 msgid "%q is already registered." msgstr "%q már regisztrálva van." #: plugin.py:181 msgid "" "[<name>] <old password> <new password>\n" "\n" " Sets the new password for the user specified by <name> to <new\n" " password>. Obviously this message must be sent to the bot\n" " privately (not in a channel). If the requesting user is an owner\n" " user (and the user whose password is being changed isn't that same\n" " owner user), then <old password> needn't be correct.\n" " " msgstr "" #: plugin.py:209 msgid "" "<password> [<True|False>]\n" "\n" " Sets the secure flag on the user of the person sending the message.\n" " Requires that the person's hostmask be in the list of hostmasks for\n" " that user in addition to the password being correct. When the\n" " secure flag is set, the user *must* identify before they can be\n" " recognized. If a specific True/False value is not given, it\n" " inverts the current value.\n" " " msgstr "" #: plugin.py:224 msgid "Secure flag set to %s" msgstr "A secure jelző be lett állítva %s-ra" #: plugin.py:232 msgid "" "<hostmask|nick>\n" "\n" " Returns the username of the user specified by <hostmask> or <nick> if\n" " the user is registered.\n" " " msgstr "" #: plugin.py:241 msgid "I haven't seen %s." msgstr "Nem láttam %s-t." #: plugin.py:246 msgid "I don't know who that is." msgstr "Nem tudom, ki az." #: plugin.py:252 msgid "" "[<nick>]\n" "\n" " Returns the hostmask of <nick>. If <nick> isn't given, return the\n" " hostmask of the person giving the command.\n" " " msgstr "" #: plugin.py:264 msgid "" "[<name>]\n" "\n" " Returns the hostmasks of the user specified by <name>; if <name>\n" " isn't specified, returns the hostmasks of the user calling the\n" " command.\n" " " msgstr "" #: plugin.py:276 msgid "%s has no registered hostmasks." msgstr "%s-nak nincsenek regisztrált hosztmaszkjai." #: plugin.py:283 msgid "You may only retrieve your own hostmasks." msgstr "Csak a saját hosztmaszkjaidat szerezheted meg." #: plugin.py:299 msgid "" "[<name>] [<hostmask>] [<password>]\n" "\n" " Adds the hostmask <hostmask> to the user specified by <name>. The\n" " <password> may only be required if the user is not recognized by\n" " hostmask. <password> is also not required if an owner user is\n" " giving the command on behalf of some other user. If <hostmask> is\n" " not given, it defaults to your current hostmask. If <name> is not\n" " given, it defaults to your currently identified name. This message\n" " must be sent to the bot privately (not on a channel) since it may\n" " contain a password.\n" " " msgstr "" #: plugin.py:313 msgid "hostmask" msgstr "hosztmaszk" #: plugin.py:314 msgid "Make sure your hostmask includes a nick, then an exclamation point (!), then a user, then an at symbol (@), then a host. Feel free to use wildcards (* and ?, which work just like they do on the command line) in any of these parts." msgstr "Győződj meg róla, hogy a hosztmaszkod tartalmaz egy nevet, aztán egy felkiáltójelet (!), aztán egy felhasználót, aztán egy kukac jelet (@), aztán egy hosztot. Nyugodtan használj helyettesítő karaktereket (* és ?, amelyek úgy működnek, mint a parancssorban) ezeknek a részeknek bármelyikében." #: plugin.py:324 #: plugin.py:347 msgid "That hostmask is already registered." msgstr "Ez a hosztmaszk már regisztrálva van." #: plugin.py:355 msgid "" "<name> <hostmask> [<password>]\n" "\n" " Removes the hostmask <hostmask> from the record of the user\n" " specified by <name>. If the hostmask given is 'all' then all\n" " hostmasks will be removed. The <password> may only be required if\n" " the user is not recognized by their hostmask. This message must be\n" " sent to the bot privately (not on a channel) since it may contain a\n" " password.\n" " " msgstr "" #: plugin.py:374 msgid "All hostmasks removed." msgstr "Minden hosztmaszk eltávolítva." #: plugin.py:378 msgid "There was no such hostmask." msgstr "Nem volt ilyen hosztmaszk." #: plugin.py:387 msgid "" "[<name>]\n" "\n" " Returns the capabilities of the user specified by <name>; if <name>\n" " isn't specified, returns the capabilities of the user calling the\n" " command.\n" " " msgstr "" #: plugin.py:407 msgid "" "<name> <password>\n" "\n" " Identifies the user as <name>. This command (and all other\n" " commands that include a password) must be sent to the bot privately,\n" " not in a channel.\n" " " msgstr "" #: plugin.py:419 msgid "Your secure flag is true and your hostmask doesn't match any of your known hostmasks." msgstr "A secure jelződ igaz és a hosztmaszkod nem egyezik egy ismert hosztmaszkoddal sem." #: plugin.py:429 msgid "" "takes no arguments\n" "\n" " Un-identifies you. Note that this may not result in the desired\n" " effect of causing the bot not to recognize you anymore, since you may\n" " have added hostmasks to your user that can cause the bot to continue to\n" " recognize you.\n" " " msgstr "" #: plugin.py:438 msgid "If you remain recognized after giving this command, you're being recognized by hostmask, rather than by password. You must remove whatever hostmask is causing you to be recognized in order not to be recognized." msgstr "Ha felismerve maradsz e parancs kiadása után, hosztmaszk alapján vagy felismerve, jelszó helyett. El kell távolítanod akármilyen hosztmaszkot, amely a fekusmerésedet okozza, hogy ne legyél felismerve." #: plugin.py:447 msgid "" "takes no arguments\n" "\n" " Returns the name of the user calling the command.\n" " " msgstr "" #: plugin.py:455 msgid "I don't recognize you." msgstr "Nem ismerlek fel." #: plugin.py:460 msgid "" "takes no arguments\n" "\n" " Returns some statistics on the user database.\n" " " msgstr "" #: plugin.py:478 msgid "I have %s registered users with %s registered hostmasks; %n and %n." msgstr "%s regisztrált felhasználóm van %s regisztrált hosztmaszkkal; %n és %n." ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/User/locales/it.po������������������������������������������������������0000644�0001750�0001750�00000033100�13634634532�020311� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2014-07-05 00:10+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugin.py:49 #, docstring msgid "" "[--capability=<capability>] [<glob>]\n" "\n" " Returns the valid registered usernames matching <glob>. If <glob> is\n" " not given, returns all registered usernames.\n" " " msgstr "" "[--capability=<capacità>] [<glob>]\n" "\n" " Restituisce i nomi utente registrati che corrispondono a <glob pattern>; se\n" " quest'ultimo non è specificato, riporta i nomi di tutti gli utenti registrati.\n" " " #: plugin.py:80 msgid "There are no matching registered users." msgstr "Non ci sono utenti registrati corrispondenti." #: plugin.py:82 msgid "There are no registered users." msgstr "Non ci sono utenti registrati." #: plugin.py:88 #, docstring msgid "" "<name> <password>\n" "\n" " Registers <name> with the given password <password> and the current\n" " hostmask of the person registering. You shouldn't register twice; if\n" " you're not recognized as a user but you've already registered, use the\n" " hostmask add command to add another hostmask to your already-registered\n" " user, or use the identify command to identify just for a session.\n" " This command (and all other commands that include a password) must be\n" " sent to the bot privately, not in a channel.\n" " " msgstr "" "<nome> <password>\n" "\n" " Registra <nome> con la data <password> e l'hostmask attuale dell'utente.\n" " Non bisogna registrarsi due volte; se non si è riconosciuti come utenti ma\n" " ci si è gia registrati, utilizzare il comando \"hostmask add\" per aggiungere\n" " un'altra hostmask per lo stesso utente oppure utilizzare il comando \"identify\"\n" " per identificarsi solo per la sessione in corso. Questo comando (e tutti quelli\n" " che includono una password) devono essere inviati al bot privatamente, mai in canale.\n" " " #: plugin.py:101 msgid "That name is already assigned to someone." msgstr "Questo nome è già assegnato a qualcuno." #: plugin.py:106 msgid "username" msgstr "nome utente" #: plugin.py:107 msgid "Hostmasks are not valid usernames." msgstr "Le hostmask non sono nomi utente validi." #: plugin.py:114 msgid "Your hostmask is already registered to %s" msgstr "La tua hostmask è già registrata a %s" #: plugin.py:130 #, docstring msgid "" "<name> [<password>]\n" "\n" " Unregisters <name> from the user database. If the user giving this\n" " command is an owner user, the password is not necessary.\n" " " msgstr "" "<nome> [<password>]\n" "\n" " Elimina <nome> dal database degli utenti. Se l'utente che usa questo\n" " comando è un owner (proprietario), la password non è necessaria.\n" " " #: plugin.py:145 msgid "This command has been disabled. You'll have to ask the owner of this bot to unregister your user." msgstr "Questo comando è stato disabilitato. Contatta l'owner del bot per de-registrarti." #: plugin.py:158 #, docstring msgid "" "<name> <new name> [<password>]\n" "\n" " Changes your current user database name to the new name given.\n" " <password> is only necessary if the user isn't recognized by hostmask.\n" " This message must be sent to the bot privately (not on a channel) since\n" " it may contain a password.\n" " " msgstr "" "<nome> <nuovo nome> [<password>]\n" "\n" " Cambia l'attuale nome nel database degli utenti con <nuovo nome>.\n" " <password> è necessaria solo se l'utente non è riconosciuto tramite\n" " l'hostmask. Questo messaggio va inviato al bot privatamente (non in\n" " canale) in quanto può contenere una password.\n" " " #: plugin.py:167 msgid "%q is already registered." msgstr "%q è già registrato." #: plugin.py:181 #, docstring msgid "" "[<name>] <old password> <new password>\n" "\n" " Sets the new password for the user specified by <name> to <new\n" " password>. Obviously this message must be sent to the bot\n" " privately (not in a channel). If the requesting user is an owner\n" " user (and the user whose password is being changed isn't that same\n" " owner user), then <old password> needn't be correct.\n" " " msgstr "" "[<nome>] <vecchia password> <nuova password>\n" "\n" " Imposta una nuova password per l'utente specificato da <nome>. Il\n" " messaggio va ovviamente inviato privatamente (non in canale). Se\n" " l'utente è l'owner (e l'utente di cui si cambia la password non è lo\n" " stesso proprietario), <vecchia password> non necessita di essere corretta.\n" " " #: plugin.py:209 #, docstring msgid "" "<password> [<True|False>]\n" "\n" " Sets the secure flag on the user of the person sending the message.\n" " Requires that the person's hostmask be in the list of hostmasks for\n" " that user in addition to the password being correct. When the\n" " secure flag is set, the user *must* identify before they can be\n" " recognized. If a specific True/False value is not given, it\n" " inverts the current value.\n" " " msgstr "" "<password> [<True|False>]\n" "\n" " Imposta il flag \"secure\" per l'utente che invia il messaggio.\n" " Richiede che la sua hostmask sia presente nell'elenco di quelle\n" " per quell'utente e di una password corretta. Quando questo flag è\n" " impostato, l'utente *deve* identificarsi prima di essere riconosciuto.\n" " Se l'argomento True/False non è specificato, il valore corrente viene invertito.\n" " " #: plugin.py:224 msgid "Secure flag set to %s" msgstr "Flag secure impostato a %s" #: plugin.py:232 #, docstring msgid "" "<hostmask|nick>\n" "\n" " Returns the username of the user specified by <hostmask> or <nick> if\n" " the user is registered.\n" " " msgstr "" "<hostmask|nick>\n" "\n" " Se l'utente è registrato, restituisce il nome utente di quello specificato da <hostmask> o <nick>.\n" " " #: plugin.py:241 msgid "I haven't seen %s." msgstr "Non ho mai visto %s." #: plugin.py:246 msgid "I don't know who that is." msgstr "Non so chi sia." #: plugin.py:252 #, docstring msgid "" "[<nick>]\n" "\n" " Returns the hostmask of <nick>. If <nick> isn't given, return the\n" " hostmask of the person giving the command.\n" " " msgstr "" "[<nick>]\n" "\n" " Restituisce l'hostmask di <nick>. Se <nick> non è specificato,\n" " riporta l'hostmask di chi ha dato il comando.\n" " " #: plugin.py:264 #, docstring msgid "" "[<name>]\n" "\n" " Returns the hostmasks of the user specified by <name>; if <name>\n" " isn't specified, returns the hostmasks of the user calling the\n" " command.\n" " " msgstr "" "[<nome>]\n" "\n" " Restituisce l'hostmask dell'utente specificato da <nome>; se <nome>\n" " non è definito, riporta l'hostmask di chi ha dato il comando.\n" " " #: plugin.py:276 msgid "%s has no registered hostmasks." msgstr "%s non ha hostmask registrate." #: plugin.py:283 msgid "You may only retrieve your own hostmasks." msgstr "Puoi recuperare solo le tue hostmask." #: plugin.py:299 #, docstring msgid "" "[<name>] [<hostmask>] [<password>]\n" "\n" " Adds the hostmask <hostmask> to the user specified by <name>. The\n" " <password> may only be required if the user is not recognized by\n" " hostmask. <password> is also not required if an owner user is\n" " giving the command on behalf of some other user. If <hostmask> is\n" " not given, it defaults to your current hostmask. If <name> is not\n" " given, it defaults to your currently identified name. This message\n" " must be sent to the bot privately (not on a channel) since it may\n" " contain a password.\n" " " msgstr "" "[<nome>] [<hostmask>] [<password>]\n" "\n" " Aggiunge <hostmask> all'utente specificato da <nome>. <password>\n" " è richiesta solo se l'utente non viene riconosciuto tramite l'hostmask\n" " e può essere omessa se un owner sta dando il comando a nome di qualcun\n" " altro. Se <hostmask> non è fornita utilizza quella attualmente in uso.\n" " Se <nome> non è specificato utilizza quello attualmente identificato.\n" " Questo messaggio va inviato al bot privatamente (non in canale) in\n" " quanto può contenere una password.\n" " " #: plugin.py:313 msgid "hostmask" msgstr "hostmask" #: plugin.py:314 msgid "Make sure your hostmask includes a nick, then an exclamation point (!), then a user, then an at symbol (@), then a host. Feel free to use wildcards (* and ?, which work just like they do on the command line) in any of these parts." msgstr "Assicurati che la tua hostmask includa un nick, un punto esclamativo, un utente, un at (@) e un host. Puoi usare wildcard (* e ?, che funzionano come da riga di comando) in qualsiasi punto." #: plugin.py:324 plugin.py:347 msgid "That hostmask is already registered." msgstr "Questa hostmask è già registrata." #: plugin.py:355 #, docstring msgid "" "<name> <hostmask> [<password>]\n" "\n" " Removes the hostmask <hostmask> from the record of the user\n" " specified by <name>. If the hostmask given is 'all' then all\n" " hostmasks will be removed. The <password> may only be required if\n" " the user is not recognized by their hostmask. This message must be\n" " sent to the bot privately (not on a channel) since it may contain a\n" " password.\n" " " msgstr "" "<nome> <hostmask> [<password>]\n" "\n" " Rimuove <hostmask> dalla lista dell'utente specificato da <nome>. Se\n" " l'hostmask fornita è \"all\" verranno rimosse tutte. <password> è richiesta\n" " solo se l'utente non viene riconosciuto tramite l'hostmask. Questo messaggio\n" " va inviato al bot privatamente (non in canale) in quanto può contenere una password.\n" " " #: plugin.py:374 msgid "All hostmasks removed." msgstr "Tutte le hostmask sono state rimosse." #: plugin.py:378 msgid "There was no such hostmask." msgstr "Non c'è nessuna hostmask." #: plugin.py:387 #, docstring msgid "" "[<name>]\n" "\n" " Returns the capabilities of the user specified by <name>; if <name>\n" " isn't specified, returns the capabilities of the user calling the\n" " command.\n" " " msgstr "" "[<nome>]\n" "\n" " Restituisce le capacità dell'utente specificato da <nome>; se <nome>\n" " non è fornito riporta le capacità dell'utente che ha usato il comando.\n" " " #: plugin.py:407 #, docstring msgid "" "<name> <password>\n" "\n" " Identifies the user as <name>. This command (and all other\n" " commands that include a password) must be sent to the bot privately,\n" " not in a channel.\n" " " msgstr "" "<nome> <password>\n" "\n" " Identifica l'utente come <nome>. Questo comando (e tutti quelli che\n" " includono una password) devono essere inviati al bot privatamente, mai in canale.\n" " " #: plugin.py:419 msgid "Your secure flag is true and your hostmask doesn't match any of your known hostmasks." msgstr "Il tuo flag secure è impostato a True e la tua hostmask non corrisponde a nessuna di quelle conosciute." #: plugin.py:429 #, docstring msgid "" "takes no arguments\n" "\n" " Un-identifies you. Note that this may not result in the desired\n" " effect of causing the bot not to recognize you anymore, since you may\n" " have added hostmasks to your user that can cause the bot to continue to\n" " recognize you.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Ti de-identifica. Nota che questo potrebbe non sortire l'effetto desiderato,\n" " il bot potrebbe continuare a riconoscerti in quanto sono state eventualmente\n" " aggiunta altre hostmask per il tuo utente.\n" " " #: plugin.py:438 msgid "If you remain recognized after giving this command, you're being recognized by hostmask, rather than by password. You must remove whatever hostmask is causing you to be recognized in order not to be recognized." msgstr "Se dopo aver utilizzato questo comando si è ancora riconosciuti, ciò avviene tramite l'hostmask piuttosto che la password. È necessario rimuovere qualsiasi hostmask che causa il riconoscimento." #: plugin.py:447 #, docstring msgid "" "takes no arguments\n" "\n" " Returns the name of the user calling the command.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Restituisce il nome dell'utente che usa questo comando.\n" " " #: plugin.py:455 msgid "I don't recognize you." msgstr "Non ti riconosco." #: plugin.py:460 #, docstring msgid "" "takes no arguments\n" "\n" " Returns some statistics on the user database.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Riporta alcune statistiche a proposito del database degli utenti.\n" " " #: plugin.py:478 msgid "I have %s registered users with %s registered hostmasks; %n and %n." msgstr "Ho %s utenti registrati con %s hostmask registrate; %n e %n." ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/User/plugin.py����������������������������������������������������������0000644�0001750�0001750�00000053624�13634634532�017600� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import re import sys import fnmatch import supybot.conf as conf import supybot.gpg as gpg import supybot.utils as utils import supybot.ircdb as ircdb from supybot.commands import * import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('User') class User(callbacks.Plugin): """Provides commands for dealing with users, such as registration and authentication to the bot. This is a core Supybot plugin that should not be removed!""" def _checkNotChannel(self, irc, msg, password=' '): if password and msg.channel: raise callbacks.Error(conf.supybot.replies.requiresPrivacy()) @internationalizeDocstring def list(self, irc, msg, args, optlist, glob): """[--capability=<capability>] [<glob>] Returns the valid registered usernames matching <glob>. If <glob> is not given, returns all registered usernames. """ predicates = [] for (option, arg) in optlist: if option == 'capability': if arg in conf.supybot.capabilities.private(): try: u = ircdb.users.getUser(msg.prefix) if not u._checkCapability('admin'): raise KeyError except KeyError: # Note that it may be raised by checkCapability too. irc.error(_('This is a private capability. Only admins ' 'can see who has it.'), Raise=True) def p(u, cap=arg): try: return u._checkCapability(cap) except KeyError: return False predicates.append(p) if glob: r = re.compile(fnmatch.translate(glob), re.I) def p(u): return r.match(u.name) is not None predicates.append(p) users = [] for u in ircdb.users.values(): for predicate in predicates: if not predicate(u): break else: users.append(u.name) if users: utils.sortBy(str.lower, users) private = self.registryValue("listInPrivate", msg.channel, irc.network) irc.reply(format('%L', users), private=private) else: if predicates: irc.reply(_('There are no matching registered users.')) else: irc.reply(_('There are no registered users.')) list = wrap(list, [getopts({'capability':'capability'}), additional('glob')]) @internationalizeDocstring def register(self, irc, msg, args, name, password): """<name> <password> Registers <name> with the given password <password> and the current hostmask of the person registering. You shouldn't register twice; if you're not recognized as a user but you've already registered, use the hostmask add command to add another hostmask to your already-registered user, or use the identify command to identify just for a session. This command (and all other commands that include a password) must be sent to the bot privately, not in a channel. """ addHostmask = True try: ircdb.users.getUserId(name) irc.error(_('That name is already assigned to someone.'), Raise=True) except KeyError: pass if ircutils.isUserHostmask(name): irc.errorInvalid(_('username'), name, _('Hostmasks are not valid usernames.'), Raise=True) try: u = ircdb.users.getUser(msg.prefix) if u._checkCapability('owner'): addHostmask = False else: irc.error(_('Your hostmask is already registered to %s') % u.name) return except KeyError: pass user = ircdb.users.newUser() user.name = name user.setPassword(password) if addHostmask: user.addHostmask(msg.prefix) ircdb.users.setUser(user) irc.replySuccess() register = wrap(register, ['private', 'something', 'something']) @internationalizeDocstring def unregister(self, irc, msg, args, user, password): """<name> [<password>] Unregisters <name> from the user database. If the user giving this command is an owner user, the password is not necessary. """ try: caller = ircdb.users.getUser(msg.prefix) isOwner = caller._checkCapability('owner') except KeyError: caller = None isOwner = False if not conf.supybot.databases.users.allowUnregistration(): if not caller or not isOwner: self.log.warning('%s tried to unregister user %s.', msg.prefix, user.name) irc.error(_('This command has been disabled. You\'ll have to ' 'ask the owner of this bot to unregister your ' 'user.'), Raise=True) if isOwner or user.checkPassword(password): ircdb.users.delUser(user.id) irc.replySuccess() else: irc.error(conf.supybot.replies.incorrectAuthentication()) unregister = wrap(unregister, ['private', 'otherUser', additional('anything')]) @internationalizeDocstring def changename(self, irc, msg, args, user, newname, password): """<name> <new name> [<password>] Changes your current user database name to the new name given. <password> is only necessary if the user isn't recognized by hostmask. This message must be sent to the bot privately (not on a channel) since it may contain a password. """ try: id = ircdb.users.getUserId(newname) irc.error(format(_('%q is already registered.'), newname)) return except KeyError: pass if user.checkHostmask(msg.prefix) or user.checkPassword(password): user.name = newname ircdb.users.setUser(user) irc.replySuccess() changename = wrap(changename, ['private', 'otherUser', 'something', additional('something', '')]) class set(callbacks.Commands): @internationalizeDocstring def password(self, irc, msg, args, user, password, newpassword): """[<name>] <old password> <new password> Sets the new password for the user specified by <name> to <new password>. Obviously this message must be sent to the bot privately (not in a channel). If the requesting user is an owner user, then <old password> needn't be correct. """ try: u = ircdb.users.getUser(msg.prefix) except KeyError: u = None if user is None: if u is None: irc.errorNotRegistered(Raise=True) user = u if user.checkPassword(password) or \ (u and u._checkCapability('owner')): user.setPassword(newpassword) ircdb.users.setUser(user) irc.replySuccess() else: irc.error(conf.supybot.replies.incorrectAuthentication()) password = wrap(password, ['private', optional('otherUser'), 'something', 'something']) @internationalizeDocstring def secure(self, irc, msg, args, user, password, value): """<password> [<True|False>] Sets the secure flag on the user of the person sending the message. Requires that the person's hostmask be in the list of hostmasks for that user in addition to the password being correct. When the secure flag is set, the user *must* identify before they can be recognized. If a specific True/False value is not given, it inverts the current value. """ if value is None: value = not user.secure if user.checkPassword(password) and \ user.checkHostmask(msg.prefix, useAuth=False): user.secure = value ircdb.users.setUser(user) irc.reply(_('Secure flag set to %s') % value) else: irc.error(conf.supybot.replies.incorrectAuthentication()) secure = wrap(secure, ['private', 'user', 'something', additional('boolean')]) @internationalizeDocstring def username(self, irc, msg, args, hostmask): """<hostmask|nick> Returns the username of the user specified by <hostmask> or <nick> if the user is registered. """ if ircutils.isNick(hostmask): try: hostmask = irc.state.nickToHostmask(hostmask) except KeyError: irc.error(_('I haven\'t seen %s.') % hostmask, Raise=True) try: user = ircdb.users.getUser(hostmask) irc.reply(user.name) except KeyError: irc.error(_('I don\'t know who that is.')) username = wrap(username, [first('nick', 'hostmask')]) class hostmask(callbacks.Commands): @internationalizeDocstring def hostmask(self, irc, msg, args, nick): """[<nick>] Returns the hostmask of <nick>. If <nick> isn't given, return the hostmask of the person giving the command. """ if not nick: nick = msg.nick irc.reply(irc.state.nickToHostmask(nick)) hostmask = wrap(hostmask, [additional('seenNick')]) @internationalizeDocstring def list(self, irc, msg, args, name): """[<name>] Returns the hostmasks of the user specified by <name>; if <name> isn't specified, returns the hostmasks of the user calling the command. """ def getHostmasks(user): hostmasks = list(map(repr, user.hostmasks)) if hostmasks: hostmasks.sort() return format('%L', hostmasks) else: return format(_('%s has no registered hostmasks.'), user.name) try: user = ircdb.users.getUser(msg.prefix) if name: if name != user.name and \ not ircdb.checkCapability(msg.prefix, 'owner'): irc.error(_('You may only retrieve your own ' 'hostmasks.'), Raise=True) else: try: user = ircdb.users.getUser(name) irc.reply(getHostmasks(user), private=True) except KeyError: irc.errorNoUser() else: irc.reply(getHostmasks(user), private=True) except KeyError: irc.errorNotRegistered() list = wrap(list, [additional('something')]) @internationalizeDocstring def add(self, irc, msg, args, user, hostmask, password): """[<name>] [<hostmask>] [<password>] Adds the hostmask <hostmask> to the user specified by <name>. The <password> may only be required if the user is not recognized by hostmask. <password> is also not required if an owner user is giving the command on behalf of some other user. If <hostmask> is not given, it defaults to your current hostmask. If <name> is not given, it defaults to your currently identified name. This message must be sent to the bot privately (not on a channel) since it may contain a password. """ caller_is_owner = ircdb.checkCapability(msg.prefix, 'owner') if not hostmask: hostmask = msg.prefix if not ircutils.isUserHostmask(hostmask): irc.errorInvalid(_('hostmask'), hostmask, _('Make sure your hostmask includes a nick, ' 'then an exclamation point (!), then a user, ' 'then an at symbol (@), then a host. Feel ' 'free to use wildcards (* and ?, which work ' 'just like they do on the command line) in ' 'any of these parts.'), Raise=True) try: otherId = ircdb.users.getUserId(hostmask) if otherId != user.id: if caller_is_owner: err = _('That hostmask is already registered to %s.') err %= otherId else: err = _('That hostmask is already registered.') irc.error(err, Raise=True) except KeyError: pass if not user.checkPassword(password) and \ not user.checkHostmask(msg.prefix) and \ not caller_is_owner: irc.error(conf.supybot.replies.incorrectAuthentication(), Raise=True) try: user.addHostmask(hostmask) except ValueError as e: irc.error(str(e), Raise=True) try: ircdb.users.setUser(user) except ircdb.DuplicateHostmask as e: user.removeHostmask(hostmask) if caller_is_owner: err = _('That hostmask is already registered to %s.') \ % e.args[0] else: err = _('That hostmask is already registered.') irc.error(err, Raise=True) except ValueError as e: irc.error(str(e), Raise=True) irc.replySuccess() add = wrap(add, ['private', first('otherUser', 'user'), optional('something'), additional('something', '')]) @internationalizeDocstring def remove(self, irc, msg, args, user, hostmask, password): """[<name>] [<hostmask>] [<password>] Removes the hostmask <hostmask> from the record of the user specified by <name>. If the hostmask given is 'all' then all hostmasks will be removed. The <password> may only be required if the user is not recognized by their hostmask. This message must be sent to the bot privately (not on a channel) since it may contain a password. If <hostmask> is not given, it defaults to your current hostmask. If <name> is not given, it defaults to your currently identified name. """ if not hostmask: hostmask = msg.prefix if not user.checkPassword(password) and \ not user.checkHostmask(msg.prefix): if not ircdb.checkCapability(msg.prefix, 'owner'): irc.error(conf.supybot.replies.incorrectAuthentication()) return try: s = '' if hostmask == 'all': user.hostmasks.clear() s = _('All hostmasks removed.') else: user.removeHostmask(hostmask) except KeyError: irc.error(_('There was no such hostmask.')) return ircdb.users.setUser(user) irc.replySuccess(s) remove = wrap(remove, ['private', first('otherUser', 'user'), optional('something'), additional('something', '')]) def callCommand(self, command, irc, msg, *args, **kwargs): if command[0] != 'gpg' or \ (gpg.available and self.registryValue('gpg.enable')): return super(User, self) \ .callCommand(command, irc, msg, *args, **kwargs) else: irc.error(_('GPG features are not enabled.')) @internationalizeDocstring def capabilities(self, irc, msg, args, user): """[<name>] Returns the capabilities of the user specified by <name>; if <name> isn't specified, returns the capabilities of the user calling the command. """ try: u = ircdb.users.getUser(msg.prefix) except KeyError: irc.errorNotRegistered() else: if u == user or u._checkCapability('admin'): irc.reply('[%s]' % '; '.join(user.capabilities), private=True) else: irc.error(conf.supybot.replies.incorrectAuthentication(), Raise=True) capabilities = wrap(capabilities, [first('otherUser', 'user')]) @internationalizeDocstring def identify(self, irc, msg, args, user, password): """<name> <password> Identifies the user as <name>. This command (and all other commands that include a password) must be sent to the bot privately, not in a channel. """ if user.checkPassword(password): try: user.addAuth(msg.prefix) ircdb.users.setUser(user, flush=False) irc.replySuccess() except ValueError: irc.error(_('Your secure flag is true and your hostmask ' 'doesn\'t match any of your known hostmasks.')) else: self.log.warning('Failed identification attempt by %s (password ' 'did not match for %s).', msg.prefix, user.name) irc.error(conf.supybot.replies.incorrectAuthentication()) identify = wrap(identify, ['private', 'otherUser', 'something']) @internationalizeDocstring def unidentify(self, irc, msg, args, user): """takes no arguments Un-identifies you. Note that this may not result in the desired effect of causing the bot not to recognize you anymore, since you may have added hostmasks to your user that can cause the bot to continue to recognize you. """ user.clearAuth() ircdb.users.setUser(user) irc.replySuccess(_('If you remain recognized after giving this command, ' 'you\'re being recognized by hostmask, rather than ' 'by password. You must remove whatever hostmask is ' 'causing you to be recognized in order not to be ' 'recognized.')) unidentify = wrap(unidentify, ['user']) @internationalizeDocstring def whoami(self, irc, msg, args): """takes no arguments Returns the name of the user calling the command. """ try: user = ircdb.users.getUser(msg.prefix) irc.reply(user.name) except KeyError: error = self.registryValue('customWhoamiError') or \ _('I don\'t recognize you. You can message me either of these two commands: "user identify <username> <password>" to log in or "user register <username> <password>" to register.') irc.reply(error) whoami = wrap(whoami) @internationalizeDocstring def stats(self, irc, msg, args): """takes no arguments Returns some statistics on the user database. """ users = 0 owners = 0 admins = 0 hostmasks = 0 for user in ircdb.users.values(): users += 1 hostmasks += len(user.hostmasks) try: if user._checkCapability('owner'): owners += 1 elif user._checkCapability('admin'): admins += 1 except KeyError: pass irc.reply(format(_('I have %s registered users ' 'with %s registered hostmasks; ' '%n and %n.'), users, hostmasks, (owners, 'owner'), (admins, 'admin'))) stats = wrap(stats) Class = User # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/User/test.py������������������������������������������������������������0000644�0001750�0001750�00000021201�13634634532�017243� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import re from supybot.test import PluginTestCase, network import supybot.conf as conf import supybot.world as world import supybot.ircdb as ircdb import supybot.utils as utils class UserTestCase(PluginTestCase): plugins = ('User', 'Admin', 'Config') prefix1 = 'somethingElse!user@host1.tld' prefix2 = 'EvensomethingElse!user@host2.tld' prefix3 = 'Completely!Different@host3.tld__no_testcap__' def testHostmaskList(self): self.assertError('hostmask list') original = self.prefix self.prefix = self.prefix1 self.assertNotError('register foo bar') self.prefix = original self.assertError('hostmask list foo') self.assertNotError('hostmask add foo [hostmask] bar') self.assertNotError('hostmask add foo') self.assertNotRegexp('hostmask add foo', 'IrcSet') def testHostmaskListHandlesEmptyListGracefully(self): self.assertError('hostmask list') self.prefix = self.prefix1 self.assertNotError('register foo bar') self.assertNotError('hostmask remove foo %s' % self.prefix1) self.assertNotError('identify foo bar') self.assertRegexp('hostmask list', 'no registered hostmasks') def testHostmaskOverlap(self): self.assertNotError('register foo passwd', frm=self.prefix1) self.assertNotError('register bar passwd', frm=self.prefix2) self.assertResponse('whoami', 'foo', frm=self.prefix1) self.assertResponse('whoami', 'bar', frm=self.prefix2) self.assertNotError('hostmask add foo *!*@foobar/b', frm=self.prefix1) self.assertResponse('hostmask add bar *!*@foobar/*', 'Error: That hostmask is already registered to foo.', frm=self.prefix2) self.assertRegexp('hostmask list foo', r'\*!\*@foobar/b', frm=self.prefix1) self.assertNotRegexp('hostmask list bar', 'foobar', frm=self.prefix2) def testHostmaskOverlapPrivacy(self): self.assertNotError('register foo passwd', frm=self.prefix1) self.assertNotError('register bar passwd', frm=self.prefix3) self.assertResponse('whoami', 'foo', frm=self.prefix1) self.assertResponse('whoami', 'bar', frm=self.prefix3) self.assertNotError('hostmask add foo *!*@foobar/b', frm=self.prefix1) ircdb.users.getUser('bar').addCapability('owner') self.assertResponse('whoami', 'bar', frm=self.prefix3) self.assertResponse('capabilities', '[owner]', frm=self.prefix3) self.assertResponse('hostmask add *!*@foobar/*', 'Error: That hostmask is already registered to foo.', frm=self.prefix3) ircdb.users.getUser('bar').removeCapability('owner') self.assertResponse('hostmask add *!*@foobar/*', 'Error: That hostmask is already registered.', frm=self.prefix3) def testHostmask(self): self.assertResponse('hostmask', self.prefix) self.assertError('@hostmask asdf') m = self.irc.takeMsg() self.assertFalse(m is not None, m) def testRegisterUnregister(self): self.prefix = self.prefix1 self.assertNotError('register foo bar') self.assertError('register foo baz') self.assertTrue(ircdb.users.getUserId('foo')) self.assertError('unregister foo') self.assertNotError('unregister foo bar') self.assertRaises(KeyError, ircdb.users.getUserId, 'foo') def testDisallowedUnregistration(self): self.prefix = self.prefix1 self.assertNotError('register foo bar') orig = conf.supybot.databases.users.allowUnregistration() conf.supybot.databases.users.allowUnregistration.setValue(False) try: self.assertError('unregister foo') m = self.irc.takeMsg() self.assertFalse(m is not None, m) self.assertTrue(ircdb.users.getUserId('foo')) finally: conf.supybot.databases.users.allowUnregistration.setValue(orig) def testList(self): self.prefix = self.prefix1 self.assertNotError('register foo bar') self.assertResponse('user list', 'foo') self.prefix = self.prefix2 self.assertNotError('register biff quux') self.assertResponse('user list', 'biff and foo') self.assertRegexp('user list --capability testcap', 'no matching') self.assertNotError('admin capability add biff testcap') self.assertResponse('user list --capability testcap', 'biff') self.assertNotError('config capabilities.private testcap') self.assertRegexp('user list --capability testcap', 'Error:.*private') self.assertNotError('admin capability add biff admin') self.assertResponse('user list --capability testcap', 'biff') self.assertNotError('admin capability remove biff admin') self.assertRegexp('user list --capability testcap', 'Error:.*private') self.assertNotError('config capabilities.private ""') self.assertResponse('user list --capability testcap', 'biff') self.assertNotError('admin capability remove biff testcap') self.assertRegexp('user list --capability testcap', 'no matching') self.assertResponse('user list f', 'biff and foo') self.assertResponse('user list f*', 'foo') self.assertResponse('user list *f', 'biff') self.assertNotError('unregister biff quux') self.assertResponse('user list', 'foo') self.assertNotError('unregister foo bar') self.assertRegexp('user list', 'no registered users') self.assertRegexp('user list asdlfkjasldkj', 'no matching registered') def testListHandlesCaps(self): self.prefix = self.prefix1 self.assertNotError('register Foo bar') self.assertResponse('user list', 'Foo') self.assertResponse('user list f*', 'Foo') def testChangeUsername(self): self.prefix = self.prefix1 self.assertNotError('register foo bar') self.prefix = self.prefix2 self.assertNotError('register bar baz') self.prefix = self.prefix1 self.assertError('changename foo bar') self.assertNotError('changename foo baz') def testSetpassword(self): self.prefix = self.prefix1 self.assertNotError('register foo bar') password = ircdb.users.getUser(self.prefix).password self.assertNotEqual(password, 'bar') self.assertNotError('set password foo bar baz') self.assertNotEqual(ircdb.users.getUser(self.prefix).password,password) self.assertNotEqual(ircdb.users.getUser(self.prefix).password, 'baz') def testStats(self): self.assertNotError('user stats') self.assertNotError('load Lart') self.assertNotError('user stats') def testUserPluginAndUserList(self): self.prefix = self.prefix1 self.assertNotError('register Foo bar') self.assertResponse('user list', 'Foo') self.assertNotError('load Seen') self.assertResponse('user list', 'Foo') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Utilities/��������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�016761� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Utilities/__init__.py���������������������������������������������������0000644�0001750�0001750�00000004473�13634634532�021074� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Various utility commands, mostly useful for manipulating nested commands. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload reload(plugin) # In case we're being reloaded. if world.testing: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Utilities/config.py�����������������������������������������������������0000644�0001750�0001750�00000004701�13634634532�020574� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Utilities') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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('Utilities', True) Utilities = conf.registerPlugin('Utilities') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Utilities, 'someConfigVariableName', # registry.Boolean(False, _("""Help for someConfigVariableName."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������limnoria-2020.03.17/plugins/Utilities/locales/������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�020403� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Utilities/locales/de.po�������������������������������������������������0000644�0001750�0001750�00000005644�13634634532�021336� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-10-30 17:46+0100\n" "Last-Translator: Florian Besser <fbesser@gmail.com>\n" "Language-Team: German <fbesser@gmail.com>\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: German\n" "X-Poedit-Country: GERMANY\n" #: plugin.py:45 msgid "" "requires no arguments\n" "\n" " Does nothing. Useful sometimes for sequencing commands when you don't\n" " care about their non-error return values.\n" " " msgstr "" "benötigt keine Argumente\n" "\n" "Tut nichts. Manchmal nützlich um Befehle aneinanderzuketten, wenn die Rückgabewerte der Befehle egal ist." #: plugin.py:59 msgid "" "[<text>]\n" "\n" " Does nothing except to reply with a success message. This is useful\n" " when you want to run multiple commands as nested commands, and don't\n" " care about their output as long as they're successful. An error, of\n" " course, will break out of this command. <text>, if given, will be\n" " appended to the end of the success message.\n" " " msgstr "" #: plugin.py:72 msgid "" "<text> [<text> ...]\n" "\n" " Returns the last argument given. Useful when you'd like multiple\n" " nested commands to run, but only the output of the last one to be\n" " returned.\n" " " msgstr "" #: plugin.py:86 msgid "" "<text>\n" "\n" " Returns the arguments given it. Uses our standard substitute on the\n" " string(s) given to it; $nick (or $who), $randomNick, $randomInt,\n" " $botnick, $channel, $user, $host, $today, $now, and $randomDate are all\n" " handled appropriately.\n" " " msgstr "" "<Text>\n" "\n" "Gibt die angegeben Argumente an. Wendet unsere Standardsubstitution auf die Zeichenketten an: $nick (or $who), $randomNick, $randomInt, $botnick, $channel, $user, $host, $today, $now, and $randomDate werden behandelt." #: plugin.py:99 msgid "" "<arg> [<arg> ...]\n" "\n" " Shuffles the arguments given.\n" " " msgstr "" "<Argument> [<Argument> ...]\n" "\n" "Mischt die angegebenen Argumente" #: plugin.py:109 msgid "" "<num> <arg> [<arg> ...]\n" "\n" " Randomly chooses <num> items out of the arguments given.\n" " " msgstr "" "<Zahl> <Argument> [<Argument> ...]\n" "\n" "Wählt zufällig <Zahl> bestandteile aus den angegeben Argumenten aus." #: plugin.py:122 msgid "" "<arg> [<arg> ...]\n" "\n" " Counts the arguments given.\n" " " msgstr "" "<Argument> [<Argument> ...]\n" "\n" "Zählt die angegeben Argumente." #: plugin.py:131 msgid "" "<command> <text>\n" "\n" " Tokenizes <text> and calls <command> with the resulting arguments.\n" " " msgstr "" "<Befehl> <Text>\n" "\n" "Bricht den <Text> auseinander und ruft <Befehl> mit den resultierenden Argumenten auf." ��������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Utilities/locales/fi.po�������������������������������������������������0000644�0001750�0001750�00000010652�13634634532�021337� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Utilities plugin in Limnoria. # Copyright (C) 2011 Limnoria # Mikaela Suomalainen <mikaela.suomalainen@outlook.com>, 2011. # msgid "" msgstr "" "Project-Id-Version: Utilities plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 13:30+EET\n" "PO-Revision-Date: 2014-12-20 13:58+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: \n" "Language: fi\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-Generator: Poedit 1.6.10\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: plugin.py:41 #, fuzzy msgid "Provides useful commands for bot scripting / command nesting." msgstr "" "Tarjoaa hyödyllisiä komentoja botin skriptaukseen / komentoja laittamiseen " "sisäkkäisiksi." #: plugin.py:46 msgid "" "requires no arguments\n" "\n" " Does nothing. Useful sometimes for sequencing commands when you " "don't\n" " care about their non-error return values.\n" " " msgstr "" "ei ota parametrejä\n" "\n" " Ei tee mitään. Hyödyllinen komentojen suorittamiseen ketjussa, kun " "et\n" " välitä niiden ei-virhe arvoista.\n" " " #: plugin.py:60 msgid "" "[<text>]\n" "\n" " Does nothing except to reply with a success message. This is " "useful\n" " when you want to run multiple commands as nested commands, and " "don't\n" " care about their output as long as they're successful. An error, " "of\n" " course, will break out of this command. <text>, if given, will be\n" " appended to the end of the success message.\n" " " msgstr "" "[<teksti>]\n" "\n" " Ei tee mitään muuta, kuin vastaa onnistumisviestillä. Tämä on " "hyödyllinen, kun\n" " haluat suorittaa monta komentoa sisäkkäisinä komentoina, ja et\n" " välitä niiden ulostuloista niin kauan, kuin ne ovat onnistuneita. " "Virheilmoitus, tietenkin, \n" " murtaa tämän komennon. <Teksti>, lisätään, jos se on annettu, \n" " onnistumisviestin perään.\n" " " #: plugin.py:73 msgid "" "<text> [<text> ...]\n" "\n" " Returns the last argument given. Useful when you'd like multiple\n" " nested commands to run, but only the output of the last one to be\n" " returned.\n" " " msgstr "" "<teksti> [<teksti> ...]\n" "\n" " Palauttaa viimeisen annetun parametrin. Hyödyllinen, kun haluat " "monen sisäkkäisen komennon tulevan suoritetuksi, mutta\n" " vastaanottaa vain niistä viimeisen\n" " ulostulon.\n" " " #: plugin.py:87 msgid "" "<text>\n" "\n" " Returns the arguments given it. Uses our standard substitute on " "the\n" " string(s) given to it; $nick (or $who), $randomNick, $randomInt,\n" " $botnick, $channel, $user, $host, $today, $now, and $randomDate are " "all\n" " handled appropriately.\n" " " msgstr "" "<teksti>\n" "\n" " Palauttaa parametrit, joita sille on annettu. Käyttää " "perusmuunnoksia \n" " merkkiketju(issa), jotka on annettu sille; $nick (tai $who), " "$randomNick, $randomInt,\n" " $botnick, $channel, $user, $host, $today, $now, and $randomDate " "hoidetaan kaikki\n" " oikealla tavalla.\n" " " #: plugin.py:100 msgid "" "<arg> [<arg> ...]\n" "\n" " Shuffles the arguments given.\n" " " msgstr "" "<parametri> [<parametri> ...]\n" "\n" " Sekoittaa annetut parametrit keskenään.\n" " " #: plugin.py:110 msgid "" "<arg> [<arg> ...]\n" "\n" " Sorts the arguments given.\n" " " msgstr "" "<parametri> [<parametri> ...]\n" "\n" " Lajittelee annetut parametrit.\n" " " #: plugin.py:121 msgid "" "<num> <arg> [<arg> ...]\n" "\n" " Randomly chooses <num> items out of the arguments given.\n" " " msgstr "" "<numero> <parametri> [<parametri> ...]\n" "\n" " Sattumanvaraisesti valitsee <numeron> verran parametrejä annetuista " "parametreistä.\n" " " #: plugin.py:134 msgid "" "<arg> [<arg> ...]\n" "\n" " Counts the arguments given.\n" " " msgstr "" "<parametri> [<parametri> ...]\n" "\n" " Laskee annetut parametrit.\n" " " #: plugin.py:143 msgid "" "<command> <text>\n" "\n" " Tokenizes <text> and calls <command> with the resulting arguments.\n" " " msgstr "" "<command> <text>\n" "\n" " Tokenizoi <tekstin> ja kutsuu <komennon> tuloksena olevilla " "parametreillä.\n" " " ��������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Utilities/locales/fr.po�������������������������������������������������0000644�0001750�0001750�00000007007�13634634532�021350� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-20 11:29+CET\n" "PO-Revision-Date: \n" "Last-Translator: Valentin Lorentz <progval@gmail.com>\n" "Language-Team: Limnoria <progval@gmail.com>\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:45 msgid "" "requires no arguments\n" "\n" " Does nothing. Useful sometimes for sequencing commands when you don't\n" " care about their non-error return values.\n" " " msgstr "" "ne requiert pas d'argument\n" "\n" "Ne fait rien. Utile pour séquencer les commandes lorsque vous vous fichez de leur valeur de retour." #: plugin.py:59 msgid "" "[<text>]\n" "\n" " Does nothing except to reply with a success message. This is useful\n" " when you want to run multiple commands as nested commands, and don't\n" " care about their output as long as they're successful. An error, of\n" " course, will break out of this command. <text>, if given, will be\n" " appended to the end of the success message.\n" " " msgstr "" "[<texte>]\n" "\n" "Ne fait rien excepté répondre avec un message de succès. C'est utile lorsque vous voulez lancer plusieurs commandes comme commandes imbriquées, et vous ne voulez pas vous occuper de leur retour, du moment que ce n'est pas une erreur. Si c'est une erreur, bien sûr, la commande s'arrêtera. <texte>, si il est donné, sera ajouté à la fin du message de succès." #: plugin.py:72 msgid "" "<text> [<text> ...]\n" "\n" " Returns the last argument given. Useful when you'd like multiple\n" " nested commands to run, but only the output of the last one to be\n" " returned.\n" " " msgstr "" "<texte> [<texte> ...]\n" "\n" "Retourne le dernier argument de la commande. Utile lorsque vous avez plusieurs commandes imbriquées, mais seulement la sortie de la dernière sera retournée." #: plugin.py:86 msgid "" "<text>\n" "\n" " Returns the arguments given it. Uses our standard substitute on the\n" " string(s) given to it; $nick (or $who), $randomNick, $randomInt,\n" " $botnick, $channel, $user, $host, $today, $now, and $randomDate are all\n" " handled appropriately.\n" " " msgstr "" "<texte>\n" "\n" "Retourne les arguments donnés. Utilise notre système de substitution standard sur la/les chaîne(s) qui lui est donnée ; $nick (ou $who), $randomNick, $randomInt, $botnick, $channel, $user, $host, $today, $now, et $randomDate sont tous gérés correctement." #: plugin.py:99 msgid "" "<arg> [<arg> ...]\n" "\n" " Shuffles the arguments given.\n" " " msgstr "" "<argument> [<argument> ...]\n" "\n" "Mélange les arguments donnés." #: plugin.py:109 msgid "" "<num> <arg> [<arg> ...]\n" "\n" " Randomly chooses <num> items out of the arguments given.\n" " " msgstr "" "<nombre> <argument> [<argument> ...]\n" "\n" "Choisi de façon aléatoire un certain <nombre> d'<argument>s." #: plugin.py:122 msgid "" "<arg> [<arg> ...]\n" "\n" " Counts the arguments given.\n" " " msgstr "" "<argument> [<argument> ...]\n" "\n" "Retorune le nombre d'<argument>s donnés." #: plugin.py:131 msgid "" "<command> <text>\n" "\n" " Tokenizes <text> and calls <command> with the resulting arguments.\n" " " msgstr "" "<commande> <texte>\n" "\n" "Tokénise le <texte> et appelle la commande avec l'argument résultant." �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Utilities/locales/it.po�������������������������������������������������0000644�0001750�0001750�00000007303�13634634532�021354� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-06-15 18:37+0200\n" "Last-Translator: skizzhg <skizzhg@gmx.com>\n" "Language-Team: Italian <skizzhg@gmx.com>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: plugin.py:45 #, docstring msgid "" "requires no arguments\n" "\n" " Does nothing. Useful sometimes for sequencing commands when you don't\n" " care about their non-error return values.\n" " " msgstr "" "non necessita argomenti\n" "\n" " Non fa niente. Utile per eseguire comandi in sequenza quando non ci si\n" " cura del valore dello stato di uscita restituito.\n" " " #: plugin.py:59 #, docstring msgid "" "[<text>]\n" "\n" " Does nothing except to reply with a success message. This is useful\n" " when you want to run multiple commands as nested commands, and don't\n" " care about their output as long as they're successful. An error, of\n" " course, will break out of this command. <text>, if given, will be\n" " appended to the end of the success message.\n" " " msgstr "" "[<testo>]\n" "\n" " Non fa nient'altro che rispondere con un messaggio di successo. Utile\n" " quando si vuole eseguire comandi multipli come nidificati, e non ci si\n" " cura del loro output finché questi riescono con successo. Un errore,\n" " naturalmente, interromperà questo comando. <testo>, se fornito, sarà\n" " aggiunto alla fine del messaggio di successo.\n" " " #: plugin.py:72 #, docstring msgid "" "<text> [<text> ...]\n" "\n" " Returns the last argument given. Useful when you'd like multiple\n" " nested commands to run, but only the output of the last one to be\n" " returned.\n" " " msgstr "" "<testo> [<testo> ...]\n" "\n" " Restituisce l'ultimo argomento dato. Utile quando si vogliono eseguire\n" " comandi nidificati ottenendo solo l'output dell'ultimo.\n" " " #: plugin.py:86 #, docstring msgid "" "<text>\n" "\n" " Returns the arguments given it. Uses our standard substitute on the\n" " string(s) given to it; $nick (or $who), $randomNick, $randomInt,\n" " $botnick, $channel, $user, $host, $today, $now, and $randomDate are all\n" " handled appropriately.\n" " " msgstr "" "<testo>\n" "\n" " Restituisce gli argomenti dati. Utilizza il nostro sistema di sostituzione\n" " standard con la stringa fornita; $nick (o $who), $randomNick, $randomInt, $botnick,\n" " $channel, $user, $host, $today, $now e $randomDate sono tutte gestite correttamente.\n" " " #: plugin.py:99 #, docstring msgid "" "<arg> [<arg> ...]\n" "\n" " Shuffles the arguments given.\n" " " msgstr "" "<argomento> [<argomento> ...]\n" "\n" " Mescola gli argomenti forniti.\n" " " #: plugin.py:109 #, docstring msgid "" "<num> <arg> [<arg> ...]\n" "\n" " Randomly chooses <num> items out of the arguments given.\n" " " msgstr "" "<numero> <argomento> [<argomento> ...]\n" "\n" " Sceglie in modo casuale un certo <numero> di argomenti.\n" " " #: plugin.py:122 #, docstring msgid "" "<arg> [<arg> ...]\n" "\n" " Counts the arguments given.\n" " " msgstr "" "<argomento> [<argomento> ...]\n" "\n" " Conta gli argomenti forniti.\n" " " #: plugin.py:131 #, docstring msgid "" "<command> <text>\n" "\n" " Tokenizes <text> and calls <command> with the resulting arguments.\n" " " msgstr "" "<comando> <testo>\n" "\n" " Tokenizza <testo> e chiama <comando> con gli argomenti risultanti.\n" " " �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Utilities/plugin.py�����������������������������������������������������0000644�0001750�0001750�00000015555�13634634532�020636� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2010, James McCoy # 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 types import random from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Utilities') class Utilities(callbacks.Plugin): """Provides useful commands for bot scripting / command nesting.""" # Yes, I really do mean "requires no arguments" below. "takes no # arguments" would probably lead people to think it was a useless command. @internationalizeDocstring def ignore(self, irc, msg, args): """requires no arguments Does nothing. Useful sometimes for sequencing commands when you don't care about their non-error return values. """ msg.tag('ignored') irc.noReply() # Do be careful not to wrap this unless you do any('something'). @internationalizeDocstring def success(self, irc, msg, args, text): """[<text>] Does nothing except to reply with a success message. This is useful when you want to run multiple commands as nested commands, and don't care about their output as long as they're successful. An error, of course, will break out of this command. <text>, if given, will be appended to the end of the success message. """ irc.replySuccess(text) success = wrap(success, [additional('text')]) @internationalizeDocstring def last(self, irc, msg, args): """<text> [<text> ...] Returns the last argument given. Useful when you'd like multiple nested commands to run, but only the output of the last one to be returned. """ args = list(filter(None, args)) if args: irc.reply(args[-1]) else: raise callbacks.ArgumentError @internationalizeDocstring def echo(self, irc, msg, args, text): """<text> Returns the arguments given it. Uses our standard substitute on the string(s) given to it; $nick (or $who), $randomNick, $randomInt, $botnick, $channel, $user, $host, $today, $now, and $randomDate are all handled appropriately. """ text = ircutils.standardSubstitute(irc, msg, text) irc.reply(text, prefixNick=False) echo = wrap(echo, ['text']) @internationalizeDocstring def shuffle(self, irc, msg, args, things): """<arg> [<arg> ...] Shuffles the arguments given. """ random.shuffle(things) irc.reply(' '.join(things)) shuffle = wrap(shuffle, [many('anything')]) @internationalizeDocstring def sort(self, irc, msg, args, things): """<arg> [<arg> ...] Sorts the arguments given. """ irc.reply(' '.join(map(str, sorted(things)))) # Keep ints as ints, floats as floats, without comparing between numbers # and strings. sort = wrap(sort, [first(many(first('int', 'float')), many('anything'))]) @internationalizeDocstring def sample(self, irc, msg, args, num, things): """<num> <arg> [<arg> ...] Randomly chooses <num> items out of the arguments given. """ try: samp = random.sample(things, num) irc.reply(' '.join(samp)) except ValueError as e: irc.error('%s' % (e,)) sample = wrap(sample, ['positiveInt', many('anything')]) @internationalizeDocstring def countargs(self, irc, msg, args, things): """<arg> [<arg> ...] Counts the arguments given. """ irc.reply(len(things)) countargs = wrap(countargs, [any('anything')]) @internationalizeDocstring def apply(self, irc, msg, args, command, rest): """<command> <text> Tokenizes <text> and calls <command> with the resulting arguments. """ args = [token and token or '""' for token in rest] text = ' '.join(args) commands = command.split() commands = list(map(callbacks.canonicalName, commands)) tokens = callbacks.tokenize(text, channel=msg.channel, network=irc.network) allTokens = commands + tokens self.Proxy(irc, msg, allTokens) apply = wrap(apply, ['something', many('something')]) def let(self, irc, msg, args, var_name, _, value, __, command): """<variable> = <value> in <command> Defines <variable> to be equal to <value> in the <command> and runs the <command>. '=' and 'in' can be omitted.""" if msg.reply_env and var_name in msg.reply_env: # For security reason (eg. a Sudo-like plugin), we don't want # to make it possible to override stuff like $nick. irc.error(_('Cannot set a variable that already exists.'), Raise=True) fake_msg = ircmsgs.IrcMsg(msg=msg) if fake_msg.reply_env is None: fake_msg.reply_env = {} fake_msg.reply_env[var_name] = value tokens = callbacks.tokenize(command, channel=msg.channel, network=irc.network) self.Proxy(irc, fake_msg, tokens) let = wrap(let, [ 'something', optional(('literal', ['='])), 'something', optional(('literal', ['in'])), 'text']) Class = Utilities # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Utilities/test.py�������������������������������������������������������0000644�0001750�0001750�00000007512�13634634532�020311� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf8 -*- ### # Copyright (c) 2002-2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.utils.minisix import u from supybot.test import * class UtilitiesTestCase(PluginTestCase): plugins = ('Math', 'Utilities', 'String') def testIgnore(self): self.assertNoResponse('utilities ignore foo bar baz', 1) self.assertError('utilities ignore [re m/foo bar]') self.assertResponse('echo [utilities ignore foobar] qux', 'qux') def testSuccess(self): self.assertNotError('success 1') self.assertError('success [re m/foo bar]') def testLast(self): self.assertResponse('utilities last foo bar baz', 'baz') def testEcho(self): self.assertHelp('echo') self.assertResponse('echo foo', 'foo') self.assertResponse(u('echo 好'), '好') self.assertResponse(u('echo "好"'), '好') def testEchoDollarOneRepliesDollarOne(self): self.assertResponse('echo $1', '$1') def testEchoStandardSubstitute(self): self.assertNotRegexp('echo $nick', r'\$') def testEchoStripCtcp(self): self.assertResponse('echo \x01ACTION foo\x01', "ACTION foo") def testApply(self): self.assertResponse('apply "utilities last" a', 'a') self.assertResponse('apply "utilities last" a b', 'b') def testShuffle(self): self.assertResponse('shuffle a', 'a') def testSort(self): self.assertResponse('sort abc cab cba bca', 'abc bca cab cba') self.assertResponse('sort 2 12 42 7 2', '2 2 7 12 42') self.assertResponse('sort 2 8 12.2 12.11 42 7 2', '2 2 7 8 12.11 12.2 42') 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]$') def testCountargs(self): self.assertResponse('countargs a b c', '3') self.assertResponse('countargs a "b c"', '2') self.assertResponse('countargs', '0') def testLet(self): self.assertResponse('let x = 42 in echo foo $x bar', 'foo 42 bar') self.assertResponse( 'let y = 21 in "' 'let x = [math calc 2*[echo $y]] in ' 'echo foo $x bar"', 'foo 42 bar') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Web/��������������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�015523� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Web/__init__.py���������������������������������������������������������0000644�0001750�0001750�00000004431�13634634532�017630� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Includes various web-related commands. """ import supybot import supybot.world as world __version__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch __maintainer__ = supybot.authors.limnoria_core # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} from . import config from . import plugin from importlib import reload 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Web/config.py�����������������������������������������������������������0000644�0001750�0001750�00000012122�13634634532�017332� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot.conf as conf import supybot.registry as registry from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Web') def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified themself 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 Web = conf.registerPlugin('Web', True) if yn("""This plugin also offers a snarfer that will try to fetch the title of URLs that it sees in the channel. Would like you this snarfer to be enabled?""", default=False): Web.titleSnarfer.setValue(True) Web = conf.registerPlugin('Web') conf.registerChannelValue(Web, 'titleSnarfer', registry.Boolean(False, _("""Determines whether the bot will output the HTML title of URLs it sees in the channel."""))) conf.registerChannelValue(Web, 'snarferReportIOExceptions', registry.Boolean(False, _("""Determines whether the bot will notfiy the user about network exceptions like hostnotfound, timeout ...."""))) conf.registerChannelValue(Web, 'snarferShowDomain', registry.Boolean(True, _("""Determines whether domain names should be displayed by the title snarfer."""))) conf.registerChannelValue(Web, 'snarfMultipleUrls', registry.Boolean(False, _("""Determines whether the title snarfer will query all URLs in a message, or only the first one."""))) conf.registerChannelValue(Web, 'snarferShowTargetDomain', registry.Boolean(False, _("""Determines whether the domain name displayed by the snarfer will be the original one (posted on IRC) or the target one (got after following redirects, if any)."""))) conf.registerChannelValue(Web, 'snarferPrefix', registry.String(_('Title:'), _("""Determines the string used at before a web page's title."""))) conf.registerChannelValue(Web, 'nonSnarfingRegexp', registry.Regexp(None, _("""Determines what URLs matching the given regexp will not be snarfed. Give the empty string if you have no URLs that you'd like to exclude from being snarfed."""))) conf.registerChannelValue(Web, 'checkIgnored', registry.Boolean(True, _("""Determines whether the title snarfer checks if the author of a message is ignored."""))) conf.registerGlobalValue(Web, 'urlWhitelist', registry.SpaceSeparatedListOfStrings([], """If set, bot will only fetch data from urls in the whitelist, i.e. starting with http://domain/optionalpath/. This will apply to all commands that retrieve data from user-supplied URLs, including fetch, headers, title, doctype.""")) conf.registerGlobalValue(Web, 'timeout', registry.NonNegativeInteger(5, """Determines the maximum number of seconds the bot will wait for the site to respond, when using a command in this plugin other than 'fetch'. If 0, will use socket.defaulttimeout""")) conf.registerGroup(Web, 'fetch') conf.registerGlobalValue(Web.fetch, 'maximum', registry.NonNegativeInteger(0, _("""Determines the maximum number of bytes the bot will download via the 'fetch' command in this plugin."""))) conf.registerGlobalValue(Web.fetch, 'timeout', registry.NonNegativeInteger(5, """Determines the maximum number of seconds the bot will wait for the site to respond, when using the 'fetch' command in this plugin. If 0, will use socket.defaulttimeout""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Web/locales/������������������������������������������������������������0000755�0001750�0001750�00000000000�13634634547�017145� 5����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������limnoria-2020.03.17/plugins/Web/locales/de.po�������������������������������������������������������0000644�0001750�0001750�00000011465�13634634532�020076� 0����������������������������������������������������������������������������������������������������ustar �val�����������������������������val�����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # msgid "" msgstr "" "Project-Id-Version: Supybot\n" "POT-Creation-Date: 2012-02-15 23:19+CET\n" "PO-Revision-Date: 2012-04-27 15:47+0200\n" "Last-Translator: Mikaela Suomalainen <mikaela.suomalainen@outlook.com>\n" "Language-Team: German <fbesser@gmail.com>\n" "Language: de\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: de\n" #: config.py:50 msgid "" "Determines whether the bot will output the\n" " HTML title of URLs it sees in the channel." msgstr "Legt fest ob der Bot den HTML Titel, von URLs die er im Channel sieht, ausgibt." #: config.py:53 #, fuzzy msgid "" "Determines what URLs matching the given regexp\n" " will not be snarfed. Give the empty string if you have no URLs that you'd\n" " like to exclude from being snarfed." msgstr "Legt fest welche URLs im Kanal gefangen werden und in der Datenbank gespeichert werden; URLs die auf den regulären Ausdruck zutreffen werden nicht gefangen. Gebe eine leere Zeichenkette an, falls du keine URLs hast die for dem gefangen werden ausgeschlossen werden." #: config.py:59 msgid "" "Determines the maximum number of\n" " bytes the bot will download via the 'fetch' command in this plugin." msgstr "Legt die maximal Anzahl an Bytes fest die der Bot über den 'fetch' Befehl herunterläd." #: plugin.py:71 msgid "Add the help for \"help Web\" here." msgstr "Füge die Hilfe für \"help Web\" hier hinzu." #: plugin.py:107 msgid "Title: %s (at %s)" msgstr "Titel: %s (auf %s)" #: plugin.py:114 msgid "" "<url>\n" "\n" " Returns the HTTP headers of <url>. Only HTTP urls are valid, of\n" " course.\n" " " msgstr "" "<url>\n" "\n" "Gibt den HTTP Kopf von <url> aus. Natürlich sind nur HTTP URLS zulässig." #: plugin.py:121 msgid "%s: %s" msgstr "%s: %s" #: plugin.py:131 msgid "" "<url>\n" "\n" " Returns the DOCTYPE string of <url>. Only HTTP urls are valid, of\n" " course.\n" " " msgstr "" "<url>\n" "\n" "Gibt die DOCTYPE Zeichenkette von <url> aus. Natürlich sind nur HTTP URLS zulässig" #: plugin.py:143 msgid "That URL has no specified doctype." msgstr "Diese URL hat doctype nicht spezifiziert." #: plugin.py:148 msgid "" "<url>\n" "\n" " Returns the Content-Length header of <url>. Only HTTP urls are valid,\n" " of course.\n" " " msgstr "" "<url>\n" "\n" "Gibt Content-Length Kopf der <url> aus. Natürlich sind nur HTTP URLs zulässig" #: plugin.py:157 #: plugin.py:162 msgid "%u is %S long." msgstr "%u ist %S lang." #: plugin.py:164 #, fuzzy msgid "The server didn't tell me how long %u is but it's longer than %S." msgstr "Der Server hat mir nicht gesagt wie lang %u ist, aber es ist länger als %S." #: plugin.py:173 msgid "" "<url>\n" "\n" " Returns the HTML <title>... of a URL.\n" " " msgstr "" "\n" "\n" "Gibt den HTML ... einer URL aus." #: plugin.py:188 msgid "That URL appears to have no HTML title." msgstr "Es scheint so als habe die URL keinen HTML Titel." #: plugin.py:190 msgid "That URL appears to have no HTML title within the first %S." msgstr "Es scheint so als habe die URL keinen HTML Titel innerhalb der ersten %S." #: plugin.py:198 msgid "" "\n" "\n" " Returns Netcraft.com's determination of what operating system and\n" " webserver is running on the host given.\n" " " msgstr "" "\n" "\n" "Gibt die Vermutung von Netcraft.com, über das Betriebssystem und Webserver des gegeben Hosts, aus." #: plugin.py:212 msgid "No results found for %s." msgstr "Keine Ergebnisse für %s gefunden." #: plugin.py:214 msgid "The format of page the was odd." msgstr "Das Format der Seite ist komisch." #: plugin.py:219 msgid "" "\n" "\n" " Returns the URL quoted form of the text.\n" " " msgstr "" "\n" "\n" "Gibt die URL zitierte Form vom gegeben Text aus." #: plugin.py:228 msgid "" "\n" "\n" " Returns the text un-URL quoted.\n" " " msgstr "" "\n" "\n" "Gibt den Text nicht URL zitiert aus." #: plugin.py:238 msgid "" "\n" "\n" " Returns the contents of , or as much as is configured in\n" " supybot.plugins.Web.fetch.maximum. If that configuration variable is\n" " set to 0, this command will be effectively disabled.\n" " " msgstr "" "\n" "\n" "Gibt den Inhalt von aus. oder soviel wie in supybot.plugins.Web.fetch.maximum konfiguriert wurde. Falls diese Konfigurationsvariable auf 0 gesetzt ist, wird der Befehl praktisch deaktiviert." #: plugin.py:246 msgid "This command is disabled (supybot.plugins.Web.fetch.maximum is set to 0)." msgstr "Dieser Befehl ist abgeschaltet (supybot.plugins.Web.fetch.maximum ist auf 0 gesetzt)." limnoria-2020.03.17/plugins/Web/locales/fi.po0000644000175000017500000001647013634634532020105 0ustar valval00000000000000# Web plugin in Limnoria. # Copyright (C) 2011 Limnoria # Mikaela Suomalainen , 2011-2014. # msgid "" msgstr "" "Project-Id-Version: Web plugin for Limnoria\n" "POT-Creation-Date: 2014-12-20 14:42+EET\n" "PO-Revision-Date: 2014-12-20 14:42+0200\n" "Last-Translator: Mikaela Suomalainen \n" "Language-Team: \n" "Language: fi\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-Generator: Poedit 1.6.10\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: config.py:50 msgid "" "Determines whether the bot will output the\n" " HTML title of URLs it sees in the channel." msgstr "" "Määrittää tulostaako botti\n" " kanavalle näkimiensä URL-osoitteiden HTML otsikot." #: config.py:53 msgid "" "Determines whether the bot will notfiy the user\n" " about network exceptions like hostnotfound, timeout ...." msgstr "" "Määrittää ilmoitetaanko käyttäjää verkkovirheistä,\n" " kuten isäntää ei löydy, aikakatkaisu ...." #: config.py:56 msgid "" "Determines whether the domain name displayed\n" " by the snarfer will be the original one (posted on IRC) or the target " "one\n" " (got after following redirects, if any)." msgstr "" "Määrittää onko kaappaajan näyttämä domain nimi se, joka lähetettiin " "alunperin IRC:ssä\n" " vai kohde domainin nimi (seuraten uudelleenohjauksia siinä tapauksessa, " "että niitä on)." #: config.py:60 msgid "" "Determines what URLs matching the given regexp\n" " will not be snarfed. Give the empty string if you have no URLs that " "you'd\n" " like to exclude from being snarfed." msgstr "" "Määrittää mitä säännöllistä lauseketta täsmäävät URL-osoitteet eivät tule " "kaapatuiksi.\n" " Anna tyhjä merkkiketju, mikäli sinulla ei ole URL-osoitteita, joiden et " "haluaisi tulevan\n" " kaapatuiksi." #: config.py:72 msgid "" "Determines the maximum number of\n" " bytes the bot will download via the 'fetch' command in this plugin." msgstr "" "Määrittää enimmäismäärän bittejä, jotka botti lataa \n" " käyttämällä 'fetch' komentoa tässä lisäosassa." #: plugin.py:90 msgid "" "Runs a command in a forked process with limited memory resources\n" " to prevent memory bomb caused by specially crafted http responses." msgstr "" "Suorittaa komennon forkatussa prosessissa rajoitetuilla muistiresursseilla\n" " estääkseen muistipommin, jonka aiheuttavat vartavasten luodut http-" "vastaukset." #: plugin.py:102 msgid "Page is too big or the server took too much time to answer the request." msgstr "Sivu on liian suuri tai palvelin vastasi pyyntöön liian hitaasti." #: plugin.py:111 msgid "Display a nice error instead of \"An error has occurred\"." msgstr "Näytä kiva virheilmoitus ilmoitukset \"Virhe on tapahtunut\" sijaan." #: plugin.py:121 msgid "Add the help for \"help Web\" here." msgstr "Lisää ohje komennolle \"help Web\" tähän." #: plugin.py:166 msgid "Title: %s (at %s)" msgstr "Otsikko: %s (sivustolla %s)" #: plugin.py:188 msgid "" "\n" "\n" " Returns the HTTP headers of . Only HTTP urls are valid, of\n" " course.\n" " " msgstr "" "\n" "\n" " Palauttaa -osoitteen HTTP otsikot. Tietysti, vain\n" " HTTP URL-osoitteet ovat kelvollisia.\n" " " #: plugin.py:198 msgid "%s: %s" msgstr "%s: %s" #: plugin.py:210 msgid "" "\n" "\n" " Returns the DOCTYPE string of . Only HTTP urls are valid, of\n" " course.\n" " " msgstr "" "\n" "\n" " Palauttaa -osoitteen DOCTYPE merkkiketjun. Tietysti, \n" " vain HTTP URL-osoitteet ovat kelvollisia.\n" " " #: plugin.py:226 msgid "That URL has no specified doctype." msgstr "Tuo URL-osoite ei ole määrittänyt doctypeä." #: plugin.py:233 msgid "" "\n" "\n" " Returns the Content-Length header of . Only HTTP urls are " "valid,\n" " of course.\n" " " msgstr "" "\n" "\n" " Palauttaa -osoitteen sisällön pituus otsikon. Tietysti, \n" " vain HTTP URL-osoitteet ovat kelvollisia.\n" " " #: plugin.py:245 plugin.py:250 msgid "%u is %S long." msgstr "%u on %S pitkä." #: plugin.py:252 msgid "The server didn't tell me how long %u is but it's longer than %S." msgstr "" "Palvelin ei kertonut minulle, kuinka pitkä %u on, mutta se on pidempi kuin " "%S." #: plugin.py:263 msgid "" "[--no-filter] \n" "\n" " Returns the HTML ... of a URL.\n" " If --no-filter is given, the bot won't strip special chars (action,\n" " DCC, ...).\n" " " msgstr "" "[--no-filter] \n" "\n" " Palauttaa ... URL-osoitteen titletageista.\n" " Jos--no-filter annetaan, erikoismerkkejä (action,\n" " DCC, ...) ei riisuta.\n" " " #: plugin.py:281 #, fuzzy msgid "Could not guess the page's encoding. (Try installing python-charade.)" msgstr "" "Sivun merkistökoodausta ei pystytty arvaamaan. (Kokeile python-charade:n " "asentamista.)" #: plugin.py:295 msgid "That URL appears to have no HTML title." msgstr "Tuolla URL-osoitteella ei vaikuta olevan HTTP otsikkoa." #: plugin.py:297 msgid "That URL appears to have no HTML title within the first %S." msgstr "" "Tuolla URL-osoitteella ei vaikuta olevan HTML otsikkoa ensinmäisissä %S." #: plugin.py:303 msgid "" "\n" "\n" " Returns the URL quoted form of the text.\n" " " msgstr "" "\n" "\n" " Palauttaa URL lainatun muodon tekstistä.\n" " " #: plugin.py:312 msgid "" "\n" "\n" " Returns the text un-URL quoted.\n" " " msgstr "" "\n" "\n" " Palauttaa tekstin URL lainaamattomassa muodossa.\n" " " #: plugin.py:324 msgid "" "\n" "\n" " Returns the contents of , or as much as is configured in\n" " supybot.plugins.Web.fetch.maximum. If that configuration variable " "is\n" " set to 0, this command will be effectively disabled.\n" " " msgstr "" "\n" "\n" " Palauttaa sisällön, tai niin paljon kuin on " "määritetty asetuksessa \n" " supybot.plugins.Web.fetch.maximum. Jos tuo asetusarvo on asetettu " "arvoon 0, \n" " tämä komento poistetaan käytöstä.\n" " " #: plugin.py:335 msgid "" "This command is disabled (supybot.plugins.Web.fetch.maximum is set to 0)." msgstr "" "Tämä komento on poistettu käytöstä (supybot.plugins.Web.fetch.maximum on " "asetettu arvoon 0)." #~ msgid "Page is too big." #~ msgstr "Sivu on liian suuri." #~ msgid "" #~ "\n" #~ "\n" #~ " Returns the HTML ... of a URL.\n" #~ " " #~ msgstr "" #~ "\n" #~ "\n" #~ " Palauttaa tiedon ... URL-soitteesta.\n" #~ " " #~ msgid "" #~ "\n" #~ "\n" #~ " Returns Netcraft.com's determination of what operating system " #~ "and\n" #~ " webserver is running on the host given.\n" #~ " " #~ msgstr "" #~ "\n" #~ "\n" #~ " Palauttaa Netcraft.comin määritelmän mitä käyttöjärjestelmää ja\n" #~ " verkkopalvelinta annettu isäntä käyttää.\n" #~ " " #~ msgid "No results found for %s." #~ msgstr "Tuloksia ei löytynyt kohteelle %s." #~ msgid "The format of page the was odd." #~ msgstr "Sivun muoto oli omituinen." limnoria-2020.03.17/plugins/Web/locales/fr.po0000644000175000017500000001363713634634532020120 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2014-01-22 13:46+CET\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: Limnoria \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Poedit-SourceCharset: ASCII\n" "X-Generator: Poedit 1.5.4\n" "Language: fr\n" #: config.py:50 msgid "" "Determines whether the bot will output the\n" " HTML title of URLs it sees in the channel." msgstr "" "Détermine si le bot affichera le titre HTML des URLs qu'il voit sur le canal." #: config.py:53 msgid "" "Determines whether the bot will notfiy the user\n" " about network exceptions like hostnotfound, timeout ...." msgstr "" "Détermine si le bot notifiera les utilisateurs à propos d’exceptions liées " "au réseau, comme hostnotfound, timeout, …" #: config.py:56 msgid "" "Determines whether the domain name displayed\n" " by the snarfer will be the original one (posted on IRC) or the target " "one\n" " (got after following redirects, if any)." msgstr "" "Détermine si le nom de domaine affiché par le snarfer est l’original (posté " "sur IRC) ou celui de la cible (après avoir suivi les redirections, s’il y en " "a)." #: config.py:60 msgid "" "Determines what URLs matching the given regexp\n" " will not be snarfed. Give the empty string if you have no URLs that " "you'd\n" " like to exclude from being snarfed." msgstr "" "Détermine quelles URLs ne seront pas écoutées. Donnez une chaîne vide si " "vous ne voulez ignorer aucune URL." #: config.py:72 msgid "" "Determines the maximum number of\n" " bytes the bot will download via the 'fetch' command in this plugin." msgstr "" "Détermine le nombre maximum d'octet que le bot téléchargera via la commande " "'fetch' de ce plugin." #: plugin.py:86 msgid "" "Runs a command in a forked process with limited memory resources\n" " to prevent memory bomb caused by specially crafted http responses." msgstr "." #: plugin.py:98 msgid "Page is too big." msgstr "La page est trop grosse." #: plugin.py:106 msgid "Display a nice error instead of \"An error has occurred\"." msgstr "." #: plugin.py:116 msgid "Add the help for \"help Web\" here." msgstr "" #: plugin.py:162 msgid "Title: %s (at %s)" msgstr "Titre : %s (de %s)" #: plugin.py:184 msgid "" "\n" "\n" " Returns the HTTP headers of . Only HTTP urls are valid, of\n" " course.\n" " " msgstr "" "\n" "\n" "Retourne les en-têtes HTTP de l'. Seules les URLs HTTP sont valides, " "bien sûr." #: plugin.py:194 msgid "%s: %s" msgstr "%s : %s" #: plugin.py:206 msgid "" "\n" "\n" " Returns the DOCTYPE string of . Only HTTP urls are valid, of\n" " course.\n" " " msgstr "" "\n" "\n" "Retourne le DOCTYPE de l'. Seules les URLs HTTP sont valides, bien sûr." #: plugin.py:222 msgid "That URL has no specified doctype." msgstr "Cette URL n'a pas de doctype spécifié." #: plugin.py:229 msgid "" "\n" "\n" " Returns the Content-Length header of . Only HTTP urls are " "valid,\n" " of course.\n" " " msgstr "" "\n" "\n" "Retourne le'en-têtes HTTP Content-Length de l'. Seules les URLs HTTP " "sont valides, bien sûr." #: plugin.py:241 plugin.py:246 msgid "%u is %S long." msgstr "%u est long de %S." #: plugin.py:248 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:259 msgid "" "[--no-filter] \n" "\n" " Returns the HTML ... of a URL.\n" " If --no-filter is given, the bot won't strip special chars (action,\n" " DCC, ...).\n" " " msgstr "" "[--no-filter] Retourne le titre HTML ... d'une adresse. " "Si --no-filter est donné, le bot ne supprimera pas les caractères spéciaux " "(action, DCC, ...)" #: plugin.py:288 msgid "That URL appears to have no HTML title." msgstr "Cette URL semble ne pas avoir de titre HTML." #: plugin.py:290 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:296 msgid "" "\n" "\n" " Returns the URL quoted form of the text.\n" " " msgstr "" "\n" "\n" "Retourne la forme formattée pour URLs du texte." #: plugin.py:305 msgid "" "\n" "\n" " Returns the text un-URL quoted.\n" " " msgstr "" "\n" "\n" "Retourne la forme dé-formattée pour URLs du texte." #: plugin.py:317 msgid "" "\n" "\n" " Returns the contents of , or as much as is configured in\n" " supybot.plugins.Web.fetch.maximum. If that configuration variable " "is\n" " set to 0, this command will be effectively disabled.\n" " " msgstr "" "\n" "\n" "Retourne le contenu de l', ou les supybot.plugins.Web.fetch.maximum " "premiers octets. Si la variable de configution est définie à 0, elle sera " "effectivement désactivée." #: plugin.py:328 msgid "" "This command is disabled (supybot.plugins.Web.fetch.maximum is set to 0)." msgstr "" "Cette commande est désactivée (supybot.plugins.Web.fetch.maximum vaut 0)." #~ msgid "" #~ "\n" #~ "\n" #~ " Returns the HTML ... of a URL.\n" #~ " " #~ msgstr "" #~ "\n" #~ "\n" #~ "Retourne le titre HTTP de l'." #~ msgid "" #~ "\n" #~ "\n" #~ " Returns Netcraft.com's determination of what operating system " #~ "and\n" #~ " webserver is running on the host given.\n" #~ " " #~ msgstr "" #~ "Retourne ce que Netcraft.com dit du système d'exploitation " #~ "et du serveur web utilisés par l'hôte." #~ msgid "No results found for %s." #~ msgstr "Pas de résultat trouvé pour %s." #~ msgid "The format of page the was odd." #~ msgstr "Le format de la page est bizarre." limnoria-2020.03.17/plugins/Web/locales/it.po0000644000175000017500000001141213634634532020112 0ustar valval00000000000000msgid "" msgstr "" "Project-Id-Version: Limnoria\n" "POT-Creation-Date: 2011-02-26 09:49+CET\n" "PO-Revision-Date: 2011-01-28 20:03+0100\n" "Last-Translator: skizzhg \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: config.py:50 msgid "" "Determines whether the bot will output the\n" " HTML title of URLs it sees in the channel." msgstr "" "Determina se il bot mostrerà il titolo HTML degli URL che vede in canale." #: config.py:53 msgid "" "Determines what URLs matching the given regexp\n" " will not be snarfed. Give the empty string if you have no URLs that you'd\n" " like to exclude from being snarfed." msgstr "" "Determina quali URL corrispondenti alla regexp fornita non verranno intercettati.\n" " Se non si vuole escludere alcun URL, fornire una stringa vuota.\n" #: config.py:60 msgid "" "Determines the maximum number of\n" " bytes the bot will download via the 'fetch' command in this plugin." msgstr "" "Determina il numero massimo di byte che il bot scaricherà tramite il comando \"fetch\" di questo plugin." #: plugin.py:71 #, docstring msgid "Add the help for \"help Web\" here." msgstr "" #: plugin.py:107 msgid "Title: %s (at %s)" msgstr "Titolo: %s (su %s)" #: plugin.py:114 #, docstring msgid "" "\n" "\n" " Returns the HTTP headers of . Only HTTP urls are valid, of\n" " course.\n" " " msgstr "" "\n" "\n" " Restituisce gli header HTTP di . Naturalmente sono validi solo ULR HTTP.\n" " " #: plugin.py:121 msgid "%s: %s" msgstr "%s: %s" #: plugin.py:131 #, docstring msgid "" "\n" "\n" " Returns the DOCTYPE string of . Only HTTP urls are valid, of\n" " course.\n" " " msgstr "" "\n" "\n" " Restituisce la stringa DOCTYPE di . Naturalmente sono validi solo URL HTTP.\n" " " #: plugin.py:143 msgid "That URL has no specified doctype." msgstr "Questo URL non ha un doctype specificato." #: plugin.py:148 #, docstring msgid "" "\n" "\n" " Returns the Content-Length header of . Only HTTP urls are valid,\n" " of course.\n" " " msgstr "" "\n" "\n" " Restituisce l'header Content-Length di . Naturalmente sono validi solo ULR HTTP.\n" " " #: plugin.py:157 plugin.py:162 msgid "%u is %S long." msgstr "%u è lungo %S." #: plugin.py:164 msgid "The server didn't tell me how long %u is but it's longer than %S." msgstr "Il server non mi ha detto quanto sia lungo %u ma è più di %S." #: plugin.py:173 #, docstring msgid "" "\n" "\n" " Returns the HTML ... of a URL.\n" " " msgstr "" "\n" "\n" " Restituisce il tag HTML ... di un URL.\n" " " #: plugin.py:188 msgid "That URL appears to have no HTML title." msgstr "Questo URL sembra non avere un titolo HTML." #: plugin.py:190 msgid "That URL appears to have no HTML title within the first %S." msgstr "Sembra che questo URL non abbia un titolo HTML entro i primi %S." #: plugin.py:198 #, docstring msgid "" "\n" "\n" " Returns Netcraft.com's determination of what operating system and\n" " webserver is running on the host given.\n" " " msgstr "" "\n" "\n" " Riporta la stima di Netcraft.com riguardo a quale sistema\n" " operativo e server web girano sull'host richiesto.\n" " " #: plugin.py:212 msgid "No results found for %s." msgstr "Nessun risultato trovato per %s." #: plugin.py:214 msgid "The format of page the was odd." msgstr "Il formato della pagina è strano." #: plugin.py:219 #, docstring msgid "" "\n" "\n" " Returns the URL quoted form of the text.\n" " " msgstr "" "\n" "\n" " Codifica il testo in base alla codifica URL.\n" " " #: plugin.py:228 #, docstring msgid "" "\n" "\n" " Returns the text un-URL quoted.\n" " " msgstr "" "\n" "\n" " Decodifica il testo codificato secondo la codifica URL.\n" " " #: plugin.py:238 #, docstring msgid "" "\n" "\n" " Returns the contents of , or as much as is configured in\n" " supybot.plugins.Web.fetch.maximum. If that configuration variable is\n" " set to 0, this command will be effectively disabled.\n" " " msgstr "" "\n" "\n" " Riporta il contenuto di , o tanti byte quanti sono definiti in\n" " supybot.plugins.Web.fetch.maximum. Se questa variabile è impostata a 0,\n" " il comando sarà disabilitato.\n" " " #: plugin.py:246 msgid "This command is disabled (supybot.plugins.Web.fetch.maximum is set to 0)." msgstr "Questo comando è disabilitato (supybot.plugins.Web.fetch.maximum è impostata a 0)." limnoria-2020.03.17/plugins/Web/plugin.py0000644000175000017500000003501113634634532017365 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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 re import sys import string import socket import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.utils.minisix as minisix import supybot.plugins as plugins import supybot.commands as commands import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization('Web') if minisix.PY3: from html.parser import HTMLParser from html.entities import entitydefs import http.client as http_client else: from HTMLParser import HTMLParser from htmlentitydefs import entitydefs import httplib as http_client class Title(utils.web.HtmlToText): entitydefs = entitydefs.copy() entitydefs['nbsp'] = ' ' def __init__(self): self.inTitle = False self.inSvg = False utils.web.HtmlToText.__init__(self) @property def inHtmlTitle(self): return self.inTitle and not self.inSvg def handle_starttag(self, tag, attrs): if tag == 'title': self.inTitle = True elif tag == 'svg': self.inSvg = True def handle_endtag(self, tag): if tag == 'title': self.inTitle = False elif tag == 'svg': self.inSvg = False def append(self, data): if self.inHtmlTitle: super(Title, self).append(data) class DelayedIrc: def __init__(self, irc): self._irc = irc self._replies = [] def reply(self, *args, **kwargs): self._replies.append(('reply', args, kwargs)) def error(self, *args, **kwargs): self._replies.append(('error', args, kwargs)) def __getattr__(self, name): assert name not in ('reply', 'error', '_irc', '_msg', '_replies') return getattr(self._irc, name) if hasattr(http_client, '_MAXHEADERS'): def fetch_sandbox(f): """Runs a command in a forked process with limited memory resources to prevent memory bomb caused by specially crafted http responses. On CPython versions with support for limiting the number of headers, this is the identity function""" return f else: # For the following CPython versions (as well as the matching Pypy # versions): # * 2.6 before 2.6.9 # * 2.7 before 2.7.9 # * 3.2 before 3.2.6 # * 3.3 before 3.3.3 def fetch_sandbox(f): """Runs a command in a forked process with limited memory resources to prevent memory bomb caused by specially crafted http responses.""" def process(self, irc, msg, *args, **kwargs): delayed_irc = DelayedIrc(irc) f(self, delayed_irc, msg, *args, **kwargs) return delayed_irc._replies def newf(self, irc, *args): try: replies = commands.process(process, self, irc, *args, timeout=10, heap_size=10*1024*1024, pn=self.name(), cn=f.__name__) except (commands.ProcessTimeoutError, MemoryError): raise utils.web.Error(_('Page is too big or the server took ' 'too much time to answer the request.')) else: for (method, args, kwargs) in replies: getattr(irc, method)(*args, **kwargs) newf.__doc__ = f.__doc__ return newf def catch_web_errors(f): """Display a nice error instead of "An error has occurred".""" def newf(self, irc, *args, **kwargs): try: f(self, irc, *args, **kwargs) except utils.web.Error as e: irc.reply(str(e)) return utils.python.changeFunctionName(newf, f.__name__, f.__doc__) class Web(callbacks.PluginRegexp): """Add the help for 'help Web' here.""" regexps = ['titleSnarfer'] threaded = True def noIgnore(self, irc, msg): return not self.registryValue('checkIgnored', msg.channel, irc.network) def getTitle(self, irc, url, raiseErrors, msg): size = conf.supybot.protocols.http.peekSize() timeout = self.registryValue('timeout') headers = conf.defaultHttpHeaders(irc.network, msg.channel) try: (target, text) = utils.web.getUrlTargetAndContent(url, size=size, timeout=timeout, headers=headers) except Exception as e: if raiseErrors: irc.error(_('That URL raised <' + str(e)) + '>', Raise=True) else: self.log.info('Web plugin TitleSnarfer: URL <%s> raised <%s>', url, str(e)) return try: text = text.decode(utils.web.getEncoding(text) or 'utf8', 'replace') except UnicodeDecodeError: if minisix.PY3: if raiseErrors: irc.error(_('Could not guess the page\'s encoding. (Try ' 'installing python-charade.)'), Raise=True) else: self.log.info('Web plugin TitleSnarfer: URL <%s> Could ' 'not guess the page\'s encoding. (Try ' 'installing python-charade.)', url) return try: parser = Title() parser.feed(text) except UnicodeDecodeError: # Workaround for Python 2 # https://github.com/ProgVal/Limnoria/issues/1359 parser = Title() parser.feed(text.encode('utf8')) parser.close() title = utils.str.normalizeWhitespace(''.join(parser.data).strip()) if title: return (target, title) elif raiseErrors: if len(text) < size: irc.error(_('That URL appears to have no HTML title.'), Raise=True) else: irc.error(format(_('That URL appears to have no HTML title ' 'within the first %S.'), size), Raise=True) else: if len(text) < size: self.log.debug('Web plugin TitleSnarfer: URL <%s> appears ' 'to have no HTML title. ', url) else: self.log.debug('Web plugin TitleSnarfer: URL <%s> appears ' 'to have no HTML title within the first %S.', url, size) @fetch_sandbox def titleSnarfer(self, irc, msg, match): channel = msg.channel network = irc.network if not channel: return if callbacks.addressed(irc.nick, msg): return if self.registryValue('titleSnarfer', channel, network): url = match.group(0) if not self._checkURLWhitelist(url): return r = self.registryValue('nonSnarfingRegexp', channel, network) if r and r.search(url): self.log.debug('Not titleSnarfing %q.', url) return r = self.getTitle(irc, url, False, msg) if not r: return (target, title) = r if title: domain = utils.web.getDomain(target if self.registryValue('snarferShowTargetDomain', channel, network) else url) prefix = self.registryValue('snarferPrefix', channel, network) if prefix: s = "%s %s" % (prefix, title) else: s = title if self.registryValue('snarferShowDomain', channel, network): s += format(_(' (at %s)'), domain) irc.reply(s, prefixNick=False) if self.registryValue('snarfMultipleUrls', channel, network): # FIXME: hack msg.tag('repliedTo', False) titleSnarfer = urlSnarfer(titleSnarfer) titleSnarfer.__doc__ = utils.web._httpUrlRe def _checkURLWhitelist(self, url): if not self.registryValue('urlWhitelist'): return True passed = False for wu in self.registryValue('urlWhitelist'): if wu.endswith('/') and url.find(wu) == 0: passed = True break if (not wu.endswith('/')) and (url.find(wu + '/') == 0 or url == wu): passed = True break return passed @wrap(['httpUrl']) @catch_web_errors @fetch_sandbox def headers(self, irc, msg, args, url): """ Returns the HTTP headers of . Only HTTP urls are valid, of course. """ if not self._checkURLWhitelist(url): irc.error("This url is not on the whitelist.") return timeout = self.registryValue('timeout') fd = utils.web.getUrlFd(url, timeout=timeout) try: s = ', '.join([format(_('%s: %s'), k, v) for (k, v) in fd.headers.items()]) irc.reply(s) finally: fd.close() _doctypeRe = re.compile(r'(]+>)', re.M) @wrap(['httpUrl']) @catch_web_errors @fetch_sandbox def doctype(self, irc, msg, args, url): """ Returns the DOCTYPE string of . Only HTTP urls are valid, of course. """ if not self._checkURLWhitelist(url): irc.error("This url is not on the whitelist.") return size = conf.supybot.protocols.http.peekSize() timeout = self.registryValue('timeout') s = utils.web.getUrl(url, size=size, timeout=timeout).decode('utf8') m = self._doctypeRe.search(s) if m: s = utils.str.normalizeWhitespace(m.group(0)) irc.reply(s) else: irc.reply(_('That URL has no specified doctype.')) @wrap(['httpUrl']) @catch_web_errors @fetch_sandbox def size(self, irc, msg, args, url): """ Returns the Content-Length header of . Only HTTP urls are valid, of course. """ if not self._checkURLWhitelist(url): irc.error("This url is not on the whitelist.") return timeout = self.registryValue('timeout') fd = utils.web.getUrlFd(url, timeout=timeout) try: try: size = fd.headers['Content-Length'] if size is None: raise KeyError('content-length') irc.reply(format(_('%u is %S long.'), url, int(size))) except KeyError: size = conf.supybot.protocols.http.peekSize() s = fd.read(size) if len(s) != size: irc.reply(format(_('%u is %S long.'), url, len(s))) else: irc.reply(format(_('The server didn\'t tell me how long %u ' 'is but it\'s longer than %S.'), url, size)) finally: fd.close() @wrap([getopts({'no-filter': ''}), 'httpUrl']) @catch_web_errors @fetch_sandbox def title(self, irc, msg, args, optlist, url): """[--no-filter] Returns the HTML ... of a URL. If --no-filter is given, the bot won't strip special chars (action, DCC, ...). """ if not self._checkURLWhitelist(url): irc.error("This url is not on the whitelist.") return r = self.getTitle(irc, url, True, msg) if not r: return (target, title) = r if title: if not [y for x,y in optlist if x == 'no-filter']: for i in range(1, 4): title = title.replace(chr(i), '') irc.reply(title) @wrap(['text']) def urlquote(self, irc, msg, args, text): """ Returns the URL quoted form of the text. """ irc.reply(utils.web.urlquote(text)) @wrap(['text']) def urlunquote(self, irc, msg, args, text): """ Returns the text un-URL quoted. """ s = utils.web.urlunquote(text) irc.reply(s) @wrap(['url']) @catch_web_errors @fetch_sandbox def fetch(self, irc, msg, args, url): """ Returns the contents of , or as much as is configured in supybot.plugins.Web.fetch.maximum. If that configuration variable is set to 0, this command will be effectively disabled. """ if not self._checkURLWhitelist(url): irc.error("This url is not on the whitelist.") return max = self.registryValue('fetch.maximum') timeout = self.registryValue('fetch.timeout') if not max: irc.error(_('This command is disabled ' '(supybot.plugins.Web.fetch.maximum is set to 0).'), Raise=True) fd = utils.web.getUrl(url, size=max, timeout=timeout).decode('utf8') irc.reply(fd) Class = Web # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/Web/test.py0000644000175000017500000002070613634634532017053 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * class WebTestCase(ChannelPluginTestCase): plugins = ('Web', 'Admin',) timeout = 10 if network: def testHeaders(self): self.assertError('headers ftp://ftp.cdrom.com/pub/linux') self.assertNotError('headers http://www.slashdot.org/') def testDoctype(self): self.assertError('doctype ftp://ftp.cdrom.com/pub/linux') self.assertNotError('doctype http://www.slashdot.org/') m = self.getMsg('doctype http://moobot.sf.net/') self.assertTrue(m.args[1].endswith('>')) def testSize(self): self.assertError('size ftp://ftp.cdrom.com/pub/linux') self.assertNotError('size http://supybot.sf.net/') self.assertNotError('size http://www.slashdot.org/') def testTitle(self): # Checks for @title not-working correctly self.assertResponse('title ' 'http://www.catb.org/~esr/jargon/html/F/foo.html', 'foo') # Checks for only grabbing the real title tags instead of title # tags inside, for example, script tags. Bug #1190350 self.assertNotRegexp('title ' 'http://www.irinnews.org/report.asp?ReportID=45910&' 'SelectRegion=West_Africa&SelectCountry=CHAD', r'document\.write\(') # Checks that title parser grabs the full title instead of just # part of it. self.assertRegexp('title http://www.n-e-r-d.com/', 'N.*E.*R.*D') # Checks that the parser doesn't hang on invalid tags self.assertNotError( 'title http://www.youtube.com/watch?v=x4BtiqPN4u8') self.assertResponse( 'title http://www.thefreedictionary.com/don%27t', "Don't - definition of don't by The Free Dictionary") self.assertRegexp( 'title ' 'https://twitter.com/rlbarnes/status/656554266744586240', '"PSA: In Firefox 44 Nightly, "http:" pages with ' ' are now marked insecure. ' 'https://t.co/qS9LxuRPdm"$') def testTitleSnarfer(self): try: conf.supybot.plugins.Web.titleSnarfer.setValue(True) self.assertSnarfRegexp('http://microsoft.com/', 'Microsoft') finally: conf.supybot.plugins.Web.titleSnarfer.setValue(False) def testMultipleTitleSnarfer(self): try: conf.supybot.plugins.Web.titleSnarfer.setValue(True) conf.supybot.plugins.Web.snarfMultipleUrls.setValue(True) self.feedMsg( 'https://microsoft.com/ https://google.com/') m1 = self.getMsg(' ') m2 = self.getMsg(' ') self.assertTrue(('Microsoft' in m1.args[1]) ^ ('Microsoft' in m2.args[1])) self.assertTrue(('Google' in m1.args[1]) ^ ('Google' in m2.args[1])) finally: conf.supybot.plugins.Web.titleSnarfer.setValue(False) conf.supybot.plugins.Web.snarfMultipleUrls.setValue(False) def testNonSnarfing(self): snarf = conf.supybot.plugins.Web.nonSnarfingRegexp() title = conf.supybot.plugins.Web.titleSnarfer() try: conf.supybot.plugins.Web.nonSnarfingRegexp.set('m/fr/') try: conf.supybot.plugins.Web.titleSnarfer.setValue(True) self.assertSnarfNoResponse('https://www.google.fr/', 2) self.assertSnarfRegexp('https://www.google.com/', r'Google') finally: conf.supybot.plugins.Web.titleSnarfer.setValue(title) finally: conf.supybot.plugins.Web.nonSnarfingRegexp.setValue(snarf) def testSnarferIgnore(self): conf.supybot.plugins.Web.titleSnarfer.setValue(True) (oldprefix, self.prefix) = (self.prefix, 'foo!bar@baz') try: self.assertSnarfRegexp('http://google.com/', 'Google') self.assertNotError('admin ignore add %s' % self.prefix) self.assertSnarfNoResponse('http://google.com/') self.assertNoResponse('title http://www.google.com/') finally: conf.supybot.plugins.Web.titleSnarfer.setValue(False) (self.prefix, oldprefix) = (oldprefix, self.prefix) self.assertNotError('admin ignore remove %s' % oldprefix) def testSnarferNotIgnore(self): conf.supybot.plugins.Web.titleSnarfer.setValue(True) conf.supybot.plugins.Web.checkIgnored.setValue(False) (oldprefix, self.prefix) = (self.prefix, 'foo!bar@baz') try: self.assertSnarfRegexp('https://google.it/', 'Google') self.assertNotError('admin ignore add %s' % self.prefix) self.assertSnarfRegexp('https://www.google.it/', 'Google') self.assertNoResponse('title http://www.google.it/') finally: conf.supybot.plugins.Web.titleSnarfer.setValue(False) conf.supybot.plugins.Web.checkIgnored.setValue(True) (self.prefix, oldprefix) = (oldprefix, self.prefix) self.assertNotError('admin ignore remove %s' % oldprefix) def testWhitelist(self): fm = conf.supybot.plugins.Web.fetch.maximum() uw = conf.supybot.plugins.Web.urlWhitelist() try: conf.supybot.plugins.Web.fetch.maximum.set(1024) self.assertNotError('web fetch http://fsf.org') conf.supybot.plugins.Web.urlWhitelist.set('http://slashdot.org') self.assertError('web fetch http://fsf.org') self.assertError('wef title http://fsf.org') self.assertError('web fetch http://slashdot.org.evildomain.com') self.assertNotError('web fetch http://slashdot.org') self.assertNotError('web fetch http://slashdot.org/recent') conf.supybot.plugins.Web.urlWhitelist.set('http://slashdot.org http://fsf.org') self.assertNotError('doctype http://fsf.org') finally: conf.supybot.plugins.Web.urlWhitelist.set('') conf.supybot.plugins.Web.fetch.maximum.set(fm) def testNonSnarfingRegexpConfigurable(self): self.assertSnarfNoResponse('http://foo.bar.baz/', 2) try: conf.supybot.plugins.Web.nonSnarfingRegexp.set('m/biff/') self.assertSnarfNoResponse('http://biff.bar.baz/', 2) finally: conf.supybot.plugins.Web.nonSnarfingRegexp.set('') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/plugins/__init__.py0000644000175000017500000005450013634634532017115 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2008, James McCoy # 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 gc import os import csv import time import codecs import fnmatch import os.path import threading import collections.abc from .. import callbacks, conf, dbi, ircdb, ircutils, log, utils, world from ..commands import * class NoSuitableDatabase(Exception): def __init__(self, suitable): self.suitable = list(suitable) self.suitable.sort() def __str__(self): return format('No suitable databases were found. Suitable databases ' 'include %L. If you have one of these databases ' 'installed, make sure it is listed in the ' 'supybot.databases configuration variable.', self.suitable) def DB(filename, types): # We don't care if any of the DBs are actually available when # documenting, so just fake that we found something suitable if world.documenting: def junk(*args, **kwargs): pass return junk def MakeDB(*args, **kwargs): for type in conf.supybot.databases(): # Can't do this because Python sucks. Go ahead, try it! # filename = '.'.join([filename, type, 'db']) fn = '.'.join([filename, type, 'db']) fn = utils.file.sanitizeName(fn) path = conf.supybot.directories.data.dirize(fn) try: return types[type](path, *args, **kwargs) except KeyError: continue raise NoSuitableDatabase(types.keys()) return MakeDB def makeChannelFilename(filename, channel=None, dirname=None): assert channel is not None, 'Channel should not be None' filename = os.path.basename(filename) channelSpecific = conf.supybot.databases.plugins.channelSpecific channel = channelSpecific.getChannelLink(channel) channel = utils.file.sanitizeName(ircutils.toLower(channel)) if dirname is None: dirname = conf.supybot.directories.data.dirize(channel) if not os.path.exists(dirname): os.makedirs(dirname) return os.path.join(dirname, filename) def getChannel(channel): assert channel is not None, 'Channel should not be None' channelSpecific = conf.supybot.databases.plugins.channelSpecific return channelSpecific.getChannelLink(channel) # XXX This shouldn't be a mixin. This should be contained by classes that # want such behavior. But at this point, it wouldn't gain much for us # to refactor it. # XXX We need to get rid of this, it's ugly and opposed to # database-independence. class ChannelDBHandler(object): """A class to handle database stuff for individual channels transparently. """ suffix = '.db' def __init__(self, suffix='.db'): self.dbCache = ircutils.IrcDict() suffix = self.suffix if self.suffix and self.suffix[0] != '.': suffix = '.' + suffix self.suffix = suffix def makeFilename(self, channel): """Override this to specialize the filenames of your databases.""" channel = ircutils.toLower(channel) className = self.__class__.__name__ return makeChannelFilename(className + self.suffix, channel) def makeDb(self, filename): """Override this to create your databases.""" raise NotImplementedError 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 die(self): for db in self.dbCache.values(): try: db.commit() except AttributeError: # In case it's not an SQLite database. pass try: db.close() except AttributeError: # In case it doesn't have a close method. pass del db gc.collect() class DbiChannelDB(object): """This just handles some of the general stuff for Channel DBI databases. Check out ChannelIdDatabasePlugin for an example of how to use this.""" def __init__(self, filename): self.filename = filename self.dbs = ircutils.IrcDict() def _getDb(self, channel): filename = makeChannelFilename(self.filename, channel) try: db = self.dbs[channel] except KeyError: db = self.DB(filename) self.dbs[channel] = db return db def close(self): for db in self.dbs.values(): db.close() def flush(self): for db in self.dbs.values(): db.flush() def __getattr__(self, attr): def _getDbAndDispatcher(channel, *args, **kwargs): db = self._getDb(channel) return getattr(db, attr)(*args, **kwargs) return _getDbAndDispatcher class ChannelUserDictionary(collections.abc.MutableMapping): IdDict = dict def __init__(self): self.channels = ircutils.IrcDict() def __getitem__(self, key): (channel, id) = key return self.channels[channel][id] def __setitem__(self, key, v): (channel, id) = key if channel not in self.channels: self.channels[channel] = self.IdDict() self.channels[channel][id] = v def __delitem__(self, key): (channel, id) = key del self.channels[channel][id] def __iter__(self): for channel, ids in self.channels.items(): for id_, value in ids.items(): yield (channel, id_) def __len__(self): return sum([len(x) for x in self.channels]) def items(self): for (channel, ids) in self.channels.items(): for (id, v) in ids.items(): yield ((channel, id), v) def keys(self): L = [] for (k, _) in self.items(): L.append(k) return L # XXX The interface to this needs to be made *much* more like the dbi.DB # interface. This is just too odd and not extensible; any extension # would very much feel like an extension, rather than part of the db # itself. class ChannelUserDB(ChannelUserDictionary): def __init__(self, filename): ChannelUserDictionary.__init__(self) self.filename = filename try: fd = codecs.open(self.filename, encoding='utf8') except EnvironmentError as e: log.warning('Couldn\'t open %s: %s.', self.filename, e) return reader = csv.reader(fd) try: lineno = 0 for t in reader: lineno += 1 try: channel = t.pop(0) id = t.pop(0) try: id = int(id) except ValueError: # We'll skip over this so, say, nicks can be kept here. pass v = self.deserialize(channel, id, t) self[channel, id] = v except Exception as e: log.warning('Invalid line #%s in %s.', lineno, self.__class__.__name__) log.debug('Exception: %s', utils.exnToString(e)) except Exception as e: # This catches exceptions from csv.reader. log.warning('Invalid line #%s in %s.', lineno, self.__class__.__name__) log.debug('Exception: %s', utils.exnToString(e)) def flush(self): mode = 'wb' if utils.minisix.PY2 else 'w' fd = utils.file.AtomicFile(self.filename, mode, makeBackupIfSmaller=False) writer = csv.writer(fd) items = list(self.items()) if not items: log.debug('%s: Refusing to write blank file.', self.__class__.__name__) fd.rollback() return try: items.sort() except TypeError: # FIXME: Implement an algorithm that can order dictionnaries # with both strings and integers as keys. pass for ((channel, id), v) in items: L = self.serialize(v) L.insert(0, id) L.insert(0, channel) writer.writerow(L) fd.close() def close(self): self.flush() self.clear() def deserialize(self, channel, id, L): """Should take a list of strings and return an object to be accessed via self.get(channel, id).""" raise NotImplementedError def serialize(self, x): """Should take an object (as returned by self.get(channel, id)) and return a list (of any type serializable to csv).""" raise NotImplementedError def getUserName(id): if isinstance(id, int): try: return ircdb.users.getUser(id).name except KeyError: return 'a user that is no longer registered' else: return id class ChannelIdDatabasePlugin(callbacks.Plugin): class DB(DbiChannelDB): class DB(dbi.DB): class Record(dbi.Record): __fields__ = [ 'at', 'by', 'text' ] def add(self, at, by, text, **kwargs): record = self.Record(at=at, by=by, text=text, **kwargs) return super(self.__class__, self).add(record) def __init__(self, irc): self.__parent = super(ChannelIdDatabasePlugin, self) self.__parent.__init__(irc) self.db = DB(self.name(), {'flat': self.DB})() def die(self): self.db.close() self.__parent.die() def getCommandHelp(self, name, simpleSyntax=None): help = self.__parent.getCommandHelp(name, simpleSyntax) help = help.replace('$Types', format('%p', self.name())) help = help.replace('$Type', self.name()) help = help.replace('$types', format('%p', self.name().lower())) help = help.replace('$type', self.name().lower()) return help def noSuchRecord(self, irc, channel, id): irc.error('There is no %s with id #%s in my database for %s.' % (self.name(), id, channel)) def checkChangeAllowed(self, irc, msg, channel, user, record): # Checks and returns True if either the user ID (integer) # or the hostmask of the caller match. if (hasattr(user, 'id') and user.id == record.by) or user == record.by: return True cap = ircdb.makeChannelCapability(channel, 'op') if ircdb.checkCapability(msg.prefix, cap): return True irc.errorNoCapability(cap) def addValidator(self, irc, text): """This should irc.error or raise an exception if text is invalid.""" pass def getUserId(self, irc, prefix, channel=None): try: user = ircdb.users.getUser(prefix) return user.id except KeyError: if conf.get(conf.supybot.databases.plugins.requireRegistration, channel=channel, network=irc.network): irc.errorNotRegistered(Raise=True) return def add(self, irc, msg, args, channel, text): """[] Adds to the $type database for . is only necessary if the message isn't sent in the channel itself. """ user = self.getUserId(irc, msg.prefix, channel) or msg.prefix at = time.time() self.addValidator(irc, text) if text is not None: id = self.db.add(channel, at, user, text) irc.replySuccess('%s #%s added.' % (self.name(), id)) add = wrap(add, ['channeldb', 'text']) def remove(self, irc, msg, args, channel, id): """[] Removes the $type with id from the $type database for . is only necessary if the message isn't sent in the channel itself. """ user = self.getUserId(irc, msg.prefix, channel) or msg.prefix try: record = self.db.get(channel, id) self.checkChangeAllowed(irc, msg, channel, user, record) self.db.remove(channel, id) irc.replySuccess() except KeyError: self.noSuchRecord(irc, channel, id) remove = wrap(remove, ['channeldb', 'id']) def searchSerializeRecord(self, record): text = utils.str.ellipsisify(record.text, 50) return format('#%s: %q', record.id, text) def search(self, irc, msg, args, channel, optlist, glob): """[] [--{regexp,by} ] [] Searches for $types matching the criteria given. """ predicates = [] def p(record): for predicate in predicates: if not predicate(record): return False return True for (opt, arg) in optlist: if opt == 'by': predicates.append(lambda r, arg=arg: r.by == arg.id) elif opt == 'regexp': if not ircdb.checkCapability(msg.prefix, 'trusted'): # Limited --regexp to trusted users, because specially # crafted regexps can freeze the bot. See # https://github.com/ProgVal/Limnoria/issues/855 for details irc.errorNoCapability('trusted') predicates.append(lambda r: regexp_wrapper(r.text, reobj=arg, timeout=0.1, plugin_name=self.name(), fcn_name='search')) if glob: def globP(r, glob=glob.lower()): return fnmatch.fnmatch(r.text.lower(), glob) predicates.append(globP) L = [] for record in self.db.select(channel, p): L.append(self.searchSerializeRecord(record)) if L: L.sort() irc.reply(format('%s found: %L', len(L), L)) else: what = self.name().lower() irc.reply(format('No matching %p were found.', what)) search = wrap(search, ['channeldb', getopts({'by': 'otherUser', 'regexp': 'regexpMatcher'}), additional(rest('glob'))]) def showRecord(self, record): name = getUserName(record.by) return format('%s #%s: %q (added by %s at %t)', self.name(), record.id, record.text, name, record.at) def get(self, irc, msg, args, channel, id): """[] Gets the $type with id from the $type database for . is only necessary if the message isn't sent in the channel itself. """ try: record = self.db.get(channel, id) irc.reply(self.showRecord(record)) except KeyError: self.noSuchRecord(irc, channel, id) get = wrap(get, ['channeldb', 'id']) def change(self, irc, msg, args, channel, id, replacer): """[] Changes the $type with id according to the regular expression . is only necessary if the message isn't sent in the channel itself. """ user = self.getUserId(irc, msg.prefix, channel) or msg.prefix try: record = self.db.get(channel, id) self.checkChangeAllowed(irc, msg, channel, user, record) record.text = replacer(record.text) self.db.set(channel, id, record) irc.replySuccess() except KeyError: self.noSuchRecord(irc, channel, id) change = wrap(change, ['channeldb', 'id', 'regexpReplacer']) def stats(self, irc, msg, args, channel): """[] Returns the number of $types in the database for . is only necessary if the message isn't sent in the channel itself. """ n = self.db.size(channel) whats = self.name().lower() irc.reply(format('There %b %n in my database.', n, (n, whats))) stats = wrap(stats, ['channeldb']) class PeriodicFileDownloader(object): """A class to periodically download a file/files. A class-level dictionary 'periodicFiles' maps names of files to three-tuples of (url, seconds between downloads, function to run with downloaded file). 'url' should be in some form that urllib2.urlopen can handle (do note that urllib2.urlopen handles file:// links perfectly well.) 'seconds between downloads' is the number of seconds between downloads, obviously. An important point to remember, however, is that it is only engaged when a command is run. I.e., if you say you want the file downloaded every day, but no commands that use it are run in a week, the next time such a command is run, it'll be using a week-old file. If you don't want such behavior, you'll have to give an error mess age to the user and tell them to call you back in the morning. 'function to run with downloaded file' is a function that will be passed a string *filename* of the downloaded file. This will be some random filename probably generated via some mktemp-type-thing. You can do what you want with this; you may want to build a database, take some stats, or simply rename the file. You can pass None as your function and the file with automatically be renamed to match the filename you have it listed under. It'll be in conf.supybot.directories.data, of course. Aside from that dictionary, simply use self.getFile(filename) in any method that makes use of a periodically downloaded file, and you'll be set. """ periodicFiles = None def __init__(self): if self.periodicFiles is None: raise ValueError('You must provide files to download') self.lastDownloaded = {} self.downloadedCounter = {} for filename in self.periodicFiles: if self.periodicFiles[filename][-1] is None: fullname = os.path.join(conf.supybot.directories.data(), filename) if os.path.exists(fullname): self.lastDownloaded[filename] = os.stat(fullname).st_ctime else: self.lastDownloaded[filename] = 0 else: self.lastDownloaded[filename] = 0 self.currentlyDownloading = set() self.downloadedCounter[filename] = 0 self.getFile(filename) def _downloadFile(self, filename, url, f): self.currentlyDownloading.add(filename) try: try: infd = utils.web.getUrlFd(url) except IOError as e: self.log.warning('Error downloading %s: %s', url, e) return except utils.web.Error as e: self.log.warning('Error downloading %s: %s', url, e) return confDir = conf.supybot.directories.data() newFilename = os.path.join(confDir, utils.file.mktemp()) outfd = open(newFilename, 'wb') start = time.time() s = infd.read(4096) while s: outfd.write(s) s = infd.read(4096) infd.close() outfd.close() self.log.info('Downloaded %s in %s seconds', filename, time.time()-start) self.downloadedCounter[filename] += 1 self.lastDownloaded[filename] = time.time() if f is None: toFilename = os.path.join(confDir, filename) if os.name == 'nt': # Windows, grrr... if os.path.exists(toFilename): os.remove(toFilename) os.rename(newFilename, toFilename) else: start = time.time() f(newFilename) total = time.time() - start self.log.info('Function ran on %s in %s seconds', filename, total) finally: self.currentlyDownloading.remove(filename) def getFile(self, filename): if world.documenting: return (url, timeLimit, f) = self.periodicFiles[filename] if time.time() - self.lastDownloaded[filename] > timeLimit and \ filename not in self.currentlyDownloading: self.log.info('Beginning download of %s', url) args = (filename, url, f) name = '%s #%s' % (filename, self.downloadedCounter[filename]) t = threading.Thread(target=self._downloadFile, name=name, args=(filename, url, f)) t.setDaemon(True) t.start() world.threadsSpawned += 1 # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/requirements.txt0000644000175000017500000000013213634634532016577 0ustar valval00000000000000chardet pytz python-dateutil python-gnupg feedparser sqlalchemy PySocks mock cryptography limnoria-2020.03.17/scripts/0000755000175000017500000000000013634634547015014 5ustar valval00000000000000limnoria-2020.03.17/scripts/supybot0000644000175000017500000003710413634634532016443 0ustar valval00000000000000#!/usr/bin/env python3 ### # Copyright (c) 2003-2004, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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. ### """ This is the main program to run Supybot. """ import supybot import re import os import sys import atexit import shutil import signal if sys.version_info < (3, 4, 0): sys.stderr.write('This program requires Python 3.4 or later.') sys.stderr.write(os.linesep) sys.exit(-1) from io import StringIO # Import this after version check since this will fail on Python 2 def _termHandler(signalNumber, stackFrame): raise SystemExit('Signal #%s.' % signalNumber) signal.signal(signal.SIGTERM, _termHandler) import time import optparse import textwrap started = time.time() import supybot import supybot.utils as utils import supybot.registry as registry import supybot.questions as questions import supybot.ircutils as ircutils try: import supybot.i18n as i18n except ImportError: sys.stderr.write("""Error: You are running a mix of Limnoria and stock Supybot code. Although you run one of Limnoria\'s executables, Python tries to load stock Supybot\'s library. To fix this issue, uninstall Supybot ("%s -m pip uninstall supybot" should do the job) and install Limnoria again. For your information, Supybot's libraries are installed here: %s\n""" % (sys.executable, '\n '.join(supybot.__path__))) exit(-1) from supybot.version import version def main(): import supybot.conf as conf import supybot.world as world import supybot.drivers as drivers import supybot.schedule as schedule # We schedule this event rather than have it actually run because if there # is a failure between now and the time it takes the Owner plugin to load # all the various plugins, our registry file might be wiped. That's bad. interrupted = False when = conf.supybot.upkeepInterval() schedule.addPeriodicEvent(world.upkeep, when, name='upkeep', now=False) world.startedAt = started while world.ircs: try: drivers.run() except KeyboardInterrupt: if interrupted: # Interrupted while waiting for queues to clear. Let's clear # them ourselves. for irc in world.ircs: irc._reallyDie() continue else: interrupted = True log.info('Exiting due to Ctrl-C. ' 'If the bot doesn\'t exit within a few seconds, ' 'feel free to press Ctrl-C again to make it exit ' 'without flushing its message queues.') world.upkeep() for irc in world.ircs: quitmsg = conf.supybot.plugins.Owner.quitMsg() or \ 'Ctrl-C at console.' # Because we're quitting from the console, none of the # standard msg substitutions exist, and these will show as # raw strings by default. Substitute them here with # something meaningful instead. env = dict((key, '') for key in ('who', 'nick', 'user', 'host')) quitmsg = ircutils.standardSubstitute(irc, None, quitmsg, env=env) irc.queueMsg(ircmsgs.quit(quitmsg)) irc.die() except SystemExit as e: s = str(e) if s: log.info('Exiting due to %s', s) break except: try: # Ok, now we're *REALLY* paranoid! log.exception('Exception raised out of drivers.run:') except Exception as e: print('Exception raised in log.exception. This is *really*') print('bad. Hopefully it won\'t happen again, but tell us') print('about it anyway, this is a significant problem.') print('Anyway, here\'s the exception: %s' % \ utils.gen.exnToString(e)) except: print('Oh, this really sucks. Not only did log.exception') print('raise an exception, but freaking-a, it was a string') print('exception. People who raise string exceptions should') print('die a slow, painful death.') httpserver.stopServer() now = time.time() seconds = now - world.startedAt log.info('Total uptime: %s.', utils.gen.timeElapsed(seconds)) (user, system, _, _, _) = os.times() log.info('Total CPU time taken: %.2f seconds.', user+system) log.info('No more Irc objects, exiting.') if __name__ == '__main__': parser = optparse.OptionParser(usage='Usage: %prog [options] configFile', version='Limnoria %s running on Python %s' % (version, sys.version)) parser.add_option('-P', '--profile', action='store_true', dest='profile', help='enables profiling') parser.add_option('-n', '--nick', action='store', dest='nick', default='', help='nick the bot should use') parser.add_option('-u', '--user', action='store', dest='user', default='', help='full username the bot should use') parser.add_option('-i', '--ident', action='store', dest='ident', default='', help='ident the bot should use') parser.add_option('-d', '--daemon', action='store_true', dest='daemon', help='Determines whether the bot will daemonize. ' 'This is a no-op on non-POSIX systems.') parser.add_option('', '--allow-default-owner', action='store_true', dest='allowDefaultOwner', help='Determines whether the bot will allow its ' 'defaultCapabilities not to include "-owner", thus ' 'giving all users the owner capability by default. ' 'This is a security risk since it allows anyone to run ' 'any command on your bot, so we advise not to use this.') parser.add_option('', '--allow-root', action='store_true', dest='allowRoot', help='Determines whether the bot will be allowed to run ' 'as root. This should not be used except in special ' 'circumstances, such as running inside a containerized ' 'environment.') parser.add_option('', '--debug', action='store_true', dest='debug', help='Determines whether some extra debugging stuff ' 'will be logged in this script.') parser.add_option('', '--disable-multiprocessing', action='store_true', dest='disableMultiprocessing', help='Disables multiprocessing stuff. May lead to ' 'vulnerabilities.') (options, args) = parser.parse_args() if os.name == 'posix': if (os.getuid() == 0 or os.geteuid() == 0) and not options.allowRoot: sys.stderr.write('Running as root is not supported by default (see --allow-root).') sys.stderr.write(os.linesep) sys.exit(-1) if len(args) > 1: parser.error("""Only one configuration file should be specified.""") elif not args: parser.error(utils.str.normalizeWhitespace("""It seems you've given me no configuration file. If you do have a configuration file, be sure to specify the filename. If you don't have a configuration file, read docs/GETTING_STARTED and follow the instructions.""")) else: registryFilename = args.pop() try: # The registry *MUST* be opened before importing log or conf. registry.open_registry(registryFilename) shutil.copyfile(registryFilename, registryFilename + '.bak') except registry.InvalidRegistryFile as e: s = '%s in %s. Please fix this error and start supybot again.' % \ (e, registryFilename) s = textwrap.fill(s) sys.stderr.write(s) sys.stderr.write(os.linesep) raise sys.exit(-1) except EnvironmentError as e: sys.stderr.write(str(e)) sys.stderr.write(os.linesep) sys.exit(-1) i18n.getLocaleFromRegistryCache() try: import supybot.log as log except supybot.registry.InvalidRegistryValue as e: # This is raised here because supybot.log imports supybot.conf. name = e.value._name errmsg = textwrap.fill('%s: %s' % (name, e), width=78, subsequent_indent=' '*len(name)) sys.stderr.write(errmsg) sys.stderr.write(os.linesep) sys.stderr.write('Please fix this error in your configuration file ' 'and restart your bot.') sys.stderr.write(os.linesep) sys.exit(-1) import supybot.conf as conf import supybot.world as world i18n.import_conf() world.starting = True def closeRegistry(): # We only print if world.dying so we don't see these messages during # upkeep. logger = log.debug if world.dying: logger = log.info logger('Writing registry file to %s', registryFilename) registry.close(conf.supybot, registryFilename) logger('Finished writing registry file.') world.flushers.append(closeRegistry) world.registryFilename = registryFilename nick = options.nick or conf.supybot.nick() user = options.user or conf.supybot.user() ident = options.ident or conf.supybot.ident() networks = conf.supybot.networks() if not networks: questions.output("""No networks defined. Perhaps you should re-run the wizard?""", fd=sys.stderr) # XXX We should turn off logging here for a prettier presentation. sys.exit(-1) if os.name == 'posix' and options.daemon: def fork(): child = os.fork() if child != 0: if options.debug: print('Parent exiting, child PID: %s' % child) # We must us os._exit instead of sys.exit so atexit handlers # don't run. They shouldn't be dangerous, but they're ugly. os._exit(0) fork() os.setsid() # What the heck does this do? I wonder if it breaks anything... # ...It did. I don't know why, but it seems largely useless. It seems # to me reasonable that we should respect the user's umask. #os.umask(0) # Let's not do this for now (at least until I can make sure it works): # Actually, let's never do this -- we'll always have files open in the # bot directories, so they won't be able to be unmounted anyway. # os.chdir('/') fork() # Since this is the indicator that no writing should be done to stdout, # we'll set it to True before closing stdout et alii. conf.daemonized = True # Closing stdin shouldn't cause problems. We'll let it raise an # exception if it does. sys.stdin.close() # Closing these two might cause problems; we log writes to them as # level WARNING on upkeep. sys.stdout.close() sys.stderr.close() sys.stdout = StringIO() sys.stderr = StringIO() # We have to be really methodical here. os.close(0) os.close(1) os.close(2) fd = os.open('/dev/null', os.O_RDWR) os.dup2(fd, 0) os.dup2(fd, 1) os.dup2(fd, 2) signal.signal(signal.SIGHUP, signal.SIG_IGN) log.info('Completed daemonization. Current PID: %s', os.getpid()) # Stop setting our own umask. See comment above. #os.umask(077) # Let's write the PID file. This has to go after daemonization, obviously. pidFile = conf.supybot.pidFile() if pidFile: try: fd = open(pidFile, 'w') pid = os.getpid() fd.write('%s%s' % (pid, os.linesep)) fd.close() def removePidFile(): try: os.remove(pidFile) except EnvironmentError as e: log.error('Could not remove pid file: %s', e) atexit.register(removePidFile) except EnvironmentError as e: log.critical('Error opening/writing pid file %s: %s', pidFile, e) sys.exit(-1) conf.allowDefaultOwner = options.allowDefaultOwner world.disableMultiprocessing = options.disableMultiprocessing if not os.path.exists(conf.supybot.directories.log()): os.mkdir(conf.supybot.directories.log()) if not os.path.exists(conf.supybot.directories.conf()): os.mkdir(conf.supybot.directories.conf()) if not os.path.exists(conf.supybot.directories.data()): os.mkdir(conf.supybot.directories.data()) if not os.path.exists(conf.supybot.directories.data.tmp()): os.mkdir(conf.supybot.directories.tmp()) userdataFilename = os.path.join(conf.supybot.directories.conf(), 'userdata.conf') # Let's open this now since we've got our directories setup. if not os.path.exists(userdataFilename): fd = open(userdataFilename, 'w') fd.write('\n') fd.close() registry.open_registry(userdataFilename) import supybot.irclib as irclib import supybot.ircmsgs as ircmsgs import supybot.drivers as drivers import supybot.callbacks as callbacks import supybot.plugins.Owner as Owner # These may take some resources, and it does not need to be run while boot, so # we import it as late as possible (but before plugins are loaded). import supybot.httpserver as httpserver owner = Owner.Class() if options.profile: import profile world.profiling = True profile.run('main()', '%s-%i.prof' % (nick, time.time())) else: main() # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/scripts/supybot-adduser0000644000175000017500000001214213634634532020063 0ustar valval00000000000000#!/usr/bin/env python3 ### # Copyright (c) 2002-2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot from supybot.questions import * import os import sys import optparse def main(): import supybot.log as log import supybot.conf as conf conf.supybot.log.stdout.setValue(False) parser = optparse.OptionParser(usage='Usage: %prog [options] ', version='supybot %s' % conf.version) parser.add_option('-u', '--username', action='store', default='', dest='name', help='username for the user.') parser.add_option('-p', '--password', action='store', default='', dest='password', help='password for the user.') parser.add_option('-c', '--capability', action='append', dest='capabilities', metavar='CAPABILITY', help='capability the user should have; ' 'this option may be given multiple times.') (options, args) = parser.parse_args() if len(args) is not 1: parser.error('Specify the users.conf file you\'d like to use. ' 'Be sure *not* to specify your registry file, generated ' 'by supybot-wizard. This is not the file you want. ' 'Instead, take a look in your conf directory (usually ' 'named "conf") and take a gander at the file ' '"users.conf". That\'s the one you want.') filename = os.path.abspath(args[0]) conf.supybot.directories.log.setValue('/') conf.supybot.directories.conf.setValue('/') conf.supybot.directories.data.setValue('/') conf.supybot.directories.plugins.setValue(['/']) conf.supybot.databases.users.filename.setValue(filename) import supybot.ircdb as ircdb if not options.name: name = '' while not name: name = something('What is the user\'s name?') try: # Check to see if the user is already in the database. _ = ircdb.users.getUser(name) # Uh oh. That user already exists; # otherwise we'd have KeyError'ed. output('That user already exists. Try another name.') name = '' except KeyError: # Good. No such user exists. We'll pass. pass else: try: # Same as above. We exit here instead. _ = ircdb.users.getUser(options.name) output('That user already exists. Try another name.') sys.exit(-1) except KeyError: name = options.name if not options.password: password = getpass('What is %s\'s password? ' % name) else: password = options.password if not options.capabilities: capabilities = [] prompt = 'Would you like to give %s a capability?' % name while yn(prompt): capabilities.append(anything('What capability?')) prompt = 'Would you like to give %s another capability?' % name else: capabilities = options.capabilities user = ircdb.users.newUser() user.name = name user.setPassword(password) for capability in capabilities: user.addCapability(capability) ircdb.users.setUser(user) ircdb.users.flush() #os.system('cat %s' % filename) # Was this here just for debugging? ircdb.users.close() print('User %s added.' % name) if __name__ == '__main__': try: main() except KeyboardInterrupt: pass # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/scripts/supybot-botchk0000644000175000017500000001246113634634532017712 0ustar valval00000000000000#!/usr/bin/env python3 ### # Copyright (c) 2005, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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. ### VERBOSE = False def readPid(filename): fd = open(filename) try: return int(fd.read().strip()) finally: fd.close() def isAlive(pid): try: os.kill(pid, 0) return True except OSError: return False def debug(s): if VERBOSE: if not s.endswith(os.linesep): s += os.linesep sys.stdout.write(s) if __name__ == '__main__': # XXX I wanted this for conf.version, but this will create directories. We # really need to refactor conf so it either doesn't create directories, or # so that static information (like the version) can be imported from # somewhere else. # import supybot.conf as conf import os import sys import optparse import subprocess parser = optparse.OptionParser(usage='Usage: %prog [options]') parser.add_option('', '--verbose', action='store_true', help='Makes output verbose.') parser.add_option('', '--botdir', help='Determines what directory the bot resides in and ' 'should be started from.') parser.add_option('', '--pidfile', help='Determines what file to look in for the pid of ' 'the running bot. This should be relative to the ' 'given bot directory. Note that for this to actually ' 'work, you have to make a matching entry in the ' 'supybot.pidFile config in the supybot registry.') parser.add_option('', '--supybot', default='supybot', help='Determines where the supybot executable is ' 'located. If not given, assumes that supybot is ' 'in $PATH.') parser.add_option('', '--conffile', help='Determines what configuration file should be ' 'given to the supybot executable when (re)starting the ' 'bot.') (options, args) = parser.parse_args() VERBOSE = options.verbose if args: parser.error('Extra arguments given.') if not options.botdir: parser.error('No botdir given.') if not options.pidfile: parser.error('No pidfile given.') if not options.conffile: parser.error('No conffile given.') os.chdir(options.botdir) open(options.pidfile, 'a').close() pid = None try: pid = readPid(options.pidfile) debug('Found pidFile with proper pid contents of %s' % pid) except ValueError as e: foundBot = False if pid is not None: foundBot = isAlive(pid) if foundBot: debug('Pid %s is alive and belongs to us.' % pid) else: debug('Pid %s is not the bot.' % pid) if not foundBot: # First, we check if the pidfile is writable. If not, supybot will just exit, # so we go ahead and refuse to start it. try: open(options.pidfile, 'r+') except EnvironmentError as e: debug('pidfile (%s) is not writable: %s' % (options.pidfile, e)) sys.exit(-1) debug('Bot not found, starting.') cmdline = [options.supybot, '--daemon', options.conffile] inst = subprocess.Popen(cmdline, close_fds=True, stderr=subprocess.STDOUT, stdin=None, stdout=subprocess.PIPE) debug('Output from supybot: %r' % inst.stdout.read()) ret = inst.wait() debug('Bot started, command line %r returned %s.' % (' '.join(cmdline), ret)) sys.exit(ret) else: sys.exit(0) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/scripts/supybot-plugin-create0000644000175000017500000002454613634634532021206 0ustar valval00000000000000#!/usr/bin/env python3 ### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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 __future__ import print_function import supybot import os import sys import time import os.path import optparse def error(s): sys.stderr.write(textwrap.fill(s)) sys.stderr.write(os.linesep) sys.exit(-1) if sys.version_info < (3, 4, 0): error('This script requires Python 3.4 or newer.') import supybot.conf as conf from supybot.questions import * copyright = ''' ### # Copyright (c) %s, %%s # All rights reserved. # %%s ### ''' % time.strftime('%Y') # Here we use strip() instead of lstrip() on purpose. copyright = copyright.strip() license = ''' # 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. ''' license = license.lstrip() pluginTemplate = ''' %s from supybot import utils, plugins, ircutils, callbacks from supybot.commands import * try: from supybot.i18n import PluginInternationalization _ = PluginInternationalization('%s') except ImportError: # Placeholder that allows to run the plugin on a bot # without the i18n module _ = lambda x: x class %s(callbacks.Plugin): """%s""" %s Class = %s # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: '''.lstrip() # This removes the newlines that precede and follow the text. configTemplate = ''' %s from supybot import conf, registry try: from supybot.i18n import PluginInternationalization _ = PluginInternationalization('%s') except: # Placeholder that allows to run the plugin on a bot # without the i18n module _ = 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 themself 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(%r, True) %s = conf.registerPlugin(%r) # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(%s, 'someConfigVariableName', # registry.Boolean(False, _("""Help for someConfigVariableName."""))) # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: '''.lstrip() __init__Template = ''' %s """ %s: %s """ import sys import supybot from supybot import world # Use this for the version of this plugin. __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__ = '' from . import config from . import plugin if sys.version_info >= (3, 4): from importlib import reload else: from imp import reload # In case we're being reloaded. reload(config) reload(plugin) # 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: from . import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: '''.lstrip() testTemplate = ''' %s from supybot.test import * class %sTestCase(PluginTestCase): plugins = (%r,) # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: '''.lstrip() readmeTemplate = ''' %s '''.lstrip() def main(): global copyright global license parser = optparse.OptionParser(usage='Usage: %prog [options]', version='Supybot %s' % conf.version) parser.add_option('-n', '--name', action='store', dest='name', help='sets the name for the plugin.') parser.add_option('-t', '--thread', action='store_true', dest='threaded', help='makes the plugin threaded.') parser.add_option('-a', '--author', '--real-name', action='store', dest='realName', help='Determines who the copyright is ' 'assigned to.') parser.add_option('-d', '--desc', action='store', dest='desc', help='Short description of plugin.') (options, args) = parser.parse_args() if options.name: name = options.name threaded = options.threaded else: name = something('What should the name of the plugin be?') if name.endswith('.py'): name = name[:-3] while name[0].islower(): print('Plugin names must begin with a capital letter.') name = something('What should the name of the plugin be?') if name.endswith('.py'): name = name[:-3] if os.path.exists(name): error('A file or directory named %s already exists; remove or ' 'rename it and run this program again.' % name) print(textwrap.fill(textwrap.dedent(""" Sometimes you'll want a callback to be threaded. If its methods (command or regexp-based, either one) will take a significant amount of time to run, you'll want to thread them so they don't block the entire bot.""").strip())) print() threaded = yn('Does your plugin need to be threaded?') if options.realName: realName = options.realName else: realName = something(textwrap.dedent(""" What is your name, so I can fill in the copyright and license appropriately? """).strip()) if not yn('Do you wish to use Supybot\'s license for your plugin?'): license = '#' if not options.desc: options.desc = something(textwrap.dedent(""" Please provide a short description of the plugin: """).strip()) if threaded: threaded = 'threaded = True' else: threaded = 'pass' if name.endswith('.py'): name = name[:-3] while name[0].islower(): print('Plugin names must begin with a capital letter.') name = something('What should the name of the plugin be?') if name.endswith('.py'): name = name[:-3] copyright %= (realName, license) pathname = name # Make the directory. os.mkdir(pathname) def writeFile(filename, s): fd = open(os.path.join(pathname, filename), 'w') try: fd.write(s) finally: fd.close() writeFile('plugin.py', pluginTemplate % (copyright, name, name, options.desc, threaded, name)) writeFile('config.py', configTemplate % (copyright, name, name, name, name, name)) writeFile('__init__.py', __init__Template % (copyright, name, options.desc)) writeFile('test.py', testTemplate % (copyright, name, name)) writeFile('README.md', readmeTemplate % (options.desc,)) pathname = os.path.join(pathname, 'local') os.mkdir(pathname) writeFile('__init__.py', '# Stub so local is a module, used for third-party modules\n') print('Your new plugin template is in the %s directory.' % name) if __name__ == '__main__': try: main() except KeyboardInterrupt: print() output("""It looks like you cancelled out of this script before it was finished. Obviously, nothing was written, but just run this script again whenever you want to generate a template for a plugin.""") # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/scripts/supybot-plugin-doc0000644000175000017500000002744113634634532020505 0ustar valval00000000000000#!/usr/bin/env python3 ### # Copyright (c) 2005, Ali Afshar # Copyright (c) 2009, James McCoy # 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 os import sys import shutil import supybot def error(s): sys.stderr.write('%s\n' % s) sys.exit(-1) # We need to do this before we import conf. if not os.path.exists('doc-conf'): os.mkdir('doc-conf') registryFilename = os.path.join('doc-conf', 'doc.conf') try: fd = open(registryFilename, 'w') fd.write(""" supybot.directories.data: doc-data supybot.directories.conf: doc-conf supybot.directories.log: doc-logs supybot.log.stdout: False supybot.log.level: DEBUG supybot.log.format: %(levelname)s %(message)s supybot.log.plugins.individualLogfiles: False supybot.databases: sqlite anydbm cdb flat pickle """) fd.close() except EnvironmentError as e: error('Unable to open %s for writing.' % registryFilename) import supybot.registry as registry registry.open_registry(registryFilename) import supybot.log as log import supybot.conf as conf conf.supybot.flush.setValue(False) import textwrap import supybot.utils as utils import supybot.world as world import supybot.plugin as plugin import supybot.registry as registry world.documenting = True class PluginDoc(object): def __init__(self, mod): self.mod = mod self.inst = self.mod.Class(None) self.name = self.mod.Class.__name__ self.appendExtraBlankLine = False self.lines = [] def appendLine(self, line, indent=0): line = line.strip() indent = ' ' * indent lines = textwrap.wrap(line, 79, initial_indent=indent, subsequent_indent=indent) self.lines.extend(lines) if self.appendExtraBlankLine: self.lines.append('') def renderRST(self): self.appendExtraBlankLine = False s = 'Documentation for the %s plugin for Supybot' % self.name self.appendLine(s) self.appendLine('=' * len(s)) self.lines.append('') self.appendLine('Purpose') self.appendLine('-------') pdoc = getattr(self.mod, '__doc__', 'My author didn\'t give me a purpose.') self.appendLine(pdoc) self.lines.append('') cdoc = getattr(self.mod.Class, '__doc__', None) if cdoc is not None: self.appendLine('Usage') self.appendLine('-----') self.appendLine(cdoc) self.lines.append('') commands = self.inst.listCommands() if len(commands): self.appendLine('Commands') self.appendLine('--------') for command in commands: log.debug('command: %s', command) line = '%s ' % command command = command.split() doc = self.inst.getCommandHelp(command) if doc: doc = doc.replace('\x02', '') (args, help) = doc.split(')', 1) args = args.split('(', 1)[1] args = args[len(' '.join(command)):].strip() help = help.split('--', 1)[1].strip() self.appendLine(line + args) self.appendLine(help, 1) else: self.appendLine('No help associated with this command') self.lines.append('') # now the config try: confs = conf.supybot.plugins.get(self.name) self.appendLine('Configuration') self.appendLine('-------------') except registry.NonExistentRegistryEntry: log.info('No configuration for plugin %s', plugin) self.appendLine('No configuration for this plugin') else: for confValues in self.genConfig(confs, 0): (name, isChan, isNet, help, default, indent) = confValues self.appendLine('%s' % name, indent - 1) self.appendLine( ('This config variable defaults to %s, ' 'is%s network-specific, and is %s channel-specific.') % (default, isNet, isChan), indent) self.lines.append('') self.appendLine(help, indent) self.lines.append('') return '\n'.join(self.lines) + '\n' def renderSTX(self): self.appendExtraBlankLine = True self.appendLine('Documentation for the %s plugin for ' 'Supybot' % self.name) self.appendLine('Purpose', 1) pdoc = getattr(self.mod, '__doc__', 'My author didn\'t give me a purpose.') self.appendLine(pdoc, 2) cdoc = getattr(self.mod.Class, '__doc__', None) if cdoc is not None: self.appendLine('Usage', 1) self.appendLine(cdoc, 2) commands = self.inst.listCommands() if len(commands): self.appendLine('Commands', 1) for command in commands: log.debug('command: %s', command) line = '* %s ' % command command = command.split() doc = self.inst.getCommandHelp(command) if doc: doc = doc.replace('\x02', '') (args, help) = doc.split(')', 1) args = args.split('(', 1)[1] args = args[len(' '.join(command)):].strip() help = help.split('--', 1)[1].strip() self.appendLine(line + args, 2) self.appendLine(help, 3) else: self.appendLine('No help associated with this command', 3) # now the config try: confs = conf.supybot.plugins.get(self.name) self.appendLine('Configuration', 1) except registry.NonExistentRegistryEntry: log.info('No configuration for plugin %s', plugin) self.appendLine('No configuration for this plugin', 2) else: for confValues in self.genConfig(confs, 2): (name, isChan, isNet, help, default, indent) = confValues self.appendLine('* %s' % name, indent - 1) self.appendLine( ('This config variable defaults to %s, ' 'is%s network-specific, and is %s channel-specific.') % (default, isNet, isChan), indent) self.appendLine(help, indent) return '\n'.join(self.lines) + '\n' def genConfig(self, item, origindent): confVars = item.getValues(getChildren=False, fullNames=False) if not confVars: return for (c, v) in confVars: if not isinstance(v, registry.Value): # Don't log groups continue name = v._name indent = origindent + 1 try: default = str(v) if not isinstance(v._default, str): default = utils.str.dqrepr(default) help = v.help() except registry.NonExistentRegistryEntry: pass else: cv = '' if v._channelValue else ' not' nv = '' if v._networkValue else ' not' yield (name, cv, nv, help, default, indent) for confValues in self.genConfig(v, indent): yield confValues def genDoc(m, options): Plugin = PluginDoc(m) print('Generating documentation for %s...' % Plugin.name) path = os.path.join(options.outputDir, '%s.%s' % (Plugin.name, options.format)) try: fd = open(path, 'w') except EnvironmentError as e: error('Unable to open %s for writing.' % path) f = getattr(Plugin, 'render%s' % options.format.upper(), None) if f is None: fd.close() error('Unknown render format: `%s\'' % options.format) try: fd.write(f()) finally: fd.close() if __name__ == '__main__': import glob import os.path import optparse import supybot.plugin as plugin parser = optparse.OptionParser(usage='Usage: %prog [options] [plugins]', version='Supybot %s' % conf.version) parser.add_option('-c', '--clean', action='store_true', default=False, dest='clean', help='Cleans the various data/conf/logs ' 'directories after generating the docs.') parser.add_option('-o', '--output-dir', dest='outputDir', default='.', help='Specifies the directory in which to write the ' 'documentation for the plugin.') parser.add_option('-f', '--format', dest='format', choices=['rst', 'stx'], default='stx', help='Specifies which output format to ' 'use.') parser.add_option('--plugins-dir', action='append', dest='pluginsDirs', default=[], help='Looks in in the given directory for plugins and ' 'generates documentation for all of them.') (options, args) = parser.parse_args() # This must go before checking for args, of course. for pluginDir in options.pluginsDirs: for name in glob.glob(os.path.join(pluginDir, '*')): if os.path.isdir(name): args.append(name) if not args: parser.print_help() sys.exit(-1) args = [s.rstrip('\\/') for s in args] pluginDirs = set([os.path.dirname(s) or '.' for s in args]) conf.supybot.directories.plugins.setValue(list(pluginDirs)) pluginNames = set([os.path.basename(s) for s in args]) plugins = set([]) for pluginName in pluginNames: if pluginName.endswith('.py'): pluginName = pluginName[:-3] try: pluginModule = plugin.loadPluginModule(pluginName) except ImportError as e: s = 'Failed to load plugin %s: %s\n' \ '%s(pluginDirs: %s)' % (pluginName, e, s, conf.supybot.directories.plugins()) error(s) plugins.add(pluginModule) for Plugin in plugins: genDoc(Plugin, options) if options.clean: shutil.rmtree(conf.supybot.directories.log()) shutil.rmtree(conf.supybot.directories.conf()) shutil.rmtree(conf.supybot.directories.data()) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=78: limnoria-2020.03.17/scripts/supybot-reset-password0000644000175000017500000001044013634634532021415 0ustar valval00000000000000#!/usr/bin/env python3 ### # Copyright (c) 2002-2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import supybot from supybot.questions import * import os import sys import optparse def main(): import supybot.log as log import supybot.conf as conf conf.supybot.log.stdout.setValue(False) parser = optparse.OptionParser(usage='Usage: %prog [options] ', version='supybot %s' % conf.version) parser.add_option('-u', '--username', action='store', default='', dest='name', help='username for the user.') parser.add_option('-p', '--password', action='store', default='', dest='password', help='password for the user.') (options, args) = parser.parse_args() if len(args) is not 1: parser.error('Specify the users.conf file you\'d like to use. ' 'Be sure *not* to specify your registry file, generated ' 'by supybot-wizard. This is not the file you want. ' 'Instead, take a look in your conf directory (usually ' 'named "conf") and take a gander at the file ' '"users.conf". That\'s the one you want.') filename = os.path.abspath(args[0]) conf.supybot.directories.log.setValue('/') conf.supybot.directories.conf.setValue('/') conf.supybot.directories.data.setValue('/') conf.supybot.directories.plugins.setValue(['/']) conf.supybot.databases.users.filename.setValue(filename) import supybot.ircdb as ircdb if not options.name: name = '' while not name: name = something('What is the user\'s name?') try: # Check to see if the user is already in the database. _ = ircdb.users.getUser(name) # Success! except KeyError: # Failure. No such user exists. Try another name. output('That user doesn\'t exist. Try another name.') name = '' else: try: _ = ircdb.users.getUser(options.name) name = options.name except KeyError: # Same as above. We exit here instead. output('That user doesn\'t exist. Try another name.') sys.exit(-1) if not options.password: password = getpass('Please enter new password for %s: ' % name) else: password = options.password user = ircdb.users.getUser(name) user.setPassword(password) ircdb.users.setUser(user) ircdb.users.flush() ircdb.users.close() print('User %s\'s password reset!' % name) if __name__ == '__main__': try: main() except KeyboardInterrupt: pass # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/scripts/supybot-test0000644000175000017500000002217113634634532017416 0ustar valval00000000000000#!/usr/bin/env python3 ### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2011, James McCoy # 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 os import sys import time import shutil started = time.time() import supybot import logging import traceback # We need to do this before we import conf. if not os.path.exists('test-conf'): os.mkdir('test-conf') registryFilename = os.path.join('test-conf', 'test.conf') fd = open(registryFilename, 'w') fd.write(""" supybot.directories.data: %(base_dir)s/test-data supybot.directories.conf: %(base_dir)s/test-conf supybot.directories.log: %(base_dir)s/test-logs supybot.reply.whenNotCommand: True supybot.log.stdout: False supybot.log.stdout.level: ERROR supybot.log.level: DEBUG supybot.log.format: %%(levelname)s %%(message)s supybot.log.plugins.individualLogfiles: False supybot.protocols.irc.throttleTime: 0 supybot.reply.whenAddressedBy.chars: @ supybot.networks.test.server: should.not.need.this supybot.networks.testnet1.server: should.not.need.this supybot.networks.testnet2.server: should.not.need.this supybot.networks.testnet3.server: should.not.need.this supybot.nick: test supybot.databases.users.allowUnregistration: True """ % {'base_dir': os.getcwd()}) fd.close() import supybot.registry as registry registry.open_registry(registryFilename) import supybot.log as log import supybot.conf as conf conf.allowEval = True conf.supybot.flush.setValue(False) import re import sys import glob import atexit import os.path import unittest import supybot.utils as utils import supybot.world as world import supybot.callbacks as callbacks world.startedAt = started import logging class TestLogFilter(logging.Filter): bads = [ 'No callbacks in', 'Invalid channel database', 'Exact error', 'Invalid user dictionary', 'because of noFlush', 'Queuing NICK', 'Queuing USER', 'IgnoresDB.reload failed', 'Starting log for', 'Irc object for test dying', 'Last Irc,', ] def filter(self, record): for bad in self.bads: if bad in record.msg: return False return True log._logger.addFilter(TestLogFilter()) class path(str): """A class to represent platform-independent paths.""" _r = re.compile(r'[\\/]') def __hash__(self): return reduce(lambda h, s: h ^ hash(s), self._r.split(self), 0) def __eq__(self, other): return self._r.split(self) == self._r.split(other) if __name__ == '__main__': import glob import os.path import optparse import supybot.test as test import supybot.plugin as plugin parser = optparse.OptionParser(usage='Usage: %prog [options] [plugins]', version='Supybot %s' % conf.version) parser.add_option('-c', '--clean', action='store_true', default=False, dest='clean', help='Cleans the various data/conf/logs' 'directories before running tests.') parser.add_option('-t', '--timeout', action='store', type='float', dest='timeout', help='Sets the timeout, in seconds, for tests to return ' 'responses.') parser.add_option('-v', '--verbose', action='count', default=0, help='Increase verbosity, logging extra information ' 'about each test that runs.') parser.add_option('', '--fail-fast', action='store_true', default=False, help='Stop at first failed test.') parser.add_option('', '--no-network', action='store_true', default=False, dest='nonetwork', help='Causes the network-based tests ' 'not to run.') parser.add_option('', '--no-setuid', action='store_true', default=False, dest='nosetuid', help='Causes the tests based on a ' 'setuid executable not to run.') parser.add_option('', '--trace', action='store_true', default=False, help='Traces all calls made. Unless you\'re really in ' 'a pinch, you probably shouldn\'t do this; it results ' 'in copious amounts of output.') parser.add_option('', '--plugins-dir', action='append', dest='pluginsDirs', default=[], help='Looks in in the given directory for plugins and ' 'loads the tests from all of them.') parser.add_option('', '--exclude', action='append', dest='excludePlugins', default=[], help='List of plugins you do not want --plugins-dir ' 'to include.') parser.add_option('', '--disable-multiprocessing', action='store_true', dest='disableMultiprocessing', help='Disables multiprocessing stuff.') (options, args) = parser.parse_args() world.disableMultiprocessing = options.disableMultiprocessing # This must go before checking for args, of course. for pluginDir in options.pluginsDirs: for name in glob.glob(os.path.join(pluginDir, '*')): #print '***', name if not any(map(lambda x:name in x, map(glob.glob, options.excludePlugins))) and \ os.path.isdir(name): args.append(name) if not args: parser.print_help() sys.exit(-1) if options.timeout: test.timeout = options.timeout if options.trace: traceFilename = conf.supybot.directories.log.dirize('trace.log') fd = open(traceFilename, 'w') sys.settrace(utils.gen.callTracer(fd)) atexit.register(fd.close) atexit.register(lambda : sys.settrace(None)) world.myVerbose = options.verbose if options.nonetwork: test.network = False if options.nosetuid: test.setuid = False log.testing = True world.testing = True args = [s.rstrip('\\/') for s in args] pluginDirs = set([os.path.dirname(s) or '.' for s in args]) conf.supybot.directories.plugins.setValue(list(pluginDirs)) pluginNames = set([os.path.basename(s) for s in args]) load = unittest.defaultTestLoader.loadTestsFromModule for pluginName in pluginNames: if pluginName.endswith('.py'): pluginName = pluginName[:-3] try: pluginModule = plugin.loadPluginModule(pluginName) except (ImportError, callbacks.Error) as e: sys.stderr.write('Failed to load plugin %s:' % pluginName) traceback.print_exc() sys.stderr.write('(pluginDirs: %s)\n' % conf.supybot.directories.plugins()) continue if hasattr(pluginModule, 'test'): test.suites.append(load(pluginModule.test)) suite = unittest.TestSuite(test.suites) if options.fail_fast: if sys.version_info < (2, 7, 0): print('--fail-fast is not supported on Python 2.6.') sys.exit(1) else: runner = unittest.TextTestRunner(verbosity=2, failfast=True) else: runner = unittest.TextTestRunner(verbosity=2) print('Testing began at %s (pid %s)' % (time.ctime(), os.getpid())) if options.clean: shutil.rmtree(conf.supybot.directories.log()) shutil.rmtree(conf.supybot.directories.conf()) shutil.rmtree(conf.supybot.directories.data()) result = runner.run(suite) if hasattr(unittest, 'asserts'): print('Total asserts: %s' % unittest.asserts) if result.wasSuccessful(): sys.exit(0) else: sys.exit(1) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/scripts/supybot-wizard0000644000175000017500000010627613634634532017750 0ustar valval00000000000000#!/usr/bin/env python3 ### # Copyright (c) 2003-2004, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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 __future__ import print_function import os import sys def error(s): sys.stderr.write(s) if not s.endswith(os.linesep): sys.stderr.write(os.linesep) sys.exit(-1) if sys.version_info < (3, 4, 0): error('This program requires Python 3.4 or later.') import supybot import re import time import pydoc import pprint import socket import logging import optparse try: import supybot.i18n as i18n except ImportError: sys.stderr.write("""Error: You are running a mix of Limnoria and stock Supybot code. Although you run one of Limnoria\'s executables, Python tries to load stock Supybot\'s library. To fix this issue, uninstall Supybot ("%s -m pip uninstall supybot" should do the job) and install Limnoria again. For your information, Supybot's libraries are installed here: %s\n""" % (sys.executable, '\n '.join(supybot.__path__))) exit(-1) import supybot.ansi as ansi import supybot.utils as utils import supybot.ircutils as ircutils import supybot.registry as registry # supybot.plugin, supybot.log, and supybot.conf will be imported later, # because we need to set a language before loading the conf import supybot.questions as questions from supybot.questions import output, yn, anything, something, expect, getpass def getPlugins(pluginDirs): plugins = set([]) join = os.path.join for pluginDir in pluginDirs: try: for filename in os.listdir(pluginDir): fname = join(pluginDir, filename) if (filename.endswith('.py') or os.path.isdir(fname)) \ and filename[0].isupper(): plugins.add(os.path.splitext(filename)[0]) except OSError: continue plugins.discard('Owner') plugins = list(plugins) plugins.sort() return plugins def loadPlugin(name): import supybot.plugin as plugin try: module = plugin.loadPluginModule(name) if hasattr(module, 'Class'): return module else: output("""That plugin loaded fine, but didn't seem to be a real Supybot plugin; there was no Class variable to tell us what class to load when we load the plugin. We'll skip over it for now, but you can always add it later.""") return None except Exception as e: output("""We encountered a bit of trouble trying to load plugin %r. Python told us %r. We'll skip over it for now, you can always add it later.""" % (name, utils.gen.exnToString(e))) return None def describePlugin(module, showUsage): if module.__doc__: output(module.__doc__, unformatted=False) elif hasattr(module.Class, '__doc__'): output(module.Class.__doc__, unformatted=False) else: output("""Unfortunately, this plugin doesn't seem to have any documentation. Sorry about that.""") if showUsage: if hasattr(module, 'example'): if yn('This plugin has a usage example. ' 'Would you like to see it?', default=False): pydoc.pager(module.example) else: output("""This plugin has no usage example.""") def clearLoadedPlugins(plugins, pluginRegistry): for plugin in plugins: try: pluginKey = pluginRegistry.get(plugin) if pluginKey(): plugins.remove(plugin) except registry.NonExistentRegistryEntry: continue _windowsVarRe = re.compile(r'%(\w+)%') def getDirectoryName(default, basedir=os.curdir, prompt=True): done = False while not done: if prompt: dir = something('What directory do you want to use?', default=os.path.join(basedir, default)) else: dir = os.path.join(basedir, default) orig_dir = dir dir = os.path.expanduser(dir) dir = _windowsVarRe.sub(r'$\1', dir) dir = os.path.expandvars(dir) dir = os.path.abspath(dir) try: os.makedirs(dir) done = True except OSError as e: # 17 is File exists for Linux (and likely other POSIX systems) # 183 is the same for Windows if e.args[0] == 17 or (os.name == 'nt' and e.args[0] == 183): done = True else: output("""Sorry, I couldn't make that directory for some reason. The Operating System told me %s. You're going to have to pick someplace else.""" % e) prompt = True return (dir, os.path.dirname(orig_dir)) def main(): import supybot.version as version parser = optparse.OptionParser(usage='Usage: %prog [options]', version='Supybot %s' % version.version) parser.add_option('', '--allow-root', action='store_true', dest='allowRoot', help='Determines whether the wizard will be allowed to ' 'as root. This should not be used except in special ' 'circumstances, such as running inside a containerized ' 'environment.') parser.add_option('', '--allow-home', action='store_true', dest='allowHome', help='Determines whether the wizard will be allowed to ' 'run directly in the HOME directory. ' 'You should not do this unless you want it to ' 'create multiple files in your HOME directory.') parser.add_option('', '--allow-bin', action='store_true', dest='allowBin', help='Determines whether the wizard will be allowed to ' 'run directly in a directory for binaries (eg. ' '/usr/bin, /usr/local/bin, or ~/.local/bin). ' 'You should not do this unless you want it to ' 'create multiple non-binary files in directory ' 'that should only contain binaries.') parser.add_option('', '--no-network', action='store_false', dest='network', help='Determines whether the wizard will be allowed to ' 'run without a network connection.') (options, args) = parser.parse_args() if os.name == 'posix': if (os.getuid() == 0 or os.geteuid() == 0) and not options.allowRoot: error('Running as root is not supported by default (see --allow-root).') if os.name == 'posix': if (os.getcwd() == os.path.expanduser('~')) and not options.allowHome: error('Please, don\'t run this in your HOME directory.') if (os.path.split(os.getcwd())[-1] == 'bin') and not options.allowBin: error('Please, don\'t run this in a "bin" directory.') if os.path.isfile(os.path.join('scripts', 'supybot-wizard')) or \ os.path.isfile(os.path.join('..', 'scripts', 'supybot-wizard')): print('') print('+------------------------------------------------------------+') print('| +--------------------------------------------------------+ |') print('| | Warning: It looks like you are running the wizard from | |') print('| | the Supybot source directory. This is not recommended. | |') print('| | Please press Ctrl-C and change to another directory. | |') print('| +--------------------------------------------------------+ |') print('+------------------------------------------------------------+') print('') if args: parser.error('This program takes no non-option arguments.') output("""This is a wizard to help you start running supybot. What it will do is create the necessary config files based on the options you select here. So hold on tight and be ready to be interrogated :)""") output("""First of all, we can bold the questions you're asked so you can easily distinguish the mostly useless blather (like this) from the questions that you actually have to answer.""") if yn('Would you like to try this bolding?', default=True): questions.useBold = True if not yn('Do you see this in bold?'): output("""Sorry, it looks like your terminal isn't ANSI compliant. Try again some other day, on some other terminal :)""") questions.useBold = False else: output("""Great!""") ### # Preliminary questions. ### output("""We've got some preliminary things to get out of the way before we can really start asking you questions that directly relate to what your bot is going to be like.""") # Advanced? output("""We want to know if you consider yourself an advanced Supybot user because some questions are just utterly boring and useless for new users. Others might not make sense unless you've used Supybot for some time.""") advanced = yn('Are you an advanced Supybot user?', default=False) # Language? output("""This version of Supybot (known as Limnoria) includes another language. This can be changed at any time. You need to answer with a short id for the language, such as 'en', 'fr', 'it' (without the quotes). If you want to use English, just press enter.""") language = something('What language do you want to use?', default='en') class Empty: """This is a hack to allow the i18n to get the current language, before loading the conf module, before the conf module needs i18n to set the default strings.""" def __call__(self): return self.value fakeConf = Empty() fakeConf.supybot = Empty() fakeConf.supybot.language = Empty() fakeConf.supybot.language.value = language i18n.conf = fakeConf i18n.currentLocale = language i18n.reloadLocales() import supybot.conf as conf i18n.import_conf() # It imports the real conf module ### Directories. # We set these variables in cache because otherwise conf and log will # create directories for the default values, which might not be what the # user wants. if advanced: output("""Now we've got to ask you some questions about where some of your directories are (or, perhaps, will be :)). If you're running this wizard from the directory you'll actually be starting your bot from and don't mind creating some directories in the current directory, then just don't give answers to these questions and we'll create the directories we need right here in this directory.""") # conf.supybot.directories.log output("""Your bot will need to put its logs somewhere. Do you have any specific place you'd like them? If not, just press enter and we'll make a directory named "logs" right here.""") (logDir, basedir) = getDirectoryName('logs') conf.supybot.directories.log.setValue(logDir) import supybot.log as log log._stdoutHandler.setLevel(100) # *Nothing* gets through this! # conf.supybot.directories.data output("""Your bot will need to put various data somewhere. Things like databases, downloaded files, etc. Do you have any specific place you'd like the bot to put these things? If not, just press enter and we'll make a directory named "data" right here.""") (dataDir, basedir) = getDirectoryName('data', basedir=basedir) conf.supybot.directories.data.setValue(dataDir) # conf.supybot.directories.conf output("""Your bot must know where to find its configuration files. It'll probably only make one or two, but it's gotta have some place to put them. Where should that place be? If you don't care, just press enter and we'll make a directory right here named "conf" where it'll store its stuff. """) (confDir, basedir) = getDirectoryName('conf', basedir=basedir) conf.supybot.directories.conf.setValue(confDir) # conf.supybot.directories.backup output("""Your bot must know where to place backups of its conf and data files. Where should that place be? If you don't care, just press enter and we'll make a directory right here named "backup" where it'll store its stuff.""") (backupDir, basedir) = getDirectoryName('backup', basedir=basedir) conf.supybot.directories.backup.setValue(backupDir) # conf.supybot.directories.data.tmp output("""Your bot needs a directory to put temporary files (used mainly to atomically update its configuration files).""") (tmpDir, basedir) = getDirectoryName('tmp', basedir=basedir) conf.supybot.directories.data.tmp.setValue(tmpDir) # conf.supybot.directories.data.web output("""Your bot needs a directory to put files related to the web server (templates, CSS).""") (webDir, basedir) = getDirectoryName('web', basedir=basedir) conf.supybot.directories.data.web.setValue(webDir) # imports callbacks, which imports ircdb, which requires # directories.conf import supybot.plugin as plugin # pluginDirs output("""Your bot will also need to know where to find its plugins at. Of course, it already knows where the plugins that it came with are, but your own personal plugins that you write for will probably be somewhere else.""") pluginDirs = conf.supybot.directories.plugins() output("""Currently, the bot knows about the following directories:""") output(format('%L', pluginDirs + [plugin._pluginsDir])) while yn('Would you like to add another plugin directory? ' 'Adding a local plugin directory is good style.', default=True): (pluginDir, _) = getDirectoryName('plugins', basedir=basedir) if pluginDir not in pluginDirs: pluginDirs.append(pluginDir) conf.supybot.directories.plugins.setValue(pluginDirs) else: output("""Your bot needs to create some directories in order to store the various log, config, and data files.""") basedir = something("""Where would you like to create these directories?""", default=os.curdir) # conf.supybot.directories.log (logDir, basedir) = getDirectoryName('logs', basedir=basedir, prompt=False) conf.supybot.directories.log.setValue(logDir) # conf.supybot.directories.data (dataDir, basedir) = getDirectoryName('data', basedir=basedir, prompt=False) conf.supybot.directories.data.setValue(dataDir) (tmpDir, basedir) = getDirectoryName('tmp', basedir=basedir, prompt=False) conf.supybot.directories.data.tmp.setValue(tmpDir) (webDir, basedir) = getDirectoryName('web', basedir=basedir, prompt=False) conf.supybot.directories.data.web.setValue(webDir) # conf.supybot.directories.conf (confDir, basedir) = getDirectoryName('conf', basedir=basedir, prompt=False) conf.supybot.directories.conf.setValue(confDir) # conf.supybot.directories.backup (backupDir, basedir) = getDirectoryName('backup', basedir=basedir, prompt=False) conf.supybot.directories.backup.setValue(backupDir) # pluginDirs pluginDirs = conf.supybot.directories.plugins() (pluginDir, _) = getDirectoryName('plugins', basedir=basedir, prompt=False) if pluginDir not in pluginDirs: pluginDirs.append(pluginDir) conf.supybot.directories.plugins.setValue(pluginDirs) import supybot.log as log log._stdoutHandler.setLevel(100) # *Nothing* gets through this! import supybot.plugin as plugin output("Good! We're done with the directory stuff.") ### # Bot stuff ### output("""Now we're going to ask you things that actually relate to the bot you'll be running.""") network = None while not network: output("""First, we need to know the name of the network you'd like to connect to. Not the server host, mind you, but the name of the network. If you plan to connect to chat.freenode.net, for instance, you should answer this question with 'freenode' (without the quotes). """) network = something('What IRC network will you be connecting to?') if '.' in network: output("""There shouldn't be a '.' in the network name. Remember, this is the network name, not the actual server you plan to connect to.""") network = None elif not registry.isValidRegistryName(network): output("""That's not a valid name for one reason or another. Please pick a simpler name, one more likely to be valid.""") network = None conf.supybot.networks.setValue([network]) network = conf.registerNetwork(network) defaultServer = None server = None ip = None while not ip: serverString = something('What server would you like to connect to?', default=defaultServer) if options.network: try: output("""Looking up %s...""" % serverString) ip = socket.gethostbyname(serverString) except: output("""Sorry, I couldn't find that server. Perhaps you misspelled it? Also, be sure not to put the port in the server's name -- we'll ask you about that later.""") else: ip = 'no network available' output("""Found %s (%s).""" % (serverString, ip)) # conf.supybot.networks..ssl output("""Most networks allow you to use a secure connection via SSL. If you are not sure whether this network supports SSL or not, check its website.""") use_ssl = yn('Do you want to use an SSL connection?', default=True) network.ssl.setValue(use_ssl) output("""IRC servers almost always accept connections on port 6697 (or 6667 when not using SSL). They can, however, accept connections anywhere their admins feel like they want to accept connections from.""") if yn('Does this server require connection on a non-standard port?', default=False): port = 0 while not port: port = something('What port is that?') try: i = int(port) if not (0 < i < 65536): raise ValueError() except ValueError: output("""That's not a valid port.""") port = 0 else: if network.ssl.value: port = 6697 else: port = 6667 server = ':'.join([serverString, str(port)]) network.servers.setValue([server]) # conf.supybot.nick # Force the user into specifying a nick if it didn't have one already while True: nick = something('What nick would you like your bot to use?', default=None) try: conf.supybot.nick.set(nick) break except registry.InvalidRegistryValue: output("""That's not a valid nick. Go ahead and pick another.""") # conf.supybot.user if advanced: output("""If you've ever done a /whois on a person, you know that IRC provides a way for users to show the world their full name. What would you like your bot's full name to be? If you don't care, just press enter and it'll be the same as your bot's nick.""") user = '' user = something('What would you like your bot\'s full name to be?', default=nick) conf.supybot.user.set(user) # conf.supybot.ident (if advanced) defaultIdent = 'limnoria' if advanced: output("""IRC servers also allow you to set your ident, which they might need if they can't find your identd server. What would you like your ident to be? If you don't care, press enter and we'll use 'limnoria'. In fact, we prefer that you do this, because it provides free advertising for Supybot when users /whois your bot. But, of course, it's your call.""") while True: ident = something('What would you like your bot\'s ident to be?', default=defaultIdent) try: conf.supybot.ident.set(ident) break except registry.InvalidRegistryValue: output("""That was not a valid ident. Go ahead and pick another.""") else: conf.supybot.ident.set(defaultIdent) # conf.supybot.networks..password output("""Some servers require a password to connect to them. Most public servers don't. If you try to connect to a server and for some reason it just won't work, it might be that you need to set a password.""") if yn('Do you want to set such a password?', default=False): network.password.set(getpass()) # conf.supybot.networks..channels output("""Of course, having an IRC bot isn't the most useful thing in the world unless you can make that bot join some channels.""") if yn('Do you want your bot to join some channels when it connects?', default=True): defaultChannels = ' '.join(network.channels()) output("""Separate channels with spaces. If the channel is locked with a key, follow the channel name with the key separated by a comma. For example: #supybot-bots #mychannel,mykey #otherchannel"""); while True: channels = something('What channels?', default=defaultChannels) try: network.channels.set(channels) break except registry.InvalidRegistryValue as e: output(""""%s" is an invalid IRC channel. Be sure to prefix the channel with # (or +, or !, or &, but no one uses those channels, really). Be sure the channel key (if you are supplying one) does not contain a comma.""" % e.channel) else: network.channels.setValue([]) ### # Plugins ### def configurePlugin(module, advanced): if hasattr(module, 'configure'): output("""Beginning configuration for %s...""" % module.Class.__name__) module.configure(advanced) print() # Blank line :) output("""Done!""") else: conf.registerPlugin(module.__name__, currentValue=True) plugins = getPlugins(pluginDirs + [plugin._pluginsDir]) for s in ('Admin', 'User', 'Channel', 'Misc', 'Config', 'Utilities'): m = loadPlugin(s) if m is not None: configurePlugin(m, advanced) else: error('There was an error loading one of the core plugins that ' 'under almost all circumstances are loaded. Go ahead and ' 'fix that error and run this script again.') clearLoadedPlugins(plugins, conf.supybot.plugins) output("""Now we're going to run you through plugin configuration. There's a variety of plugins in supybot by default, but you can create and add your own, of course. We'll allow you to take a look at the known plugins' descriptions and configure them if you like what you see.""") # bulk addedBulk = False if advanced and yn('Would you like to add plugins en masse first?'): addedBulk = True output(format("""The available plugins are: %L.""", plugins)) output("""What plugins would you like to add? If you've changed your mind and would rather not add plugins in bulk like this, just press enter and we'll move on to the individual plugin configuration. We suggest you to add Aka, Ctcp, Later, Network, Plugin, String, and Utilities""") massPlugins = anything('Separate plugin names by spaces or commas:') for name in re.split(r',?\s+', massPlugins): module = loadPlugin(name) if module is not None: configurePlugin(module, advanced) clearLoadedPlugins(plugins, conf.supybot.plugins) # individual if yn('Would you like to look at plugins individually?'): output("""Next comes your opportunity to learn more about the plugins that are available and select some (or all!) of them to run in your bot. Before you have to make a decision, of course, you'll be able to see a short description of the plugin and, if you choose, an example session with the plugin. Let's begin.""") # until we get example strings again, this will default to false #showUsage =yn('Would you like the option of seeing usage examples?') showUsage = False name = expect('What plugin would you like to look at?', plugins, acceptEmpty=True) while name: module = loadPlugin(name) if module is not None: describePlugin(module, showUsage) if yn('Would you like to load this plugin?', default=True): configurePlugin(module, advanced) clearLoadedPlugins(plugins, conf.supybot.plugins) if not yn('Would you like add another plugin?'): break name = expect('What plugin would you like to look at?', plugins) ### # Sundry ### output("""Although supybot offers a supybot-adduser script, with which you can add users to your bot's user database, it's *very* important that you have an owner user for you bot.""") if yn('Would you like to add an owner user for your bot?', default=True): import supybot.ircdb as ircdb name = something('What should the owner\'s username be?') try: id = ircdb.users.getUserId(name) u = ircdb.users.getUser(id) if u._checkCapability('owner'): output("""That user already exists, and has owner capabilities already. Perhaps you added it before? """) if yn('Do you want to remove its owner capability?', default=False): u.removeCapability('owner') ircdb.users.setUser(id, u) else: output("""That user already exists, but doesn't have owner capabilities.""") if yn('Do you want to add to it owner capabilities?', default=False): u.addCapability('owner') ircdb.users.setUser(id, u) except KeyError: password = getpass('What should the owner\'s password be?') u = ircdb.users.newUser() u.name = name u.setPassword(password) u.addCapability('owner') ircdb.users.setUser(u) output("""Of course, when you're in an IRC channel you can address the bot by its nick and it will respond, if you give it a valid command (it may or may not respond, depending on what your config variable replyWhenNotCommand is set to). But your bot can also respond to a short "prefix character," so instead of saying "bot: do this," you can say, "@do this" and achieve the same effect. Of course, you don't *have* to have a prefix char, but if the bot ends up participating significantly in your channel, it'll ease things.""") if yn('Would you like to set the prefix char(s) for your bot? ', default=True): output("""Enter any characters you want here, but be careful: they should be rare enough that people don't accidentally address the bot (simply because they'll probably be annoyed if they do address the bot on accident). You can even have more than one. I (jemfinch) am quite partial to @, but that's because I've been using it since my ocamlbot days.""") import supybot.callbacks as callbacks c = '' while not c: try: c = anything('What would you like your bot\'s prefix ' 'character(s) to be?') conf.supybot.reply.whenAddressedBy.chars.set(c) except registry.InvalidRegistryValue as e: output(str(e)) c = '' else: conf.supybot.reply.whenAddressedBy.chars.set('') ### # logging variables. ### if advanced: # conf.supybot.log.stdout output("""By default, your bot will log not only to files in the logs directory you gave it, but also to stdout. We find this useful for debugging, and also just for the pretty output (it's colored!)""") stdout = not yn('Would you like to turn off this logging to stdout?', default=False) conf.supybot.log.stdout.setValue(stdout) if conf.supybot.log.stdout(): # conf.something output("""Some terminals may not be able to display the pretty colors logged to stderr. By default, though, we turn the colors off for Windows machines and leave it on for *nix machines.""") if os.name is not 'nt': conf.supybot.log.stdout.colorized.setValue( not yn('Would you like to turn this colorization off?', default=False)) # conf.supybot.log.level output("""Your bot can handle debug messages at several priorities, CRITICAL, ERROR, WARNING, INFO, and DEBUG, in decreasing order of priority. By default, your bot will log all of these priorities except DEBUG. You can, however, specify that it only log messages above a certain priority level.""") priority = str(conf.supybot.log.level) logLevel = something('What would you like the minimum priority to be?' ' Just press enter to accept the default.', default=priority).lower() while logLevel not in ['debug','info','warning','error','critical']: output("""That's not a valid priority. Valid priorities include 'DEBUG', 'INFO', 'WARNING', 'ERROR', and 'CRITICAL'""") logLevel = something('What would you like the minimum priority to ' 'be? Just press enter to accept the default.', default=priority).lower() conf.supybot.log.level.set(logLevel) # conf.supybot.databases.plugins.channelSpecific output("""Many plugins in Supybot are channel-specific. Their databases, likewise, are specific to each channel the bot is in. Many people don't want this, so we have one central location in which to say that you would prefer all databases for all channels to be shared. This variable, supybot.databases.plugins.channelSpecific, is that place.""") conf.supybot.databases.plugins.channelSpecific.setValue( not yn('Would you like plugin databases to be shared by all ' 'channels, rather than specific to each channel the ' 'bot is in?')) output("""There are a lot of options we didn't ask you about simply because we'd rather you get up and running and have time left to play around with your bot. But come back and see us! When you've played around with your bot enough to know what you like, what you don't like, what you'd like to change, then take a look at your configuration file when your bot isn't running and read the comments, tweaking values to your heart's desire.""") # Let's make sure that src/ plugins are loaded. conf.registerPlugin('Admin', True) conf.registerPlugin('AutoMode', True) conf.registerPlugin('Channel', True) conf.registerPlugin('Config', True) conf.registerPlugin('Misc', True) conf.registerPlugin('Network', True) conf.registerPlugin('NickAuth', True) conf.registerPlugin('User', True) conf.registerPlugin('Utilities', True) ### # Write the registry ### # We're going to need to do a darcs predist thing here. #conf.supybot.debug.generated.setValue('...') if advanced: basedir = '.' filename = os.path.join(basedir, '%s.conf') filename = something("""In which file would you like to save this config?""", default=filename % nick) if not filename.endswith('.conf'): filename += '.conf' registry.close(conf.supybot, os.path.expanduser(filename)) # Done! output("""All done! Your new bot configuration is %s. If you're running a *nix based OS, you can probably start your bot with the command line "supybot %s". If you're not running a *nix or similar machine, you'll just have to start it like you start all your other Python scripts.""" % \ (filename, filename)) if __name__ == '__main__': try: main() except KeyboardInterrupt: # We may still be using bold text when exiting during a prompt if questions.useBold: import supybot.ansi as ansi print(ansi.RESET) print() print() output("""Well, it looks like you canceled out of the wizard before it was done. Unfortunately, I didn't get to write anything to file. Please run the wizard again to completion.""") # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/setup.py0000644000175000017500000002145513634634532015040 0ustar valval00000000000000#!/usr/bin/env python3 ### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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 os import sys import time import warnings import datetime import tempfile import subprocess warnings.filterwarnings('always', category=DeprecationWarning) debug = '--debug' in sys.argv path = os.path.dirname(__file__) if debug: print('DEBUG: Changing dir from %r to %r' % (os.getcwd(), path)) if path: os.chdir(path) VERSION_FILE = os.path.join('src', 'version.py') version = None try: if 'SOURCE_DATE_EPOCH' in os.environ: date = int(os.environ['SOURCE_DATE_EPOCH']) else: proc = subprocess.Popen('git show HEAD --format=%ct', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) date = proc.stdout.readline() if sys.version_info[0] >= 3: date = date.decode() date = int(date.strip()) version = ".".join(str(i).zfill(2) for i in time.strptime(time.asctime(time.gmtime(date)))[:3]) except: if os.path.isfile(VERSION_FILE): from src.version import version else: version = 'installed on ' + time.strftime("%Y-%m-%dT%H-%M-%S", time.gmtime()) try: os.unlink(VERSION_FILE) except OSError: # Does not exist pass if version: fd = open(os.path.join('src', 'version.py'), 'a') fd.write("version = '%s'\n" % version) fd.write('try: # For import from setup.py\n') fd.write(' import supybot.utils.python\n') fd.write(' supybot.utils.python._debug_software_version = version\n') fd.write('except ImportError:\n') fd.write(' pass\n') fd.close() if sys.version_info < (3, 4, 0): sys.stderr.write("Limnoria requires Python 3.4 or newer.") sys.stderr.write(os.linesep) sys.exit(-1) import textwrap clean = False while '--clean' in sys.argv: clean = True sys.argv.remove('--clean') import glob import shutil import os plugins = [s for s in os.listdir('plugins') if os.path.exists(os.path.join('plugins', s, 'plugin.py'))] def normalizeWhitespace(s): return ' '.join(s.split()) try: from distutils.core import setup from distutils.sysconfig import get_python_lib except ImportError as e: s = normalizeWhitespace("""Supybot requires the distutils package to install. This package is normally included with Python, but for some unfathomable reason, many distributions to take it out of standard Python and put it in another package, usually caled 'python-dev' or python-devel' or something similar. This is one of the dumbest things a distribution can do, because it means that developers cannot rely on *STANDARD* Python modules to be present on systems of that distribution. Complain to your distribution, and loudly. If you how much of our time we've wasted telling people to install what should be included by default with Python you'd understand why we're unhappy about this. Anyway, to reiterate, install the development package for Python that your distribution supplies.""") sys.stderr.write(os.linesep*2) sys.stderr.write(textwrap.fill(s)) sys.stderr.write(os.linesep*2) sys.exit(-1) if clean: previousInstall = os.path.join(get_python_lib(), 'supybot') if os.path.exists(previousInstall): try: print('Removing current installation.') shutil.rmtree(previousInstall) except Exception as e: print('Couldn\'t remove former installation: %s' % e) sys.exit(-1) packages = ['supybot', 'supybot.locales', 'supybot.utils', 'supybot.drivers', 'supybot.plugins',] + \ ['supybot.plugins.'+s for s in plugins] + \ [ 'supybot.plugins.Dict.local', 'supybot.plugins.Math.local', ] package_dir = {'supybot': 'src', 'supybot.utils': 'src/utils', 'supybot.locales': 'locales', 'supybot.plugins': 'plugins', 'supybot.drivers': 'src/drivers', 'supybot.plugins.Dict.local': 'plugins/Dict/local', 'supybot.plugins.Math.local': 'plugins/Math/local', } package_data = {'supybot.locales': [s for s in os.listdir('locales/')]} for plugin in plugins: plugin_name = 'supybot.plugins.' + plugin package_dir[plugin_name] = 'plugins/' + plugin pot_path = 'plugins/' + plugin + 'messages.pot' locales_path = 'plugins/' + plugin + '/locales/' files = [] if os.path.exists(pot_path): files.append('messages.pot') if os.path.exists(locales_path): files.extend(['locales/'+s for s in os.listdir(locales_path)]) if files: package_data.update({plugin_name: files}) setup( # Metadata name='limnoria', provides=['supybot'], version=version, author='Valentin Lorentz', url='https://github.com/ProgVal/Limnoria', author_email='progval+limnoria@progval.net', download_url='https://pypi.python.org/pypi/limnoria', description='A modified version of Supybot (an IRC bot and framework)', platforms=['linux', 'linux2', 'win32', 'cygwin', 'darwin'], long_description=normalizeWhitespace("""A robust, full-featured Python IRC bot with a clean and flexible plugin API. Equipped with a complete ACL system for specifying user permissions with as much as per-command granularity. Batteries are included in the form of numerous plugins already written."""), classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', 'Environment :: No Input/Output (Daemon)', 'Intended Audience :: End Users/Desktop', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Natural Language :: English', 'Natural Language :: Finnish', 'Natural Language :: French', 'Natural Language :: Hungarian', 'Natural Language :: Italian', 'Operating System :: OS Independent', 'Operating System :: POSIX', 'Operating System :: Microsoft :: Windows', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Topic :: Communications :: Chat :: Internet Relay Chat', 'Topic :: Software Development :: Libraries :: Python Modules', ], # Installation data packages=packages, package_dir=package_dir, package_data=package_data, scripts=['scripts/supybot', 'scripts/supybot-test', 'scripts/supybot-botchk', 'scripts/supybot-wizard', 'scripts/supybot-adduser', 'scripts/supybot-reset-password', 'scripts/supybot-plugin-doc', 'scripts/supybot-plugin-create', ], data_files=[('share/man/man1', ['man/supybot.1']), ('share/man/man1', ['man/supybot-test.1']), ('share/man/man1', ['man/supybot-botchk.1']), ('share/man/man1', ['man/supybot-wizard.1']), ('share/man/man1', ['man/supybot-adduser.1']), ('share/man/man1', ['man/supybot-plugin-doc.1']), ('share/man/man1', ['man/supybot-plugin-create.1']), ], ) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/0000755000175000017500000000000013634634547014114 5ustar valval00000000000000limnoria-2020.03.17/src/__init__.py0000644000175000017500000001027013634634532016217 0ustar valval00000000000000# -*- coding: utf-8 -*- ### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from . import dynamicScope from . import i18n builtins = (__builtins__ if isinstance(__builtins__, dict) else __builtins__.__dict__) builtins['supybotInternationalization'] = i18n.PluginInternationalization() from . import utils del builtins['supybotInternationalization'] (__builtins__ if isinstance(__builtins__, dict) else __builtins__.__dict__)['format'] = utils.str.format class Author(object): """ Describes a plugin author. All fields are optional, but the standard practice is to include an email and at least one of 'name' and 'nick'. """ def __init__(self, name=None, nick=None, email=None, **kwargs): self.__dict__.update(kwargs) self.name = name self.nick = nick self.email = email def format(self, short=False): # If only one of these are defined, take the nick as the name name = self.name or self.nick or 'Unknown author' s = name if self.nick and name != self.nick: # Format as "Name (nick)" if both are given and different s += ' (%s)' % self.nick if self.email and not short: # Add "Name (nick) " or "Name " if provided s += ' <%s>' % self.email return s def __str__(self): return self.format() class authors(object): # This is basically a bag. jemfinch = Author('Jeremy Fincher', 'jemfinch', 'jemfinch@users.sf.net') jamessan = Author('James McCoy', 'jamessan', 'vega.james@gmail.com') strike = Author('Daniel DiPaolo', 'Strike', 'ddipaolo@users.sf.net') baggins = Author('William Robinson', 'baggins', 'airbaggins@users.sf.net') skorobeus = Author('Kevin Murphy', 'Skorobeus', 'skoro@skoroworld.com') inkedmn = Author('Brett Kelly', 'inkedmn', 'inkedmn@users.sf.net') bwp = Author('Brett Phipps', 'bwp', 'phippsb@gmail.com') bear = Author('Mike Taylor', 'bear', 'bear@code-bear.com') grantbow = Author('Grant Bowman', 'Grantbow', 'grantbow@grantbow.com') stepnem = Author('Štěpán Němec', 'stepnem', 'stepnem@gmail.com') progval = Author('Valentin Lorentz', 'ProgVal', 'progval@gmail.com') jlu = Author('James Lu', email='james@overdrivenetworks.com') unknown = Author('Unknown author', email='unknown@email.invalid') # Set as __maintainer__ field for all built-in plugins limnoria_core = Author('Limnoria contributors') # Let's be somewhat safe about this. def __getattr__(self, attr): try: return getattr(super(authors, self), attr.lower()) except AttributeError: return self.unknown # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/ansi.py0000644000175000017500000000666013634634532015422 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ ansi.py ANSI Terminal Interface Color Usage: print RED + 'this is red' + RESET print BOLD + GREEN + WHITEBG + 'this is bold green on white' + RESET def move(new_x, new_y): 'Move cursor to new_x, new_y' def moveUp(lines): 'Move cursor up # of lines' def moveDown(lines): 'Move cursor down # of lines' def moveForward(chars): 'Move cursor forward # of chars' def moveBack(chars): 'Move cursor backward # of chars' def save(): 'Saves cursor position' def restore(): 'Restores cursor position' def clear(): 'Clears screen and homes cursor' def clrtoeol(): 'Clears screen to end of line' """ ################################ # C O L O R C O N S T A N T S # ################################ BLACK = '\033[30m' RED = '\033[31m' GREEN = '\033[32m' YELLOW = '\033[33m' BLUE = '\033[34m' MAGENTA = '\033[35m' CYAN = '\033[36m' WHITE = '\033[37m' RESET = '\033[0;0m' BOLD = '\033[1m' REVERSE = '\033[2m' BLACKBG = '\033[40m' REDBG = '\033[41m' GREENBG = '\033[42m' YELLOWBG = '\033[43m' BLUEBG = '\033[44m' MAGENTABG = '\033[45m' CYANBG = '\033[46m' WHITEBG = '\033[47m' #def move(new_x, new_y): # 'Move cursor to new_x, new_y' # print '\033[' + str(new_x) + ';' + str(new_y) + 'H' # #def moveUp(lines): # 'Move cursor up # of lines' # print '\033[' + str(lines) + 'A' # #def moveDown(lines): # 'Move cursor down # of lines' # print '\033[' + str(lines) + 'B' # #def moveForward(chars): # 'Move cursor forward # of chars' # print '\033[' + str(chars) + 'C' # #def moveBack(chars): # 'Move cursor backward # of chars' # print '\033[' + str(chars) + 'D' # #def save(): # 'Saves cursor position' # print '\033[s' # #def restore(): # 'Restores cursor position' # print '\033[u' # #def clear(): # 'Clears screen and homes cursor' # print '\033[2J' # #def clrtoeol(): # 'Clears screen to end of line' # print '\033[K' # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/callbacks.py0000644000175000017500000017321013634634532016403 0ustar valval00000000000000# -*- coding: utf8 -*- ### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2014, James McCoy # 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. ### """ This module contains the basic callbacks for handling PRIVMSGs. """ import re import copy import time from . import shlex import codecs import getopt import inspect from . import (conf, ircdb, irclib, ircmsgs, ircutils, log, registry, utils, world) from .utils import minisix from .utils.iter import any, all from .i18n import PluginInternationalization _ = PluginInternationalization() def _addressed(nick, msg, prefixChars=None, nicks=None, prefixStrings=None, whenAddressedByNick=None, whenAddressedByNickAtEnd=None): def get(group): if ircutils.isChannel(target): group = group.get(target) return group() def stripPrefixStrings(payload): for prefixString in prefixStrings: if payload.startswith(prefixString): payload = payload[len(prefixString):].lstrip() return payload assert msg.command == 'PRIVMSG' (target, payload) = msg.args if not payload: return '' if prefixChars is None: prefixChars = get(conf.supybot.reply.whenAddressedBy.chars) if whenAddressedByNick is None: whenAddressedByNick = get(conf.supybot.reply.whenAddressedBy.nick) if whenAddressedByNickAtEnd is None: r = conf.supybot.reply.whenAddressedBy.nick.atEnd whenAddressedByNickAtEnd = get(r) if prefixStrings is None: prefixStrings = get(conf.supybot.reply.whenAddressedBy.strings) # We have to check this before nicks -- try "@google supybot" with supybot # and whenAddressedBy.nick.atEnd on to see why. if any(payload.startswith, prefixStrings): return stripPrefixStrings(payload) elif payload[0] in prefixChars: return payload[1:].strip() if nicks is None: nicks = get(conf.supybot.reply.whenAddressedBy.nicks) nicks = list(map(ircutils.toLower, nicks)) else: nicks = list(nicks) # Just in case. nicks.insert(0, ircutils.toLower(nick)) # Ok, let's see if it's a private message. if ircutils.nickEqual(target, nick): payload = stripPrefixStrings(payload) while payload and payload[0] in prefixChars: payload = payload[1:].lstrip() return payload # Ok, not private. Does it start with our nick? elif whenAddressedByNick: for nick in nicks: lowered = ircutils.toLower(payload) if lowered.startswith(nick): try: (maybeNick, rest) = payload.split(None, 1) toContinue = False while not ircutils.isNick(maybeNick, strictRfc=True): if maybeNick[-1].isalnum(): toContinue = True break maybeNick = maybeNick[:-1] if toContinue: continue if ircutils.nickEqual(maybeNick, nick): return rest else: continue except ValueError: # split didn't work. continue elif whenAddressedByNickAtEnd and lowered.endswith(nick): rest = payload[:-len(nick)] possiblePayload = rest.rstrip(' \t,;') if possiblePayload != rest: # There should be some separator between the nick and the # previous alphanumeric character. return possiblePayload if get(conf.supybot.reply.whenNotAddressed): return payload else: return '' def addressed(nick, msg, **kwargs): """If msg is addressed to 'name', returns the portion after the address. Otherwise returns the empty string. """ payload = msg.addressed if payload is not None: return payload else: payload = _addressed(nick, msg, **kwargs) msg.tag('addressed', payload) return payload def canonicalName(command, preserve_spaces=False): """Turn a command into its canonical form. Currently, this makes everything lowercase and removes all dashes and underscores. """ if minisix.PY2 and isinstance(command, unicode): command = command.encode('utf-8') elif minisix.PY3 and isinstance(command, bytes): command = command.decode() special = '\t-_' if not preserve_spaces: special += ' ' reAppend = '' while command and command[-1] in special: reAppend = command[-1] + reAppend command = command[:-1] return ''.join([x for x in command if x not in special]).lower() + reAppend def reply(*args, **kwargs): warnings.warn('callbacks.reply is deprecated. Use irc.reply instead.', DeprecationWarning) return _makeReply(dynamic.irc, *args, **kwargs) def _makeReply(irc, msg, s, prefixNick=None, private=None, notice=None, to=None, action=None, error=False, stripCtcp=True): msg.tag('repliedTo') # Ok, let's make the target: # XXX This isn't entirely right. Consider to=#foo, private=True. target = ircutils.replyTo(msg) def isPublic(s): return irc.isChannel(irc.stripChannelPrefix(s)) if to is not None and isPublic(to): target = to if isPublic(target): channel = irc.stripChannelPrefix(target) else: channel = None if notice is None: notice = conf.get(conf.supybot.reply.withNotice, channel=channel, network=irc.network) if private is None: private = conf.get(conf.supybot.reply.inPrivate, channel=channel, network=irc.network) if prefixNick is None: prefixNick = conf.get(conf.supybot.reply.withNickPrefix, channel=channel, network=irc.network) if error: notice =conf.get(conf.supybot.reply.error.withNotice, channel=channel, network=irc.network) or notice private=conf.get(conf.supybot.reply.error.inPrivate, channel=channel, network=irc.network) or private s = _('Error: ') + s if private: prefixNick = False if to is None: target = msg.nick else: target = to if action: prefixNick = False if to is None: to = msg.nick if stripCtcp: s = s.strip('\x01') # Ok, now let's make the payload: s = ircutils.safeArgument(s) if not s and not action: s = _('Error: I tried to send you an empty message.') if prefixNick and isPublic(target): # Let's may sure we don't do, "#channel: foo.". if not isPublic(to): s = '%s: %s' % (to, s) if not isPublic(target): if conf.supybot.reply.withNoticeWhenPrivate(): notice = True # And now, let's decide whether it's a PRIVMSG or a NOTICE. msgmaker = ircmsgs.privmsg if notice: msgmaker = ircmsgs.notice # We don't use elif here because actions can't be sent as NOTICEs. if action: msgmaker = ircmsgs.action # Finally, we'll return the actual message. ret = msgmaker(target, s) ret.tag('inReplyTo', msg) return ret def error(*args, **kwargs): warnings.warn('callbacks.error is deprecated. Use irc.error instead.', DeprecationWarning) return _makeErrorReply(dynamic.irc, *args, **kwargs) def _makeErrorReply(irc, msg, s, **kwargs): """Makes an error reply to msg with the appropriate error payload.""" kwargs['error'] = True msg.tag('isError') return _makeReply(irc, msg, s, **kwargs) def getHelp(method, name=None, doc=None): if name is None: name = method.__name__ if doc is None: if method.__doc__ is None: doclines = ['This command has no help. Complain to the author.'] else: doclines = method.__doc__.splitlines() else: doclines = doc.splitlines() s = '%s %s' % (name, doclines.pop(0)) if doclines: help = ' '.join(doclines) s = '(%s) -- %s' % (ircutils.bold(s), help) return utils.str.normalizeWhitespace(s) def getSyntax(method, name=None, doc=None): if name is None: name = method.__name__ if doc is None: doclines = method.__doc__.splitlines() else: doclines = doc.splitlines() return '%s %s' % (name, doclines[0]) class Error(Exception): """Generic class for errors in Privmsg callbacks.""" pass class ArgumentError(Error): """The bot replies with a help message when this is raised.""" pass class SilentError(Error): """An error that we should not notify the user.""" pass class Tokenizer(object): # This will be used as a global environment to evaluate strings in. # Evaluation is, of course, necessary in order to allow escaped # characters to be properly handled. # # These are the characters valid in a token. Everything printable except # double-quote, left-bracket, and right-bracket. separators = '\x00\r\n \t' def __init__(self, brackets='', pipe=False, quotes='"'): if brackets: self.separators += brackets self.left = brackets[0] self.right = brackets[1] else: self.left = '' self.right = '' self.pipe = pipe if self.pipe: self.separators += '|' self.quotes = quotes self.separators += quotes def _handleToken(self, token): if token[0] == token[-1] and token[0] in self.quotes: token = token[1:-1] # FIXME: No need to tell you this is a hack. # It has to handle both IRC commands and serialized configuration. # # Whoever you are, if you make a single modification to this # code, TEST the code with Python 2 & 3, both with the unit # tests and on IRC with this: @echo "好" if minisix.PY2: try: token = token.encode('utf8').decode('string_escape') token = token.decode('utf8') except: token = token.decode('string_escape') else: token = codecs.getencoder('utf8')(token)[0] token = codecs.getdecoder('unicode_escape')(token)[0] try: token = token.encode('iso-8859-1').decode() except: # Prevent issue with tokens like '"\\x80"'. pass return token def _insideBrackets(self, lexer): ret = [] while True: token = lexer.get_token() if not token: raise SyntaxError(_('Missing "%s". You may want to ' 'quote your arguments with double ' 'quotes in order to prevent extra ' 'brackets from being evaluated ' 'as nested commands.') % self.right) elif token == self.right: return ret elif token == self.left: ret.append(self._insideBrackets(lexer)) else: ret.append(self._handleToken(token)) return ret def tokenize(self, s): lexer = shlex.shlex(minisix.io.StringIO(s)) lexer.commenters = '' lexer.quotes = self.quotes lexer.separators = self.separators args = [] ends = [] while True: token = lexer.get_token() if not token: break elif token == '|' and self.pipe: # The "and self.pipe" might seem redundant here, but it's there # for strings like 'foo | bar', where a pipe stands alone as a # token, but shouldn't be treated specially. if not args: raise SyntaxError(_('"|" with nothing preceding. I ' 'obviously can\'t do a pipe with ' 'nothing before the |.')) ends.append(args) args = [] elif token == self.left: args.append(self._insideBrackets(lexer)) elif token == self.right: raise SyntaxError(_('Spurious "%s". You may want to ' 'quote your arguments with double ' 'quotes in order to prevent extra ' 'brackets from being evaluated ' 'as nested commands.') % self.right) else: args.append(self._handleToken(token)) if ends: if not args: raise SyntaxError(_('"|" with nothing following. I ' 'obviously can\'t do a pipe with ' 'nothing after the |.')) args.append(ends.pop()) while ends: args[-1].append(ends.pop()) return args def tokenize(s, channel=None, network=None): """A utility function to create a Tokenizer and tokenize a string.""" pipe = False brackets = '' nested = conf.supybot.commands.nested if nested(): brackets = nested.brackets.getSpecific(network, channel)() if conf.get(nested.pipeSyntax, channel=channel, network=network): # No nesting, no pipe. pipe = True quotes = conf.supybot.commands.quotes.getSpecific(network, channel)() try: ret = Tokenizer(brackets=brackets,pipe=pipe,quotes=quotes).tokenize(s) return ret except ValueError as e: raise SyntaxError(str(e)) def formatCommand(command): return ' '.join(command) def checkCommandCapability(msg, cb, commandName): plugin = cb.name().lower() if not isinstance(commandName, minisix.string_types): assert commandName[0] == plugin, ('checkCommandCapability no longer ' 'accepts command names that do not start with the callback\'s ' 'name (%s): %s') % (plugin, commandName) commandName = '.'.join(commandName) def checkCapability(capability): assert ircdb.isAntiCapability(capability) if ircdb.checkCapability(msg.prefix, capability): log.info('Preventing %s from calling %s because of %s.', msg.prefix, commandName, capability) raise RuntimeError(capability) try: antiCommand = ircdb.makeAntiCapability(commandName) checkCapability(antiCommand) checkAtEnd = [commandName] default = conf.supybot.capabilities.default() if msg.channel: channel = msg.channel checkCapability(ircdb.makeChannelCapability(channel, antiCommand)) chanCommand = ircdb.makeChannelCapability(channel, commandName) checkAtEnd += [chanCommand] default &= ircdb.channels.getChannel(channel).defaultAllow return not (default or \ any(lambda x: ircdb.checkCapability(msg.prefix, x), checkAtEnd)) except RuntimeError as e: s = ircdb.unAntiCapability(str(e)) return s class RichReplyMethods(object): """This is a mixin so these replies need only be defined once. It operates under several assumptions, including the fact that 'self' is an Irc object of some sort and there is a self.msg that is an IrcMsg.""" def __makeReply(self, prefix, s): if s: s = '%s %s' % (prefix, s) else: s = prefix return ircutils.standardSubstitute(self, self.msg, s) def _getConfig(self, wrapper): return conf.get(wrapper, channel=self.msg.channel, network=self.irc.network) def replySuccess(self, s='', **kwargs): v = self._getConfig(conf.supybot.replies.success) if v: s = self.__makeReply(v, s) return self.reply(s, **kwargs) else: self.noReply() def replyError(self, s='', **kwargs): v = self._getConfig(conf.supybot.replies.error) if 'msg' in kwargs: msg = kwargs['msg'] if ircdb.checkCapability(msg.prefix, 'owner'): v = self._getConfig(conf.supybot.replies.errorOwner) s = self.__makeReply(v, s) return self.reply(s, **kwargs) def _getTarget(self, to=None): """Compute the target according to self.to, the provided to, and self.private, and return it. Mainly used by reply methods.""" # FIXME: Don't set self.to. # I still set it to be sure I don't introduce a regression, # but it does not make sense for .reply() and .replies() to # change the state of this Irc object. if to is not None: self.to = self.to or to target = self.private and self.to or self.msg.args[0] return target def replies(self, L, prefixer=None, joiner=None, onlyPrefixFirst=False, oneToOne=None, **kwargs): if prefixer is None: prefixer = '' if joiner is None: joiner = utils.str.commaAndify if isinstance(prefixer, minisix.string_types): prefixer = prefixer.__add__ if isinstance(joiner, minisix.string_types): joiner = joiner.join to = self._getTarget(kwargs.get('to')) if oneToOne is None: # Can be True, False, or None if self.irc.isChannel(to): oneToOne = conf.get(conf.supybot.reply.oneToOne, channel=to, network=self.irc.network) else: oneToOne = conf.supybot.reply.oneToOne() if oneToOne: return self.reply(prefixer(joiner(L)), **kwargs) else: msg = None first = True for s in L: if onlyPrefixFirst: if first: first = False msg = self.reply(prefixer(s), **kwargs) else: msg = self.reply(s, **kwargs) else: msg = self.reply(prefixer(s), **kwargs) return msg def noReply(self, msg=None): self.repliedTo = True def _error(self, s, Raise=False, **kwargs): if Raise: raise Error(s) else: return self.error(s, **kwargs) def errorNoCapability(self, capability, s='', **kwargs): if 'Raise' not in kwargs: kwargs['Raise'] = True log.warning('Denying %s for lacking %q capability.', self.msg.prefix, capability) # noCapability means "don't send a specific capability error # message" not "don't send a capability error message at all", like # one would think if self._getConfig(conf.supybot.reply.error.noCapability) or \ capability in conf.supybot.capabilities.private(): v = self._getConfig(conf.supybot.replies.genericNoCapability) else: v = self._getConfig(conf.supybot.replies.noCapability) try: v %= capability except TypeError: # No %s in string pass s = self.__makeReply(v, s) if s: return self._error(s, **kwargs) elif kwargs['Raise']: raise Error() def errorPossibleBug(self, s='', **kwargs): v = self._getConfig(conf.supybot.replies.possibleBug) if s: s += ' (%s)' % v else: s = v return self._error(s, **kwargs) def errorNotRegistered(self, s='', **kwargs): v = self._getConfig(conf.supybot.replies.notRegistered) return self._error(self.__makeReply(v, s), **kwargs) def errorNoUser(self, s='', name='that user', **kwargs): if 'Raise' not in kwargs: kwargs['Raise'] = True v = self._getConfig(conf.supybot.replies.noUser) try: v = v % name except TypeError: log.warning('supybot.replies.noUser should have one "%s" in it.') return self._error(self.__makeReply(v, s), **kwargs) def errorRequiresPrivacy(self, s='', **kwargs): v = self._getConfig(conf.supybot.replies.requiresPrivacy) return self._error(self.__makeReply(v, s), **kwargs) def errorInvalid(self, what, given=None, s='', repr=True, **kwargs): if given is not None: if repr: given = _repr(given) else: given = '"%s"' % given v = _('%s is not a valid %s.') % (given, what) else: v = _('That\'s not a valid %s.') % what if 'Raise' not in kwargs: kwargs['Raise'] = True if s: v += ' ' + s return self._error(v, **kwargs) _repr = repr class ReplyIrcProxy(RichReplyMethods): """This class is a thin wrapper around an irclib.Irc object that gives it the reply() and error() methods (as well as everything in RichReplyMethods, based on those two).""" def __init__(self, irc, msg): self.irc = irc self.msg = msg self.getRealIrc()._setMsgChannel(self.msg) def getRealIrc(self): """Returns the real irclib.Irc object underlying this proxy chain.""" if isinstance(self.irc, irclib.Irc): return self.irc else: return self.irc.getRealIrc() # This should make us be considered equal to our irclib.Irc object for # hashing; an important thing (no more "too many open files" exceptions :)) def __hash__(self): return hash(self.getRealIrc()) def __eq__(self, other): return self.getRealIrc() == other __req__ = __eq__ def __ne__(self, other): return not (self == other) __rne__ = __ne__ def error(self, s, msg=None, **kwargs): if 'Raise' in kwargs and kwargs['Raise']: raise Error() if msg is None: msg = self.msg if s: m = _makeErrorReply(self, msg, s, **kwargs) self.irc.queueMsg(m) return m def reply(self, s, msg=None, **kwargs): if msg is None: msg = self.msg assert not isinstance(s, ircmsgs.IrcMsg), \ 'Old code alert: there is no longer a "msg" argument to reply.' kwargs.pop('noLengthCheck', None) m = _makeReply(self, msg, s, **kwargs) self.irc.queueMsg(m) return m def __getattr__(self, attr): return getattr(self.irc, attr) SimpleProxy = ReplyIrcProxy # Backwards-compatibility class NestedCommandsIrcProxy(ReplyIrcProxy): "A proxy object to allow proper nesting of commands (even threaded ones)." _mores = ircutils.IrcDict() def __init__(self, irc, msg, args, nested=0): assert isinstance(args, list), 'Args should be a list, not a string.' super(NestedCommandsIrcProxy, self).__init__(irc, msg) self.nested = nested self.repliedTo = False if not self.nested and isinstance(irc, self.__class__): # This means we were given an NestedCommandsIrcProxy instead of an # irclib.Irc, and so we're obviously nested. But nested wasn't # set! So we take our given Irc's nested value. self.nested += irc.nested maxNesting = conf.supybot.commands.nested.maximum() if maxNesting and self.nested > maxNesting: log.warning('%s attempted more than %s levels of nesting.', self.msg.prefix, maxNesting) self.error(_('You\'ve attempted more nesting than is ' 'currently allowed on this bot.')) return # The deepcopy here is necessary for Scheduler; it re-runs already # tokenized commands. There's a possibility a simple copy[:] would # work, but we're being careful. self.args = copy.deepcopy(args) self.counter = 0 self._resetReplyAttributes() if not args: self.finalEvaled = True self._callInvalidCommands() else: self.finalEvaled = False world.commandsProcessed += 1 self.evalArgs() def __eq__(self, other): return other == self.getRealIrc() def __hash__(self): return hash(self.getRealIrc()) def _resetReplyAttributes(self): self.to = None self.action = None self.notice = None self.private = None self.noLengthCheck = None if self.msg.channel: self.prefixNick = conf.get(conf.supybot.reply.withNickPrefix, channel=self.msg.channel, network=self.irc.network) else: self.prefixNick = conf.supybot.reply.withNickPrefix() def evalArgs(self, withClass=None): while self.counter < len(self.args): self.repliedTo = False if isinstance(self.args[self.counter], minisix.string_types): # If it's a string, just go to the next arg. There is no # evaluation to be done for strings. If, at some point, # we decided to, say, convert every string using # ircutils.standardSubstitute, this would be where we would # probably put it. self.counter += 1 else: assert isinstance(self.args[self.counter], list) # It's a list. So we spawn another NestedCommandsIrcProxy # to evaluate its args. When that class has finished # evaluating its args, it will call our reply method, which # will subsequently call this function again, and we'll # pick up where we left off via self.counter. cls = withClass or self.__class__ cls(self, self.msg, self.args[self.counter], nested=self.nested+1) # We have to return here because the new NestedCommandsIrcProxy # might not have called our reply method instantly, since # its command might be threaded. So (obviously) we can't # just fall through to self.finalEval. return # Once all the list args are evaluated, we then evaluate our own # list of args, since we're assured that they're all strings now. assert all(lambda x: isinstance(x, minisix.string_types), self.args) self.finalEval() def _callInvalidCommands(self): log.debug('Calling invalidCommands.') threaded = False cbs = [] for cb in self.irc.callbacks: if hasattr(cb, 'invalidCommand'): cbs.append(cb) threaded = threaded or cb.threaded def callInvalidCommands(): self.repliedTo = False for cb in cbs: log.debug('Calling %s.invalidCommand.', cb.name()) try: cb.invalidCommand(self, self.msg, self.args) except Error as e: self.error(str(e)) except Exception as e: log.exception('Uncaught exception in %s.invalidCommand.', cb.name()) log.debug('Finished calling %s.invalidCommand.', cb.name()) if self.repliedTo: log.debug('Done calling invalidCommands: %s.',cb.name()) return if threaded: name = 'Thread #%s (for invalidCommands)' % world.threadsSpawned t = world.SupyThread(target=callInvalidCommands, name=name) t.setDaemon(True) t.start() else: callInvalidCommands() def findCallbacksForArgs(self, args): """Returns a two-tuple of (command, plugins) that has the command (a list of strings) and the plugins for which it was a command.""" assert isinstance(args, list) args = list(map(canonicalName, args)) cbs = [] maxL = [] for cb in self.irc.callbacks: if not hasattr(cb, 'getCommand'): continue L = cb.getCommand(args) #log.debug('%s.getCommand(%r) returned %r', cb.name(), args, L) if L and L >= maxL: maxL = L cbs.append((cb, L)) assert isinstance(L, list), \ 'getCommand now returns a list, not a method.' assert utils.iter.startswith(L, args), \ 'getCommand must return a prefix of the args given. ' \ '(args given: %r, returned: %r)' % (args, L) log.debug('findCallbacksForArgs: %r', cbs) cbs = [cb for (cb, L) in cbs if L == maxL] if len(maxL) == 1: # Special case: one arg determines the callback. In this case, we # have to check, in order: # 1. Whether the arg is the same as the name of a callback. This # callback would then win. for cb in cbs: if cb.canonicalName() == maxL[0]: return (maxL, [cb]) # 2. Whether a defaultplugin is defined. defaultPlugins = conf.supybot.commands.defaultPlugins try: defaultPlugin = defaultPlugins.get(maxL[0])() log.debug('defaultPlugin: %r', defaultPlugin) if defaultPlugin: cb = self.irc.getCallback(defaultPlugin) if cb in cbs: # This is just a sanity check, but there's a small # possibility that a default plugin for a command # is configured to point to a plugin that doesn't # actually have that command. return (maxL, [cb]) except registry.NonExistentRegistryEntry: pass # 3. Whether an importantPlugin is one of the responses. important = defaultPlugins.importantPlugins() important = list(map(canonicalName, important)) importants = [] for cb in cbs: if cb.canonicalName() in important: importants.append(cb) if len(importants) == 1: return (maxL, importants) return (maxL, cbs) def finalEval(self): # Now that we've already iterated through our args and made sure # that any list of args was evaluated (by spawning another # NestedCommandsIrcProxy to evaluated it into a string), we can finally # evaluated our own list of arguments. assert not self.finalEvaled, 'finalEval called twice.' self.finalEvaled = True # Now, the way we call a command is we iterate over the loaded pluings, # asking each one if the list of args we have interests it. The # way we do that is by calling getCommand on the plugin. # The plugin will return a list of args which it considers to be # "interesting." We will then give our args to the plugin which # has the *longest* list. The reason we pick the longest list is # that it seems reasonable that the longest the list, the more # specific the command is. That is, given a list of length X, a list # of length X+1 would be even more specific (assuming that both lists # used the same prefix. Of course, if two plugins return a list of the # same length, we'll just error out with a message about ambiguity. (command, cbs) = self.findCallbacksForArgs(self.args) if not cbs: # We used to handle addressedRegexps here, but I think we'll let # them handle themselves in getCommand. They can always just # return the full list of args as their "command". self._callInvalidCommands() elif len(cbs) > 1: names = sorted([cb.name() for cb in cbs]) command = formatCommand(command) self.error(format(_('The command %q is available in the %L ' 'plugins. Please specify the plugin ' 'whose command you wish to call by using ' 'its name as a command before %q.'), command, names, command)) else: cb = cbs[0] args = self.args[len(command):] if world.isMainThread() and \ (cb.threaded or conf.supybot.debug.threadAllCommands()): t = CommandThread(target=cb._callCommand, args=(command, self, self.msg, args)) t.start() else: cb._callCommand(command, self, self.msg, args) def reply(self, s, noLengthCheck=False, prefixNick=None, action=None, private=None, notice=None, to=None, msg=None, sendImmediately=False, stripCtcp=True): """ Keyword arguments: * `noLengthCheck=False`: True if the length shouldn't be checked (used for 'more' handling) * `prefixNick=True`: False if the nick shouldn't be prefixed to the reply. * `action=False`: True if the reply should be an action. * `private=False`: True if the reply should be in private. * `notice=False`: True if the reply should be noticed when the bot is configured to do so. * `to=`: The nick or channel the reply should go to. Defaults to msg.args[0] (or msg.nick if private) * `sendImmediately=False`: True if the reply should use sendMsg() which bypasses conf.supybot.protocols.irc.throttleTime and gets sent before any queued messages """ # These use and or or based on whether or not they default to True or # False. Those that default to True use and; those that default to # False use or. assert not isinstance(s, ircmsgs.IrcMsg), \ 'Old code alert: there is no longer a "msg" argument to reply.' self.repliedTo = True if sendImmediately: sendMsg = self.irc.sendMsg else: sendMsg = self.irc.queueMsg if msg is None: msg = self.msg if prefixNick is not None: self.prefixNick = prefixNick if action is not None: self.action = self.action or action if action: self.prefixNick = False if notice is not None: self.notice = self.notice or notice if private is not None: self.private = self.private or private target = self._getTarget(to) # action=True implies noLengthCheck=True and prefixNick=False self.noLengthCheck=noLengthCheck or self.noLengthCheck or self.action if not isinstance(s, minisix.string_types): # avoid trying to str() unicode s = str(s) # Allow non-string esses. if self.finalEvaled: try: if isinstance(self.irc, self.__class__): s = s[:conf.supybot.reply.maximumLength()] return self.irc.reply(s, to=self.to, notice=self.notice, action=self.action, private=self.private, prefixNick=self.prefixNick, noLengthCheck=self.noLengthCheck, stripCtcp=stripCtcp) elif self.noLengthCheck: # noLengthCheck only matters to NestedCommandsIrcProxy, so # it's not used here. Just in case you were wondering. m = _makeReply(self, msg, s, to=self.to, notice=self.notice, action=self.action, private=self.private, prefixNick=self.prefixNick, stripCtcp=stripCtcp) sendMsg(m) return m else: s = ircutils.safeArgument(s) allowedLength = conf.get(conf.supybot.reply.mores.length, channel=target, network=self.irc.network) if not allowedLength: # 0 indicates this. allowedLength = (512 - len(':') - len(self.irc.prefix) - len(' PRIVMSG ') - len(target) - len(' :') - len('\r\n') ) if self.prefixNick: allowedLength -= len(msg.nick) + len(': ') maximumMores = conf.get(conf.supybot.reply.mores.maximum, channel=target, network=self.irc.network) maximumLength = allowedLength * maximumMores if len(s) > maximumLength: log.warning('Truncating to %s bytes from %s bytes.', maximumLength, len(s)) s = s[:maximumLength] s_size = len(s.encode()) if minisix.PY3 else len(s) if s_size <= allowedLength or \ not conf.get(conf.supybot.reply.mores, channel=target, network=self.irc.network): # There's no need for action=self.action here because # action implies noLengthCheck, which has already been # handled. Let's stick an assert in here just in case. assert not self.action m = _makeReply(self, msg, s, to=self.to, notice=self.notice, private=self.private, prefixNick=self.prefixNick, stripCtcp=stripCtcp) sendMsg(m) return m # The '(XX more messages)' may have not the same # length in the current locale allowedLength -= len(_('(XX more messages)')) + 1 # bold msgs = ircutils.wrap(s, allowedLength) msgs.reverse() instant = conf.get(conf.supybot.reply.mores.instant, channel=target, network=self.irc.network) while instant > 1 and msgs: instant -= 1 response = msgs.pop() m = _makeReply(self, msg, response, to=self.to, notice=self.notice, private=self.private, prefixNick=self.prefixNick, stripCtcp=stripCtcp) sendMsg(m) # XXX We should somehow allow these to be returned, but # until someone complains, we'll be fine :) We # can't return from here, though, for obvious # reasons. # return m if not msgs: return response = msgs.pop() if msgs: if len(msgs) == 1: more = _('more message') else: more = _('more messages') n = ircutils.bold('(%i %s)' % (len(msgs), more)) response = '%s %s' % (response, n) prefix = msg.prefix if self.to and ircutils.isNick(self.to): try: state = self.getRealIrc().state prefix = state.nickToHostmask(self.to) except KeyError: pass # We'll leave it as it is. mask = prefix.split('!', 1)[1] self._mores[mask] = msgs public = bool(self.msg.channel) private = self.private or not public self._mores[msg.nick] = (private, msgs) m = _makeReply(self, msg, response, to=self.to, action=self.action, notice=self.notice, private=self.private, prefixNick=self.prefixNick, stripCtcp=stripCtcp) sendMsg(m) return m finally: self._resetReplyAttributes() else: if msg.ignored: # Since the final reply string is constructed via # ' '.join(self.args), the args index for ignored commands # needs to be popped to avoid extra spaces in the final reply. self.args.pop(self.counter) msg.tag('ignored', False) else: self.args[self.counter] = s self.evalArgs() def noReply(self, msg=None): if msg is None: msg = self.msg super(NestedCommandsIrcProxy, self).noReply(msg=msg) if self.finalEvaled: if isinstance(self.irc, NestedCommandsIrcProxy): self.irc.noReply(msg=msg) else: msg.tag('ignored', True) else: self.args.pop(self.counter) msg.tag('ignored', False) self.evalArgs() def replies(self, L, prefixer=None, joiner=None, onlyPrefixFirst=False, to=None, oneToOne=None, **kwargs): if not self.finalEvaled and oneToOne is None: oneToOne = True return super(NestedCommandsIrcProxy, self).replies(L, prefixer=prefixer, joiner=joiner, onlyPrefixFirst=onlyPrefixFirst, to=to, oneToOne=oneToOne, **kwargs) def error(self, s='', Raise=False, **kwargs): self.repliedTo = True if Raise: raise Error(s) if not isinstance(self.irc, irclib.Irc): return self.irc.error(s, **kwargs) elif s: m = _makeErrorReply(self, self.msg, s, **kwargs) self.irc.queueMsg(m) return m def __getattr__(self, attr): return getattr(self.irc, attr) IrcObjectProxy = NestedCommandsIrcProxy class CommandThread(world.SupyThread): """Just does some extra logging and error-recovery for commands that need to run in threads. """ def __init__(self, target=None, args=(), kwargs={}): self.command = args[0] self.cb = target.__self__ threadName = 'Thread #%s (for %s.%s)' % (world.threadsSpawned, self.cb.name(), self.command) log.debug('Spawning thread %s (args: %r)', threadName, args) self.__parent = super(CommandThread, self) self.__parent.__init__(target=target, name=threadName, args=args, kwargs=kwargs) self.setDaemon(True) self.originalThreaded = self.cb.threaded self.cb.threaded = True def run(self): try: self.__parent.run() finally: self.cb.threaded = self.originalThreaded class CommandProcess(world.SupyProcess): """Just does some extra logging and error-recovery for commands that need to run in processes. """ def __init__(self, target=None, args=(), kwargs={}): pn = kwargs.pop('pn', 'Unknown') cn = kwargs.pop('cn', 'unknown') procName = 'Process #%s (for %s.%s)' % (world.processesSpawned, pn, cn) log.debug('Spawning process %s (args: %r)', procName, args) self.__parent = super(CommandProcess, self) self.__parent.__init__(target=target, name=procName, args=args, kwargs=kwargs) def run(self): self.__parent.run() class CanonicalString(registry.NormalizedString): def normalize(self, s): return canonicalName(s) class CanonicalNameSet(utils.NormalizingSet): def normalize(self, s): return canonicalName(s) class CanonicalNameDict(utils.InsensitivePreservingDict): def key(self, s): return canonicalName(s) class Disabled(registry.SpaceSeparatedListOf): sorted = True Value = CanonicalString List = CanonicalNameSet conf.registerGlobalValue(conf.supybot.commands, 'disabled', Disabled([], _("""Determines what commands are currently disabled. Such commands will not appear in command lists, etc. They will appear not even to exist."""))) class DisabledCommands(object): def __init__(self): self.d = CanonicalNameDict() for name in conf.supybot.commands.disabled(): if '.' in name: (plugin, command) = name.split('.', 1) if command in self.d: if self.d[command] is not None: self.d[command].add(plugin) else: self.d[command] = CanonicalNameSet([plugin]) else: self.d[name] = None def disabled(self, command, plugin=None): if command in self.d: if self.d[command] is None: return True elif plugin in self.d[command]: return True return False def add(self, command, plugin=None): if plugin is None: self.d[command] = None else: if command in self.d: if self.d[command] is not None: self.d[command].add(plugin) else: self.d[command] = CanonicalNameSet([plugin]) def remove(self, command, plugin=None): if plugin is None: del self.d[command] else: if self.d[command] is not None: self.d[command].remove(plugin) class BasePlugin(object): def __init__(self, *args, **kwargs): self.cbs = [] for attr in dir(self): if attr != canonicalName(attr): continue obj = getattr(self, attr) if isinstance(obj, type) and issubclass(obj, BasePlugin): cb = obj(*args, **kwargs) setattr(self, attr, cb) self.cbs.append(cb) cb.log = log.getPluginLogger('%s.%s' % (self.name(),cb.name())) super(BasePlugin, self).__init__() class MetaSynchronizedAndFirewalled(log.MetaFirewall, utils.python.MetaSynchronized): pass SynchronizedAndFirewalled = MetaSynchronizedAndFirewalled( 'SynchronizedAndFirewalled', (), {}) class Commands(BasePlugin, SynchronizedAndFirewalled): __synchronized__ = ( '__call__', 'callCommand', 'invalidCommand', ) # For a while, a comment stood here to say, "Eventually callCommand." But # that's wrong, because we can't do generic error handling in this # callCommand -- plugins need to be able to override callCommand and do # error handling there (see the Web plugin for an example). __firewalled__ = {'isCommand': None, '_callCommand': None} commandArgs = ['self', 'irc', 'msg', 'args'] # These must be class-scope, so all plugins use the same one. _disabled = DisabledCommands() pre_command_callbacks = [] def name(self): return self.__class__.__name__ def canonicalName(self): return canonicalName(self.name()) def isDisabled(self, command): return self._disabled.disabled(command, self.name()) def isCommandMethod(self, name): """Returns whether a given method name is a command in this plugin.""" # This function is ugly, but I don't want users to call methods like # doPrivmsg or __init__ or whatever, and this is good to stop them. # Don't normalize this name: consider outFilter(self, irc, msg). # name = canonicalName(name) if self.isDisabled(name): return False if name != canonicalName(name): return False if hasattr(self, name): method = getattr(self, name) if inspect.ismethod(method): code = method.__func__.__code__ return inspect.getargs(code)[0] == self.commandArgs else: return False else: return False def isCommand(self, command): """Convenience, backwards-compatibility, semi-deprecated.""" if isinstance(command, minisix.string_types): return self.isCommandMethod(command) else: # Since we're doing a little type dispatching here, let's not be # too liberal. assert isinstance(command, list) return self.getCommand(command) == command def getCommand(self, args, stripOwnName=True): assert args == list(map(canonicalName, args)) first = args[0] for cb in self.cbs: if first == cb.canonicalName(): return cb.getCommand(args) if first == self.canonicalName() and len(args) > 1 and \ stripOwnName: ret = self.getCommand(args[1:], stripOwnName=False) if ret: return [first] + ret if self.isCommandMethod(first): return [first] return [] def getCommandMethod(self, command): """Gets the given command from this plugin.""" #print '*** %s.getCommandMethod(%r)' % (self.name(), command) assert not isinstance(command, minisix.string_types) assert command == list(map(canonicalName, command)) assert self.getCommand(command) == command for cb in self.cbs: if command[0] == cb.canonicalName(): return cb.getCommandMethod(command) if len(command) > 1: assert command[0] == self.canonicalName() return self.getCommandMethod(command[1:]) else: method = getattr(self, command[0]) if inspect.ismethod(method): code = method.__func__.__code__ if inspect.getargs(code)[0] == self.commandArgs: return method else: raise AttributeError def listCommands(self, pluginCommands=[]): commands = set(pluginCommands) for s in dir(self): if self.isCommandMethod(s): commands.add(s) for cb in self.cbs: name = cb.canonicalName() for command in cb.listCommands(): if command == name: commands.add(command) else: commands.add(' '.join([name, command])) L = list(commands) L.sort() return L def callCommand(self, command, irc, msg, *args, **kwargs): # We run all callbacks before checking if one of them returned True if any(bool, list(cb(self, command, irc, msg, *args, **kwargs) for cb in self.pre_command_callbacks)): return method = self.getCommandMethod(command) method(irc, msg, *args, **kwargs) def _callCommand(self, command, irc, msg, *args, **kwargs): if irc.nick == msg.args[0]: self.log.info('%s called in private by %q.', formatCommand(command), msg.prefix) else: self.log.info('%s called on %s by %q.', formatCommand(command), msg.args[0], msg.prefix) try: if len(command) == 1 or command[0] != self.canonicalName(): fullCommandName = [self.canonicalName()] + command else: fullCommandName = command # Let "P" be the plugin and "X Y" the command name. The # fullCommandName is "P X Y" # check "Y" cap = checkCommandCapability(msg, self, command[-1]) if cap: irc.errorNoCapability(cap) return # check "P", "P.X", and "P.X.Y" prefix = [] for name in fullCommandName: prefix.append(name) cap = checkCommandCapability(msg, self, prefix) if cap: irc.errorNoCapability(cap) return try: self.callingCommand = command self.callCommand(command, irc, msg, *args, **kwargs) finally: self.callingCommand = None except SilentError: pass except (getopt.GetoptError, ArgumentError) as e: self.log.debug('Got %s, giving argument error.', utils.exnToString(e)) help = self.getCommandHelp(command) if 'command has no help.' in help: # Note: this case will never happen, unless 'checkDoc' is set # to False. irc.error(_('Invalid arguments for %s.') % formatCommand(command)) else: irc.reply(help) except (SyntaxError, Error) as e: self.log.debug('Error return: %s', utils.exnToString(e)) irc.error(str(e)) except Exception as e: self.log.exception('Uncaught exception in %s.', command) if conf.supybot.reply.error.detailed(): irc.error(utils.exnToString(e)) else: irc.replyError(msg=msg) def getCommandHelp(self, command, simpleSyntax=None): method = self.getCommandMethod(command) help = getHelp chan = None net = None if dynamic.msg is not None: chan = dynamic.msg.channel if dynamic.irc is not None: net = dynamic.irc.network if simpleSyntax is None: simpleSyntax = conf.get(conf.supybot.reply.showSimpleSyntax, channel=chan, network=net) if simpleSyntax: help = getSyntax if hasattr(method, '__doc__'): return help(method, name=formatCommand(command)) else: return format(_('The %q command has no help.'), formatCommand(command)) class PluginMixin(BasePlugin, irclib.IrcCallback): public = True alwaysCall = () threaded = False noIgnore = False classModule = None Proxy = NestedCommandsIrcProxy def __init__(self, irc): myName = self.name() self.log = log.getPluginLogger(myName) self.__parent = super(PluginMixin, self) self.__parent.__init__(irc) # We can't do this because of the specialness that Owner and Misc do. # I guess plugin authors will have to get the capitalization right. # self.callAfter = map(str.lower, self.callAfter) # self.callBefore = map(str.lower, self.callBefore) def canonicalName(self): return canonicalName(self.name()) def __call__(self, irc, msg): irc = SimpleProxy(irc, msg) if msg.command == 'PRIVMSG': if hasattr(self.noIgnore, '__call__'): noIgnore = self.noIgnore(irc, msg) else: noIgnore = self.noIgnore if noIgnore or \ not ircdb.checkIgnored(msg.prefix, msg.channel) or \ not ircutils.isUserHostmask(msg.prefix): # Some services impl. self.__parent.__call__(irc, msg) else: self.__parent.__call__(irc, msg) def registryValue(self, name, channel=None, network=None, value=True): if isinstance(network, bool): # Network-unaware plugin that uses 'value' as a positional # argument. (network, value) = (value, network) plugin = self.name() group = conf.supybot.plugins.get(plugin) names = registry.split(name) for name in names: group = group.get(name) if channel or network: group = group.getSpecific(network=network, channel=channel) if value: return group() else: return group def setRegistryValue(self, name, value, channel=None, network=None): plugin = self.name() group = conf.supybot.plugins.get(plugin) names = registry.split(name) for name in names: group = group.get(name) if network: group = group.get(':' + network) if channel: group = group.get(channel) group.setValue(value) def userValue(self, name, prefixOrName, default=None): try: id = str(ircdb.users.getUserId(prefixOrName)) except KeyError: return None plugin = self.name() group = conf.users.plugins.get(plugin) names = registry.split(name) for name in names: group = group.get(name) return group.get(id)() def setUserValue(self, name, prefixOrName, value, ignoreNoUser=True, setValue=True): try: id = str(ircdb.users.getUserId(prefixOrName)) except KeyError: if ignoreNoUser: return else: raise plugin = self.name() group = conf.users.plugins.get(plugin) names = registry.split(name) for name in names: group = group.get(name) group = group.get(id) if setValue: group.setValue(value) else: group.set(value) def getPluginHelp(self): if hasattr(self, '__doc__'): return self.__doc__ else: return None class Plugin(PluginMixin, Commands): pass Privmsg = Plugin # Backwards compatibility. class PluginRegexp(Plugin): """Same as Plugin, except allows the user to also include regexp-based callbacks. All regexp-based callbacks must be specified in the set (or list) attribute "regexps", "addressedRegexps", or "unaddressedRegexps" depending on whether they should always be triggered, triggered only when the bot is addressed, or triggered only when the bot isn't addressed. """ flags = re.I regexps = () """'regexps' methods are called whether the message is addressed or not.""" addressedRegexps = () """'addressedRegexps' methods are called only when the message is addressed, and then, only with the payload (i.e., what is returned from the 'addressed' function.""" unaddressedRegexps = () """'unaddressedRegexps' methods are called only when the message is *not* addressed.""" Proxy = SimpleProxy def __init__(self, irc): self.__parent = super(PluginRegexp, self) self.__parent.__init__(irc) self.res = [] self.addressedRes = [] self.unaddressedRes = [] for name in self.regexps: method = getattr(self, name) r = re.compile(method.__doc__, self.flags) self.res.append((r, name)) for name in self.addressedRegexps: method = getattr(self, name) r = re.compile(method.__doc__, self.flags) self.addressedRes.append((r, name)) for name in self.unaddressedRegexps: method = getattr(self, name) r = re.compile(method.__doc__, self.flags) self.unaddressedRes.append((r, name)) def _callRegexp(self, name, irc, msg, m): method = getattr(self, name) try: method(irc, msg, m) except Error as e: irc.error(str(e)) except Exception as e: self.log.exception('Uncaught exception in _callRegexp:') def invalidCommand(self, irc, msg, tokens): s = ' '.join(tokens) for (r, name) in self.addressedRes: for m in r.finditer(s): self._callRegexp(name, irc, msg, m) def doPrivmsg(self, irc, msg): if msg.isError: return proxy = self.Proxy(irc, msg) if not msg.addressed: for (r, name) in self.unaddressedRes: for m in r.finditer(msg.args[1]): self._callRegexp(name, proxy, msg, m) for (r, name) in self.res: for m in r.finditer(msg.args[1]): self._callRegexp(name, proxy, msg, m) PrivmsgCommandAndRegexp = PluginRegexp # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/cdb.py0000644000175000017500000003513613634634532015220 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Database module, similar to dbhash. Uses a format similar to (if not entirely the same as) DJB's CDB . """ from __future__ import division import os import sys import struct import os.path from . import utils from .utils import minisix def hash(s): """DJB's hash function for CDB.""" h = 5381 for c in s: h = ((h + (h << 5)) ^ ord(c)) & minisix.L(0xFFFFFFFF) return h def unpack2Ints(s): """Returns two ints unpacked from the binary string s.""" return struct.unpack('%s\n' % (len(key), len(value), key, value)) def open_db(filename, mode='r', **kwargs): """Opens a database; used for compatibility with other database modules.""" if mode == 'r': return Reader(filename, **kwargs) elif mode == 'w': return ReaderWriter(filename, **kwargs) elif mode == 'c': if os.path.exists(filename): return ReaderWriter(filename, **kwargs) else: maker = Maker(filename) maker.finish() return ReaderWriter(filename, **kwargs) elif mode == 'n': maker = Maker(filename) maker.finish() return ReaderWriter(filename, **kwargs) else: raise ValueError('Invalid flag: %s' % mode) def shelf(filename, *args, **kwargs): """Opens a new shelf database object.""" if os.path.exists(filename): return Shelf(filename, *args, **kwargs) else: maker = Maker(filename) maker.finish() return Shelf(filename, *args, **kwargs) def _readKeyValue(fd): klen = 0 dlen = 0 s = initchar = fd.read(1) if s == '': return (None, None, None) s = fd.read(1) while s != ',': klen = 10 * klen + int(s) s = fd.read(1) s = fd.read(1) while s != ':': dlen = 10 * dlen + int(s) s = fd.read(1) key = fd.read(klen) assert fd.read(2) == '->' value = fd.read(dlen) assert fd.read(1) == '\n' return (initchar, key, value) def make(dbFilename, readFilename=None): """Makes a database from the filename, otherwise uses stdin.""" if readFilename is None: readfd = sys.stdin else: readfd = open(readFilename, 'rb') maker = Maker(dbFilename) while True: (initchar, key, value) = _readKeyValue(readfd) if initchar is None: break assert initchar == '+' maker.add(key, value) readfd.close() maker.finish() class Maker(object): """Class for making CDB databases.""" def __init__(self, filename): self.fd = utils.file.AtomicFile(filename, 'wb') self.filename = filename self.fd.seek(2048) self.hashPointers = [(0, 0)] * 256 #self.hashes = [[]] * 256 # Can't use this, [] stays the same... self.hashes = [] for _ in range(256): self.hashes.append([]) def add(self, key, data): """Adds a key->value pair to the database.""" h = hash(key) hashPointer = h % 256 startPosition = self.fd.tell() self.fd.write(pack2Ints(len(key), len(data))) self.fd.write(key.encode()) self.fd.write(data.encode()) self.hashes[hashPointer].append((h, startPosition)) def finish(self): """Finishes the current Maker object. Writes the remainder of the database to disk. """ for i in range(256): hash = self.hashes[i] self.hashPointers[i] = (self.fd.tell(), self._serializeHash(hash)) self._serializeHashPointers() self.fd.flush() self.fd.close() def _serializeHash(self, hash): hashLen = len(hash) * 2 a = [(0, 0)] * hashLen for (h, pos) in hash: i = (h // 256) % hashLen while a[i] != (0, 0): i = (i + 1) % hashLen a[i] = (h, pos) for (h, pos) in a: self.fd.write(pack2Ints(h, pos)) return hashLen def _serializeHashPointers(self): self.fd.seek(0) for (hashPos, hashLen) in self.hashPointers: self.fd.write(pack2Ints(hashPos, hashLen)) class Reader(utils.IterableMap): """Class for reading from a CDB database.""" def __init__(self, filename): self.filename = filename self.fd = open(filename, 'rb') self.loop = 0 self.khash = 0 self.kpos = 0 self.hpos = 0 self.hslots = 0 self.dpos = 0 self.dlen = 0 def close(self): self.fd.close() def _read(self, len, pos): self.fd.seek(pos) return self.fd.read(len) def _match(self, key, pos): return self._read(len(key), pos) == key def items(self): # uses loop/hslots in a strange, non-re-entrant manner. (self.loop,) = struct.unpack(' self.maxmods: self.flush() self.mods = 0 elif isinstance(self.maxmods, float): assert 0 <= self.maxmods if self.mods / max(len(self.cdb), 100) > self.maxmods: self.flush() self.mods = 0 def __getitem__(self, key): if key in self.removals: raise KeyError(key) else: try: return self.adds[key] except KeyError: return self.cdb[key] # If this raises KeyError, we lack key. def __delitem__(self, key): if key in self.removals: raise KeyError(key) else: if key in self.adds and key in self.cdb: self._journalRemoveKey(key) del self.adds[key] self.removals.add(key) elif key in self.adds: self._journalRemoveKey(key) del self.adds[key] elif key in self.cdb: self._journalRemoveKey(key) else: raise KeyError(key) self.mods += 1 self._flushIfOverLimit() def __setitem__(self, key, value): if key in self.removals: self.removals.remove(key) self._journalAddKey(key, value) self.adds[key] = value self.mods += 1 self._flushIfOverLimit() def __contains__(self, key): if key in self.removals: return False else: return key in self.adds or key in self.cdb has_key = __contains__ def items(self): already = set() for (key, value) in self.cdb.items(): if key in self.removals or key in already: continue elif key in self.adds: already.add(key) yield (key, self.adds[key]) else: yield (key, value) for (key, value) in self.adds.items(): if key not in already: yield (key, value) def setdefault(self, key, value): try: return self[key] except KeyError: self[key] = value return value def get(self, key, default=None): try: return self[key] except KeyError: return default class Shelf(ReaderWriter): """Uses pickle to mimic the shelf module.""" def __getitem__(self, key): return minisix.pickle.loads(ReaderWriter.__getitem__(self, key)) def __setitem__(self, key, value): ReaderWriter.__setitem__(self, key, minisix.pickle.dumps(value, True)) def items(self): for (key, value) in ReaderWriter.items(self): yield (key, minisix.pickle.loads(value)) if __name__ == '__main__': if sys.argv[0] == 'cdbdump': if len(sys.argv) == 2: fd = open(sys.argv[1], 'rb') else: fd = sys.stdin db = Reader(fd) dump(db) elif sys.argv[0] == 'cdbmake': if len(sys.argv) == 2: make(sys.argv[1]) else: make(sys.argv[1], sys.argv[2]) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/commands.py0000644000175000017500000011531713634634532016271 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009-2010,2015, James McCoy # 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. ### """ Includes wrappers for commands. """ import time import getopt import inspect import threading import multiprocessing #python2.6 or later! try: import resource except ImportError: # Windows! resource = None from . import callbacks, conf, ircdb, ircmsgs, ircutils, log, \ utils, world from .utils import minisix from .i18n import PluginInternationalization, internationalizeDocstring _ = PluginInternationalization() ### # Non-arg wrappers -- these just change the behavior of a command without # changing the arguments given to it. ### # Thread has to be a non-arg wrapper because by the time we're parsing and # validating arguments, we're inside the function we'd want to thread. def thread(f): """Makes sure a command spawns a thread when called.""" def newf(self, irc, msg, args, *L, **kwargs): if world.isMainThread(): targetArgs = (self.callingCommand, irc, msg, args) + tuple(L) t = callbacks.CommandThread(target=self._callCommand, args=targetArgs, kwargs=kwargs) t.start() else: f(self, irc, msg, args, *L, **kwargs) return utils.python.changeFunctionName(newf, f.__name__, f.__doc__) class ProcessTimeoutError(Exception): """Gets raised when a process is killed due to timeout.""" pass def _rlimit_min(a, b): if a == resource.RLIM_INFINITY: return b elif b == resource.RLIM_INFINITY: return a else: return min(soft, heap_size) def process(f, *args, **kwargs): """Runs a function in a subprocess. Several extra keyword arguments can be supplied. , the pluginname, and , the command name, are strings used to create the process name, for identification purposes. , if supplied, limits the length of execution of target function to seconds. , if supplied, limits the memory used by the target function.""" timeout = kwargs.pop('timeout', None) heap_size = kwargs.pop('heap_size', None) if resource and heap_size is None: heap_size = resource.RLIM_INFINITY if world.disableMultiprocessing: pn = kwargs.pop('pn', 'Unknown') cn = kwargs.pop('cn', 'unknown') try: return f(*args, **kwargs) except Exception as e: raise e try: q = multiprocessing.Queue() except OSError: log.error('Using multiprocessing.Queue raised an OSError.\n' 'This is probably caused by your system denying semaphore\n' 'usage. You should run these two commands:\n' '\tsudo rmdir /dev/shm\n' '\tsudo ln -Tsf /{run,dev}/shm\n' '(See https://github.com/travis-ci/travis-core/issues/187\n' 'for more information about this bug.)\n') raise def newf(f, q, *args, **kwargs): if resource: rsrc = resource.RLIMIT_DATA (soft, hard) = resource.getrlimit(rsrc) soft = _rlimit_min(soft, heap_size) hard = _rlimit_min(hard, heap_size) resource.setrlimit(rsrc, (soft, hard)) try: r = f(*args, **kwargs) q.put([False, r]) except Exception as e: q.put([True, e]) targetArgs = (f, q,) + args p = callbacks.CommandProcess(target=newf, args=targetArgs, kwargs=kwargs) p.start() p.join(timeout) if p.is_alive(): p.terminate() q.close() raise ProcessTimeoutError("%s aborted due to timeout." % (p.name,)) try: raised, v = q.get(block=False) except minisix.queue.Empty: return None finally: q.close() if raised: raise v else: return v def regexp_wrapper(s, reobj, timeout, plugin_name, fcn_name): '''A convenient wrapper to stuff regexp search queries through a subprocess. This is used because specially-crafted regexps can use exponential time and hang the bot.''' def re_bool(s, reobj): """Since we can't enqueue match objects into the multiprocessing queue, we'll just wrap the function to return bools.""" if reobj.search(s) is not None: return True else: return False try: v = process(re_bool, s, reobj, timeout=timeout, pn=plugin_name, cn=fcn_name) return v except ProcessTimeoutError: return None class UrlSnarfThread(world.SupyThread): def __init__(self, *args, **kwargs): assert 'url' in kwargs kwargs['name'] = 'Thread #%s (for snarfing %s)' % \ (world.threadsSpawned, kwargs.pop('url')) super(UrlSnarfThread, self).__init__(*args, **kwargs) self.setDaemon(True) def run(self): try: super(UrlSnarfThread, self).run() except utils.web.Error as e: log.debug('Exception in urlSnarfer: %s', utils.exnToString(e)) class SnarfQueue(ircutils.FloodQueue): timeout = conf.supybot.snarfThrottle def key(self, channel): return channel _snarfed = SnarfQueue() class SnarfIrc(object): def __init__(self, irc, channel, url): self.irc = irc self.url = url self.channel = channel def __getattr__(self, attr): return getattr(self.irc, attr) def reply(self, *args, **kwargs): _snarfed.enqueue(self.channel, self.url) return self.irc.reply(*args, **kwargs) # This lock is used to serialize the calls to snarfers, so # earlier snarfers are guaranteed to beat out later snarfers. _snarfLock = threading.Lock() def urlSnarfer(f): """Protects the snarfer from loops (with other bots) and whatnot.""" def newf(self, irc, msg, match, *L, **kwargs): url = match.group(0) channel = msg.channel if not channel: # Don't snarf in private return if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg): # Don't snarf CTCPs unless they are a /me return if ircdb.channels.getChannel(channel).lobotomized: self.log.debug('Not snarfing in %s: lobotomized.', channel) return if _snarfed.has(channel, url): self.log.info('Throttling snarf of %s in %s.', url, channel) return irc = SnarfIrc(irc, channel, url) def doSnarf(): _snarfLock.acquire() try: # This has to be *after* we've acquired the lock so we can be # sure that all previous urlSnarfers have already run to # completion. if msg.repliedTo: self.log.debug('Not snarfing, msg is already repliedTo.') return f(self, irc, msg, match, *L, **kwargs) finally: _snarfLock.release() if threading.currentThread() is not world.mainThread: doSnarf() else: L = list(L) t = UrlSnarfThread(target=doSnarf, url=url) t.start() newf = utils.python.changeFunctionName(newf, f.__name__, f.__doc__) return newf ### # Converters, which take irc, msg, args, and a state object, and build up the # validated and converted args for the method in state.args. ### # This is just so we can centralize this, since it may change. def _int(s): base = 10 if s.startswith('0x'): base = 16 s = s[2:] elif s.startswith('0b'): base = 2 s = s[2:] elif s.startswith('0') and len(s) > 1: base = 8 s = s[1:] try: return int(s, base) except ValueError: if base == 10 and '.' not in s: try: return int(float(s)) except OverflowError: raise ValueError('I don\'t understand numbers that large.') else: raise def getInt(irc, msg, args, state, type=_('integer'), p=None): try: i = _int(args[0]) if p is not None: if not p(i): state.errorInvalid(type, args[0]) state.args.append(i) del args[0] except ValueError: state.errorInvalid(type, args[0]) def getNonInt(irc, msg, args, state, type=_('non-integer value')): try: _int(args[0]) state.errorInvalid(type, args[0]) except ValueError: state.args.append(args.pop(0)) def getLong(irc, msg, args, state, type='long'): getInt(irc, msg, args, state, type) state.args[-1] = minisix.long(state.args[-1]) def getFloat(irc, msg, args, state, type=_('floating point number')): try: state.args.append(float(args[0])) del args[0] except ValueError: state.errorInvalid(type, args[0]) def getPositiveInt(irc, msg, args, state, *L): getInt(irc, msg, args, state, p=lambda i: i>0, type=_('positive integer'), *L) def getNonNegativeInt(irc, msg, args, state, *L): getInt(irc, msg, args, state, p=lambda i: i>=0, type=_('non-negative integer'), *L) def getIndex(irc, msg, args, state): getInt(irc, msg, args, state, type=_('index')) if state.args[-1] > 0: state.args[-1] -= 1 def getId(irc, msg, args, state, kind=None): type = 'id' if kind is not None and not kind.endswith('id'): type = kind + ' id' original = args[0] try: args[0] = args[0].lstrip('#') getInt(irc, msg, args, state, type=type) except Exception: args[0] = original raise def getExpiry(irc, msg, args, state): now = int(time.time()) try: expires = _int(args[0]) if expires: expires += now state.args.append(expires) del args[0] except ValueError: state.errorInvalid(_('number of seconds'), args[0]) def getBoolean(irc, msg, args, state): try: state.args.append(utils.str.toBool(args[0])) del args[0] except ValueError: state.errorInvalid(_('boolean'), args[0]) def getNetworkIrc(irc, msg, args, state, errorIfNoMatch=False): if args: for otherIrc in world.ircs: if otherIrc.network.lower() == args[0].lower(): state.args.append(otherIrc) del args[0] return if errorIfNoMatch: raise callbacks.ArgumentError else: state.args.append(irc) def getHaveVoice(irc, msg, args, state, action=_('do that')): getChannel(irc, msg, args, state) if state.channel not in irc.state.channels: state.error(_('I\'m not even in %s.') % state.channel, Raise=True) if not irc.state.channels[state.channel].isVoice(irc.nick): state.error(_('I need to be voiced to %s.') % action, Raise=True) def getHaveVoicePlus(irc, msg, args, state, action=_('do that')): getChannel(irc, msg, args, state) if state.channel not in irc.state.channels: state.error(_('I\'m not even in %s.') % state.channel, Raise=True) if not irc.state.channels[state.channel].isVoicePlus(irc.nick): # isOp includes owners and protected users state.error(_('I need to be at least voiced to %s.') % action, Raise=True) def getHaveHalfop(irc, msg, args, state, action=_('do that')): getChannel(irc, msg, args, state) if state.channel not in irc.state.channels: state.error(_('I\'m not even in %s.') % state.channel, Raise=True) if not irc.state.channels[state.channel].isHalfop(irc.nick): state.error(_('I need to be halfopped to %s.') % action, Raise=True) def getHaveHalfopPlus(irc, msg, args, state, action=_('do that')): getChannel(irc, msg, args, state) if state.channel not in irc.state.channels: state.error(_('I\'m not even in %s.') % state.channel, Raise=True) if not irc.state.channels[state.channel].isHalfopPlus(irc.nick): # isOp includes owners and protected users state.error(_('I need to be at least halfopped to %s.') % action, Raise=True) def getHaveOp(irc, msg, args, state, action=_('do that')): getChannel(irc, msg, args, state) if state.channel not in irc.state.channels: state.error(_('I\'m not even in %s.') % state.channel, Raise=True) if not irc.state.channels[state.channel].isOp(irc.nick): state.error(_('I need to be opped to %s.') % action, Raise=True) def validChannel(irc, msg, args, state): if irc.isChannel(args[0]): state.args.append(args.pop(0)) else: state.errorInvalid(_('channel'), args[0]) def getHostmask(irc, msg, args, state): if ircutils.isUserHostmask(args[0]) or \ (not conf.supybot.protocols.irc.strictRfc() and args[0].startswith('$')): state.args.append(args.pop(0)) else: try: hostmask = irc.state.nickToHostmask(args[0]) state.args.append(hostmask) del args[0] except KeyError: state.errorInvalid(_('nick or hostmask'), args[0]) def getBanmask(irc, msg, args, state): getHostmask(irc, msg, args, state) getChannel(irc, msg, args, state) banmaskstyle = conf.supybot.protocols.irc.banmask state.args[-1] = banmaskstyle.makeBanmask(state.args[-1], channel=state.channel) def getUser(irc, msg, args, state): try: state.args.append(ircdb.users.getUser(msg.prefix)) except KeyError: state.errorNotRegistered(Raise=True) def getOtherUser(irc, msg, args, state): # Although ircdb.users.getUser could accept a hostmask, we're explicitly # excluding that from our interface with this check if ircutils.isUserHostmask(args[0]): state.errorNoUser(args[0]) try: state.args.append(ircdb.users.getUser(args[0])) del args[0] except KeyError: try: getHostmask(irc, msg, [args[0]], state) hostmask = state.args.pop() state.args.append(ircdb.users.getUser(hostmask)) del args[0] except (KeyError, callbacks.Error): state.errorNoUser(name=args[0]) def _getRe(f): def get(irc, msg, args, state, convert=True): original = args[:] s = args.pop(0) def isRe(s): try: f(s) return True except ValueError: return False try: while len(s) < 512 and not isRe(s): s += ' ' + args.pop(0) if len(s) < 512: if convert: state.args.append(f(s)) else: state.args.append(s) else: raise ValueError except (ValueError, IndexError): args[:] = original state.errorInvalid(_('regular expression'), s) return get getMatcher = _getRe(utils.str.perlReToPythonRe) getMatcherMany = _getRe(utils.str.perlReToFindall) getReplacer = _getRe(utils.str.perlReToReplacer) def getNick(irc, msg, args, state): if ircutils.isNick(args[0], conf.supybot.protocols.irc.strictRfc()): if 'nicklen' in irc.state.supported: if len(args[0]) > irc.state.supported['nicklen']: state.errorInvalid(_('nick'), args[0], _('That nick is too long for this server.')) state.args.append(args.pop(0)) else: state.errorInvalid(_('nick'), args[0]) def getSeenNick(irc, msg, args, state, errmsg=None): try: irc.state.nickToHostmask(args[0]) state.args.append(args.pop(0)) except KeyError: if errmsg is None: errmsg = _('I haven\'t seen %s.') % args[0] state.error(errmsg, Raise=True) def getChannel(irc, msg, args, state): if state.channel: return if args and irc.isChannel(args[0]): channel = args.pop(0) elif msg.channel: channel = msg.channel else: state.log.debug('Raising ArgumentError because there is no channel.') print(msg.channel, msg) raise callbacks.ArgumentError state.channel = channel state.args.append(channel) def getChannels(irc, msg, args, state): if args and all(map(irc.isChannel, args[0].split(','))): channels = args.pop(0).split(',') elif msg.channel: channels = [msg.channel] else: state.log.debug('Raising ArgumentError because there is no channel.') raise callbacks.ArgumentError state.args.append(channels) def getChannelDb(irc, msg, args, state, **kwargs): channelSpecific = conf.supybot.databases.plugins.channelSpecific try: getChannel(irc, msg, args, state, **kwargs) channel = channelSpecific.getChannelLink(state.channel) state.channel = channel state.args[-1] = channel except (callbacks.ArgumentError, IndexError): if channelSpecific(): raise channel = channelSpecific.link() if not conf.get(channelSpecific.link.allow, channel): log.warning('channelSpecific.link is globally set to %s, but ' '%s disallowed linking to its db.', channel, channel) raise else: channel = channelSpecific.getChannelLink(channel) state.channel = channel state.args.append(channel) def inChannel(irc, msg, args, state): getChannel(irc, msg, args, state) if state.channel not in irc.state.channels: state.error(_('I\'m not in %s.') % state.channel, Raise=True) def onlyInChannel(irc, msg, args, state): if not (msg.channel and msg.channel in irc.state.channels): state.error(_('This command may only be given in a channel that I am ' 'in.'), Raise=True) else: state.channel = msg.channel state.args.append(state.channel) def callerInGivenChannel(irc, msg, args, state): channel = args[0] if irc.isChannel(channel): if channel in irc.state.channels: if msg.nick in irc.state.channels[channel].users: state.args.append(args.pop(0)) else: state.error(_('You must be in %s.') % channel, Raise=True) else: state.error(_('I\'m not in %s.') % channel, Raise=True) else: state.errorInvalid(_('channel'), args[0]) def nickInChannel(irc, msg, args, state): originalArgs = state.args[:] inChannel(irc, msg, args, state) state.args = originalArgs if args[0] not in irc.state.channels[state.channel].users: state.error(_('%s is not in %s.') % (args[0], state.channel), Raise=True) state.args.append(args.pop(0)) def getChannelOrNone(irc, msg, args, state): try: getChannel(irc, msg, args, state) except callbacks.ArgumentError: state.args.append(None) def getChannelOrGlobal(irc, msg, args, state): if args and args[0] == 'global': channel = args.pop(0) channel = 'global' elif args and irc.isChannel(args[0]): channel = args.pop(0) state.channel = channel elif msg.channel: channel = msg.channel state.channel = channel else: state.log.debug('Raising ArgumentError because there is no channel.') raise callbacks.ArgumentError state.args.append(channel) def checkChannelCapability(irc, msg, args, state, cap): getChannel(irc, msg, args, state) cap = ircdb.canonicalCapability(cap) cap = ircdb.makeChannelCapability(state.channel, cap) if not ircdb.checkCapability(msg.prefix, cap): state.errorNoCapability(cap, Raise=True) def getOp(irc, msg, args, state): checkChannelCapability(irc, msg, args, state, 'op') def getHalfop(irc, msg, args, state): checkChannelCapability(irc, msg, args, state, 'halfop') def getVoice(irc, msg, args, state): checkChannelCapability(irc, msg, args, state, 'voice') def getLowered(irc, msg, args, state): state.args.append(ircutils.toLower(args.pop(0))) def getSomething(irc, msg, args, state, errorMsg=None, p=None): if p is None: p = lambda _: True if not args[0] or not p(args[0]): if errorMsg is None: errorMsg = _('You must not give the empty string as an argument.') state.error(errorMsg, Raise=True) else: state.args.append(args.pop(0)) def getSomethingNoSpaces(irc, msg, args, state, *L): def p(s): return len(s.split(None, 1)) == 1 L = L or [_('You must not give a string containing spaces as an argument.')] getSomething(irc, msg, args, state, p=p, *L) def private(irc, msg, args, state): if msg.channel: state.errorRequiresPrivacy(Raise=True) def public(irc, msg, args, state, errmsg=None): if not msg.channel: if errmsg is None: errmsg = _('This message must be sent in a channel.') state.error(errmsg, Raise=True) def checkCapability(irc, msg, args, state, cap): cap = ircdb.canonicalCapability(cap) if not ircdb.checkCapability(msg.prefix, cap): state.errorNoCapability(cap, Raise=True) def checkCapabilityButIgnoreOwner(irc, msg, args, state, cap): cap = ircdb.canonicalCapability(cap) if not ircdb.checkCapability(msg.prefix, cap, ignoreOwner=True): state.errorNoCapability(cap, Raise=True) def owner(irc, msg, args, state): checkCapability(irc, msg, args, state, 'owner') def admin(irc, msg, args, state): checkCapability(irc, msg, args, state, 'admin') def anything(irc, msg, args, state): state.args.append(args.pop(0)) def getGlob(irc, msg, args, state): glob = args.pop(0) if '*' not in glob and '?' not in glob: glob = '*%s*' % glob state.args.append(glob) def getUrl(irc, msg, args, state): if utils.web.urlRe.match(args[0]): state.args.append(args.pop(0)) else: state.errorInvalid(_('url'), args[0]) def getEmail(irc, msg, args, state): if utils.net.emailRe.match(args[0]): state.args.append(args.pop(0)) else: state.errorInvalid(_('email'), args[0]) def getHttpUrl(irc, msg, args, state): if utils.web.httpUrlRe.match(args[0]): state.args.append(args.pop(0)) elif utils.web.httpUrlRe.match('http://' + args[0]): state.args.append('http://' + args.pop(0)) else: state.errorInvalid(_('http url'), args[0]) def getNow(irc, msg, args, state): state.args.append(int(time.time())) def getCommandName(irc, msg, args, state): if ' ' in args[0]: state.errorInvalid(_('command name'), args[0]) else: state.args.append(callbacks.canonicalName(args.pop(0))) def getIp(irc, msg, args, state): if utils.net.isIP(args[0]): state.args.append(args.pop(0)) else: state.errorInvalid(_('ip'), args[0]) def getLetter(irc, msg, args, state): if len(args[0]) == 1: state.args.append(args.pop(0)) else: state.errorInvalid(_('letter'), args[0]) def getMatch(irc, msg, args, state, regexp, errmsg): m = regexp.search(args[0]) if m is not None: state.args.append(m) del args[0] else: state.error(errmsg, Raise=True) def getLiteral(irc, msg, args, state, literals, errmsg=None): # ??? Should we allow abbreviations? if isinstance(literals, minisix.string_types): literals = (literals,) abbrevs = utils.abbrev(literals) if args[0] in abbrevs: state.args.append(abbrevs[args.pop(0)]) elif errmsg is not None: state.error(errmsg, Raise=True) else: raise callbacks.ArgumentError def getTo(irc, msg, args, state): if args[0].lower() == 'to': args.pop(0) def getPlugin(irc, msg, args, state, require=True): cb = irc.getCallback(args[0]) if cb is not None: state.args.append(cb) del args[0] elif require: state.errorInvalid(_('plugin'), args[0]) else: state.args.append(None) def getIrcColor(irc, msg, args, state): if args[0] in ircutils.mircColors: state.args.append(ircutils.mircColors[args.pop(0)]) else: state.errorInvalid(_('irc color')) def getText(irc, msg, args, state): if args: state.args.append(' '.join(args)) args[:] = [] else: raise IndexError wrappers = ircutils.IrcDict({ 'admin': admin, 'anything': anything, 'banmask': getBanmask, 'boolean': getBoolean, 'callerInGivenChannel': callerInGivenChannel, 'isGranted': getHaveHalfopPlus, # Backward compatibility 'capability': getSomethingNoSpaces, 'channel': getChannel, 'channels': getChannels, 'channelOrGlobal': getChannelOrGlobal, 'channelDb': getChannelDb, 'checkCapability': checkCapability, 'checkCapabilityButIgnoreOwner': checkCapabilityButIgnoreOwner, 'checkChannelCapability': checkChannelCapability, 'color': getIrcColor, 'commandName': getCommandName, 'email': getEmail, 'expiry': getExpiry, 'filename': getSomething, # XXX Check for validity. 'float': getFloat, 'glob': getGlob, 'halfop': getHalfop, 'haveHalfop': getHaveHalfop, 'haveHalfop+': getHaveHalfopPlus, 'haveOp': getHaveOp, 'haveOp+': getHaveOp, # We don't handle modes greater than op. 'haveVoice': getHaveVoice, 'haveVoice+': getHaveVoicePlus, 'hostmask': getHostmask, 'httpUrl': getHttpUrl, 'id': getId, 'inChannel': inChannel, 'index': getIndex, 'int': getInt, 'ip': getIp, 'letter': getLetter, 'literal': getLiteral, 'long': getLong, 'lowered': getLowered, 'matches': getMatch, 'networkIrc': getNetworkIrc, 'nick': getNick, 'nickInChannel': nickInChannel, 'nonInt': getNonInt, 'nonNegativeInt': getNonNegativeInt, 'now': getNow, 'onlyInChannel': onlyInChannel, 'op': getOp, 'otherUser': getOtherUser, 'owner': owner, 'plugin': getPlugin, 'positiveInt': getPositiveInt, 'private': private, 'public': public, 'regexpMatcher': getMatcher, 'regexpMatcherMany': getMatcherMany, 'regexpReplacer': getReplacer, 'seenNick': getSeenNick, 'something': getSomething, 'somethingWithoutSpaces': getSomethingNoSpaces, 'text': getText, 'to': getTo, 'url': getUrl, 'user': getUser, 'validChannel': validChannel, 'voice': getVoice, }) def addConverter(name, wrapper): wrappers[name] = wrapper class UnknownConverter(KeyError): pass def getConverter(name): try: return wrappers[name] except KeyError as e: raise UnknownConverter(str(e)) def callConverter(name, irc, msg, args, state, *L): getConverter(name)(irc, msg, args, state, *L) ### # Contexts. These determine what the nature of conversions is; whether they're # defaulted, or many of them are allowed, etc. Contexts should be reusable; # i.e., they should not maintain state between calls. ### def contextify(spec): if not isinstance(spec, context): spec = context(spec) return spec def setDefault(state, default): if callable(default): state.args.append(default()) else: state.args.append(default) class context(object): def __init__(self, spec): self.args = () self.spec = spec # for repr if isinstance(spec, tuple): assert spec, 'tuple spec must not be empty.' self.args = spec[1:] self.converter = getConverter(spec[0]) elif spec is None: self.converter = getConverter('anything') elif isinstance(spec, minisix.string_types): self.args = () self.converter = getConverter(spec) else: assert isinstance(spec, context) self.converter = spec def __call__(self, irc, msg, args, state): log.debug('args before %r: %r', self, args) self.converter(irc, msg, args, state, *self.args) log.debug('args after %r: %r', self, args) def __repr__(self): return '<%s for %s>' % (self.__class__.__name__, self.spec) class rest(context): def __call__(self, irc, msg, args, state): if args: original = args[:] args[:] = [' '.join(args)] try: super(rest, self).__call__(irc, msg, args, state) except Exception: args[:] = original else: raise IndexError # additional means: Look for this (and make sure it's of this type). If # there are no arguments for us to check, then use our default. class additional(context): def __init__(self, spec, default=None): self.__parent = super(additional, self) self.__parent.__init__(spec) self.default = default def __call__(self, irc, msg, args, state): try: self.__parent.__call__(irc, msg, args, state) except IndexError: log.debug('Got IndexError, returning default.') setDefault(state, self.default) # optional means: Look for this, but if it's not the type I'm expecting or # there are no arguments for us to check, then use the default value. class optional(additional): def __call__(self, irc, msg, args, state): try: super(optional, self).__call__(irc, msg, args, state) except (callbacks.ArgumentError, callbacks.Error) as e: log.debug('Got %s, returning default.', utils.exnToString(e)) state.errored = False setDefault(state, self.default) class any(context): def __init__(self, spec, continueOnError=False): self.__parent = super(any, self) self.__parent.__init__(spec) self.continueOnError = continueOnError def __call__(self, irc, msg, args, state): st = state.essence() try: while args: self.__parent.__call__(irc, msg, args, st) except IndexError: pass except (callbacks.ArgumentError, callbacks.Error) as e: if not self.continueOnError: raise else: log.debug('Got %s, returning default.', utils.exnToString(e)) pass state.args.append(st.args) class many(any): def __call__(self, irc, msg, args, state): super(many, self).__call__(irc, msg, args, state) if not state.args[-1]: state.args.pop() raise callbacks.ArgumentError class first(context): def __init__(self, *specs, **kw): if 'default' in kw: self.default = kw.pop('default') assert not kw, 'Bad kwargs for first.__init__' self.spec = specs # for __repr__ self.specs = list(map(contextify, specs)) def __call__(self, irc, msg, args, state): errored = False for spec in self.specs: try: spec(irc, msg, args, state) return except Exception as e: e2 = e # 'e' is local. errored = state.errored state.errored = False continue if hasattr(self, 'default'): state.args.append(self.default) else: state.errored = errored raise e2 class reverse(context): def __call__(self, irc, msg, args, state): args[:] = args[::-1] super(reverse, self).__call__(irc, msg, args, state) args[:] = args[::-1] class commalist(context): def __call__(self, irc, msg, args, state): original = args[:] st = state.essence() trailingComma = True try: while trailingComma: arg = args.pop(0) if not arg.endswith(','): trailingComma = False for part in arg.split(','): if part: # trailing commas super(commalist, self).__call__(irc, msg, [part], st) state.args.append(st.args) except Exception: args[:] = original raise class getopts(context): """The empty string indicates that no argument is taken; None indicates that there is no converter for the argument.""" def __init__(self, getopts): self.spec = getopts # for repr self.getopts = {} self.getoptL = [] self.getoptLs = '' for (name, spec) in getopts.items(): if spec == '': if len(name) == 1: self.getoptLs += name self.getopts[name] = None self.getoptL.append(name) self.getopts[name] = None else: if len(name) == 1: self.getoptLs += name + ':' self.getopts[name] = contextify(spec) self.getoptL.append(name + '=') self.getopts[name] = contextify(spec) log.debug('getopts: %r', self.getopts) log.debug('getoptL: %r', self.getoptL) def __call__(self, irc, msg, args, state): log.debug('args before %r: %r', self, args) (optlist, rest) = getopt.getopt(args, self.getoptLs, self.getoptL) getopts = [] for (opt, arg) in optlist: if opt.startswith('--'): opt = opt[2:] # Strip -- else: opt = opt[1:] log.debug('opt: %r, arg: %r', opt, arg) context = self.getopts[opt] if context is not None: st = state.essence() context(irc, msg, [arg], st) assert len(st.args) == 1 getopts.append((opt, st.args[0])) else: getopts.append((opt, True)) state.args.append(getopts) args[:] = rest log.debug('args after %r: %r', self, args) ### # This is our state object, passed to converters along with irc, msg, and args. ### class State(object): log = log def __init__(self, types): self.args = [] self.kwargs = {} self.types = types self.channel = None self.errored = False def __getattr__(self, attr): if attr.startswith('error'): self.errored = True return getattr(dynamic.irc, attr) else: raise AttributeError(attr) def essence(self): st = State(self.types) for (attr, value) in self.__dict__.items(): if attr not in ('args', 'kwargs'): setattr(st, attr, value) return st def __repr__(self): return '%s(args=%r, kwargs=%r, channel=%r)' % (self.__class__.__name__, self.args, self.kwargs, self.channel) ### # This is a compiled Spec object. ### class Spec(object): def _state(self, types, attrs={}): st = State(types) st.__dict__.update(attrs) st.allowExtra = self.allowExtra return st def __init__(self, types, allowExtra=False): self.types = types self.allowExtra = allowExtra utils.seq.mapinto(contextify, self.types) def __call__(self, irc, msg, args, stateAttrs={}): state = self._state(self.types[:], stateAttrs) while state.types: context = state.types.pop(0) try: context(irc, msg, args, state) except IndexError: raise callbacks.ArgumentError if args and not state.allowExtra: log.debug('args and not self.allowExtra: %r', args) raise callbacks.ArgumentError return state def _wrap(f, specList=[], name=None, checkDoc=True, **kw): name = name or f.__name__ assert (not checkDoc) or (hasattr(f, '__doc__') and f.__doc__), \ 'Command %r has no docstring.' % name spec = Spec(specList, **kw) def newf(self, irc, msg, args, **kwargs): state = spec(irc, msg, args, stateAttrs={'cb': self, 'log': self.log}) self.log.debug('State before call: %s', state) if state.errored: self.log.debug('Refusing to call %s due to state.errored.', f) else: try: f(self, irc, msg, args, *state.args, **state.kwargs) except TypeError: self.log.error('Spec: %s', specList) self.log.error('Received args: %s', args) code = f.__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 newf2 = utils.python.changeFunctionName(newf, name, f.__doc__) newf2.__module__ = f.__module__ return internationalizeDocstring(newf2) def wrap(f, *args, **kwargs): if callable(f): # Old-style call OR decorator syntax with no converter. # f is the command. return _wrap(f, *args, **kwargs) else: # Call with the Python decorator syntax assert isinstance(f, list) or isinstance(f, tuple) specList = f def decorator(f): return _wrap(f, specList, *args, **kwargs) return decorator wrap.__doc__ = """Useful wrapper for plugin commands. Valid converters are: %s. :param f: A command, taking (self, irc, msg, args, ...) as arguments :param specList: A list of converters and contexts""" % \ ', '.join(sorted(wrappers.keys())) __all__ = [ # Contexts. 'any', 'many', 'optional', 'additional', 'rest', 'getopts', 'first', 'reverse', 'commalist', # Converter helpers. 'getConverter', 'addConverter', 'callConverter', # Decorators. 'urlSnarfer', 'thread', # Functions. 'wrap', 'process', 'regexp_wrapper', # Stuff for testing. 'Spec', ] # This doesn't work. Suck. ## if world.testing: ## __all__.append('Spec') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/conf.py0000644000175000017500000017426613634634532015425 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2008-2009,2011, James McCoy # 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 os import sys import time import socket from . import ircutils, registry, utils from .utils import minisix from .utils.net import isSocketAddress from .version import version from .i18n import PluginInternationalization _ = PluginInternationalization() if minisix.PY2: from urllib2 import build_opener, install_opener, ProxyHandler else: from urllib.request import build_opener, install_opener, ProxyHandler ### # *** The following variables are affected by command-line options. They are # not registry variables for a specific reason. Do *not* change these to # registry variables without first consulting people smarter than yourself. ### ### # daemonized: This determines whether or not the bot has been daemonized # (i.e., set to run in the background). Obviously, this defaults # to False. A command-line option for obvious reasons. ### daemonized = False ### # allowDefaultOwner: True if supybot.capabilities is allowed not to include # '-owner' -- that is, if all users should be automatically # recognized as owners. That would suck, hence we require a # command-line option to allow this stupidity. ### allowDefaultOwner = False ### # Here we replace values in other modules as appropriate. ### utils.web.defaultHeaders['User-agent'] = \ 'Mozilla/5.0 (Compatible; Supybot %s)' % version ### # The standard registry. ### supybot = registry.Group() supybot.setName('supybot') def registerGroup(Group, name, group=None, **kwargs): if kwargs: group = registry.Group(**kwargs) return Group.register(name, group) def registerGlobalValue(group, name, value): value._networkValue = False value._channelValue = False return group.register(name, value) def registerNetworkValue(group, name, value): value._supplyDefault = True value._networkValue = True value._channelValue = False g = group.register(name, value) gname = g._name.lower() for name in registry._cache.keys(): if name.lower().startswith(gname) and len(gname) < len(name): name = name[len(gname)+1:] # +1 for . parts = registry.split(name) if len(parts) == 1 and parts[0] and ircutils.isChannel(parts[0]): # This gets the network values so they always persist. g.get(parts[0])() return g def registerChannelValue(group, name, value, opSettable=True): value._supplyDefault = True value._networkValue = True value._channelValue = True value._opSettable = opSettable g = group.register(name, value) gname = g._name.lower() for name in registry._cache.keys(): if name.lower().startswith(gname) and len(gname) < len(name): name = name[len(gname)+1:] # +1 for . parts = registry.split(name) if len(parts) == 2 and parts[0] and parts[0].startswith(':') \ and parts[1] and ircutils.isChannel(parts[1]): # This gets the network+channel values so they always persist. g.get(parts[0])() g.get(parts[0]).get(parts[1])() elif len(parts) == 1 and parts[0] and ircutils.isChannel(parts[0]): # Old-style variant of the above, without a network g.get(parts[0])() return g def registerPlugin(name, currentValue=None, public=True): group = registerGlobalValue(supybot.plugins, name, registry.Boolean(False, _("""Determines whether this plugin is loaded by default."""), showDefault=False)) supybot.plugins().add(name) registerGlobalValue(group, 'public', registry.Boolean(public, _("""Determines whether this plugin is publicly visible."""))) if currentValue is not None: supybot.plugins.get(name).setValue(currentValue) registerGroup(users.plugins, name) return group def get(group, channel=None, network=None): return group.getSpecific(channel=channel, network=network)() ### # The user info registry. ### users = registry.Group() users.setName('users') registerGroup(users, 'plugins', orderAlphabetically=True) def registerUserValue(group, name, value): assert group._name.startswith('users') value._supplyDefault = True group.register(name, value) class ValidNick(registry.String): """Value must be a valid IRC nick.""" __slots__ = () def setValue(self, v): if not ircutils.isNick(v): self.error() else: registry.String.setValue(self, v) class ValidNickOrEmpty(ValidNick): """Value must be a valid IRC nick or empty.""" __slots__ = () def setValue(self, v): if v != '' and not ircutils.isNick(v): self.error() else: registry.String.setValue(self, v) class ValidNicks(registry.SpaceSeparatedListOf): __slots__ = () Value = ValidNick class ValidNickAllowingPercentS(ValidNick): """Value must be a valid IRC nick, with the possible exception of a %s in it.""" __slots__ = () def setValue(self, v): # If this works, it's a valid nick, aside from the %s. try: ValidNick.setValue(self, v.replace('%s', '')) # It's valid aside from the %s, we'll let it through. registry.String.setValue(self, v) except registry.InvalidRegistryValue: self.error() class ValidNicksAllowingPercentS(ValidNicks): __slots__ = () Value = ValidNickAllowingPercentS class ValidChannel(registry.String): """Value must be a valid IRC channel name.""" __slots__ = ('channel',) def setValue(self, v): self.channel = v if ',' in v: # To prevent stupid users from: a) trying to add a channel key # with a comma in it, b) trying to add channels separated by # commas instead of spaces try: (channel, _) = v.split(',') except ValueError: self.error() else: channel = v if not ircutils.isChannel(channel): self.error() else: registry.String.setValue(self, v) def error(self): try: super(ValidChannel, self).error() except registry.InvalidRegistryValue as e: e.channel = self.channel raise e class ValidHostmask(registry.String): """Value must be a valid user hostmask.""" __slots__ = () def setValue(self, v): if not ircutils.isUserHostmask(v): self.error() super(ValidHostmask, self).setValue(v) registerGlobalValue(supybot, 'nick', ValidNick('supybot', _("""Determines the bot's default nick."""))) registerGlobalValue(supybot.nick, 'alternates', ValidNicksAllowingPercentS(['%s`', '%s_'], _("""Determines what alternative nicks will be used if the primary nick (supybot.nick) isn't available. A %s in this nick is replaced by the value of supybot.nick when used. If no alternates are given, or if all are used, the supybot.nick will be perturbed appropriately until an unused nick is found."""))) registerGlobalValue(supybot, 'ident', ValidNick('limnoria', _("""Determines the bot's ident string, if the server doesn't provide one by default."""))) # Although empty version strings are theoretically allowed by the RFC, # popular IRCds do not. # So, we keep replacing the empty string by the current version for # bots which are migrated from Supybot or an old version of Limnoria # (whose default value of supybot.user is the empty string). class VersionIfEmpty(registry.String): __slots__ = () def __call__(self): ret = registry.String.__call__(self) if not ret: ret = 'Limnoria $version' return ret registerGlobalValue(supybot, 'user', VersionIfEmpty('Limnoria $version', _("""Determines the real name which the bot sends to the server. A standard real name using the current version of the bot will be generated if this is left empty."""))) class Networks(registry.SpaceSeparatedSetOfStrings): __slots__ = () List = ircutils.IrcSet registerGlobalValue(supybot, 'networks', Networks([], _("""Determines what networks the bot will connect to."""), orderAlphabetically=True)) class Servers(registry.SpaceSeparatedListOfStrings): __slots__ = () def normalize(self, s): if ':' not in s: s += ':6667' return s def convert(self, s): s = self.normalize(s) (server, port) = s.rsplit(':', 1) # support for `[ipv6]:port` format if server.startswith("[") and server.endswith("]"): server = server[1:-1] port = int(port) return (server, port) def __call__(self): L = registry.SpaceSeparatedListOfStrings.__call__(self) return list(map(self.convert, L)) def __str__(self): return ' '.join(registry.SpaceSeparatedListOfStrings.__call__(self)) def append(self, s): L = registry.SpaceSeparatedListOfStrings.__call__(self) L.append(s) class SocksProxy(registry.String): """Value must be a valid hostname:port string.""" __slots__ = () def setValue(self, v): # TODO: improve checks if ':' not in v: self.error() try: int(v.rsplit(':', 1)[1]) except ValueError: self.error() super(SocksProxy, self).setValue(v) class SpaceSeparatedSetOfChannels(registry.SpaceSeparatedListOf): __slots__ = () sorted = True List = ircutils.IrcSet Value = ValidChannel def join(self, channel): from . import ircmsgs # Don't put this globally! It's recursive. key = self.key.get(channel)() if key: return ircmsgs.join(channel, key) else: return ircmsgs.join(channel) def joins(self): from . import ircmsgs # Don't put this globally! It's recursive. channels = [] channels_with_key = [] keys = [] old = None msgs = [] msg = None for channel in self(): key = self.key.get(channel)() if key: keys.append(key) channels_with_key.append(channel) else: channels.append(channel) msg = ircmsgs.joins(channels_with_key + channels, keys) if len(str(msg)) > 512: msgs.append(old) keys = [] channels_with_key = [] channels = [] old = msg if msg: msgs.append(msg) return msgs else: # Let's be explicit about it return None class ValidSaslMechanism(registry.OnlySomeStrings): __slots__ = () validStrings = ('ecdsa-nist256p-challenge', 'external', 'plain', 'scram-sha-256') class SpaceSeparatedListOfSaslMechanisms(registry.SpaceSeparatedListOf): __slots__ = () Value = ValidSaslMechanism def registerNetwork(name, password='', ssl=True, sasl_username='', sasl_password=''): network = registerGroup(supybot.networks, name) registerGlobalValue(network, 'password', registry.String(password, _("""Determines what password will be used on %s. Yes, we know that technically passwords are server-specific and not network-specific, but this is the best we can do right now.""") % name, private=True)) registerGlobalValue(network, 'servers', Servers([], _("""Space-separated list of servers the bot will connect to for %s. Each will be tried in order, wrapping back to the first when the cycle is completed.""") % name)) registerGlobalValue(network, 'channels', SpaceSeparatedSetOfChannels([], _("""Space-separated list of channels the bot will join only on %s.""") % name, private=True)) registerGlobalValue(network, 'ssl', registry.Boolean(ssl, _("""Determines whether the bot will attempt to connect with SSL sockets to %s.""") % name)) registerGlobalValue(network.ssl, 'serverFingerprints', registry.SpaceSeparatedSetOfStrings([], format(_("""Space-separated list of fingerprints of trusted certificates for this network. Supported hash algorithms are: %L. If non-empty, Certification Authority signatures will not be used to verify certificates."""), utils.net.FINGERPRINT_ALGORITHMS))) registerGlobalValue(network.ssl, 'authorityCertificate', registry.String('', _("""A certificate that is trusted to verify certificates of this network (aka. Certificate Authority)."""))) registerGlobalValue(network, 'requireStarttls', registry.Boolean(False, _("""Deprecated config value, keep it to False."""))) registerGlobalValue(network, 'certfile', registry.String('', _("""Determines what certificate file (if any) the bot will use to connect with SSL sockets to %s.""") % name)) registerChannelValue(network.channels, 'key', registry.String('', _("""Determines what key (if any) will be used to join the channel."""), private=True)) registerGlobalValue(network, 'nick', ValidNickOrEmpty('', _("""Determines what nick the bot will use on this network. If empty, defaults to supybot.nick."""))) registerGlobalValue(network, 'ident', ValidNickOrEmpty('', _("""Determines the bot's ident string, if the server doesn't provide one by default. If empty, defaults to supybot.ident."""))) registerGlobalValue(network, 'user', registry.String('', _("""Determines the real name which the bot sends to the server. If empty, defaults to supybot.user"""))) registerGlobalValue(network, 'umodes', registry.String('', _("""Determines what user modes the bot will request from the server when it first connects. If empty, defaults to supybot.protocols.irc.umodes"""))) sasl = registerGroup(network, 'sasl') registerGlobalValue(sasl, 'username', registry.String(sasl_username, _("""Determines what SASL username will be used on %s. This should be the bot's account name.""") % name, private=False)) registerGlobalValue(sasl, 'password', registry.String(sasl_password, _("""Determines what SASL password will be used on %s.""") \ % name, private=True)) registerGlobalValue(sasl, 'ecdsa_key', registry.String('', _("""Determines what SASL ECDSA key (if any) will be used on %s. The public key must be registered with NickServ for SASL ECDSA-NIST256P-CHALLENGE to work.""") % name, private=False)) registerGlobalValue(sasl, 'mechanisms', SpaceSeparatedListOfSaslMechanisms( ['ecdsa-nist256p-challenge', 'external', 'plain'], _("""Determines what SASL mechanisms will be tried and in which order."""))) registerGlobalValue(sasl, 'required', registry.Boolean(False, _("""Determines whether the bot will abort the connection if the none of the enabled SASL mechanism succeeded."""))) registerGlobalValue(network, 'socksproxy', registry.String('', _("""If not empty, determines the hostname of the socks proxy that will be used to connect to this network."""))) return network # Let's fill our networks. for (name, s) in registry._cache.items(): if name.startswith('supybot.networks.'): parts = name.split('.') name = parts[2] if name != 'default': registerNetwork(name) ### # Reply/error tweaking. ### registerGroup(supybot, 'reply') registerGroup(supybot.reply, 'format') registerChannelValue(supybot.reply.format, 'url', registry.String('<%s>', _("""Determines how urls should be formatted."""))) def url(s): if s: return supybot.reply.format.url() % s else: return '' utils.str.url = url registerChannelValue(supybot.reply.format, 'time', registry.String('%Y-%m-%dT%H:%M:%S%z', _("""Determines how timestamps printed for human reading should be formatted. Refer to the Python documentation for the time module to see valid formatting characters for time formats."""))) def timestamp(t): if t is None: t = time.time() if isinstance(t, float) or isinstance(t, int): t = time.localtime(t) format = get(supybot.reply.format.time, dynamic.channel) return time.strftime(format, t) utils.str.timestamp = timestamp registerGroup(supybot.reply.format.time, 'elapsed') registerChannelValue(supybot.reply.format.time.elapsed, 'short', registry.Boolean(False, _("""Determines whether elapsed times will be given as "1 day, 2 hours, 3 minutes, and 15 seconds" or as "1d 2h 3m 15s"."""))) originalTimeElapsed = utils.timeElapsed def timeElapsed(*args, **kwargs): kwargs['short'] = supybot.reply.format.time.elapsed.short() return originalTimeElapsed(*args, **kwargs) utils.timeElapsed = timeElapsed registerGroup(supybot.reply.format, 'list') registerChannelValue(supybot.reply.format.list, 'maximumItems', registry.NonNegativeInteger(0, _("""Maximum number of items in a list before the end is replaced with 'and others'. Set to 0 to always show the entire list."""))) originalCommaAndify = utils.str.commaAndify def commaAndify(seq, *args, **kwargs): maximum_items = supybot.reply.format.list.maximumItems.getSpecific( channel=dynamic.channel, network=getattr(dynamic.irc, 'network', None))() if maximum_items: seq = list(seq) initial_length = len(seq) if len(seq) > maximum_items: seq = seq[:maximum_items] nb_skipped = initial_length - maximum_items + 1 # Even though nb_skipped is always >= 2, some languages require # nItems for proper pluralization. seq[-1] = utils.str.nItems(nb_skipped, _('other')) return originalCommaAndify(seq, *args, **kwargs) utils.str.commaAndify = commaAndify registerGlobalValue(supybot.reply, 'maximumLength', registry.Integer(512*256, _("""Determines the absolute maximum length of the bot's reply -- no reply will be passed through the bot with a length greater than this."""))) registerChannelValue(supybot.reply, 'mores', registry.Boolean(True, _("""Determines whether the bot will break up long messages into chunks and allow users to use the 'more' command to get the remaining chunks."""))) registerChannelValue(supybot.reply.mores, 'maximum', registry.PositiveInteger(50, _("""Determines what the maximum number of chunks (for use with the 'more' command) will be."""))) registerChannelValue(supybot.reply.mores, 'length', registry.NonNegativeInteger(0, _("""Determines how long individual chunks will be. If set to 0, uses our super-tweaked, get-the-most-out-of-an-individual-message default."""))) registerChannelValue(supybot.reply.mores, 'instant', registry.PositiveInteger(1, _("""Determines how many mores will be sent instantly (i.e., without the use of the more command, immediately when they are formed). Defaults to 1, which means that a more command will be required for all but the first chunk."""))) registerChannelValue(supybot.reply, 'oneToOne', registry.Boolean(True, _("""Determines whether the bot will send multi-message replies in a single message. This defaults to True in order to prevent the bot from flooding. If this is set to False the bot will send multi-message replies on multiple lines."""))) registerChannelValue(supybot.reply, 'whenNotCommand', registry.Boolean(True, _("""Determines whether the bot will reply with an error message when it is addressed but not given a valid command. If this value is False, the bot will remain silent, as long as no other plugins override the normal behavior."""))) registerGroup(supybot.reply, 'error') registerGlobalValue(supybot.reply.error, 'detailed', registry.Boolean(False, _("""Determines whether error messages that result from bugs in the bot will show a detailed error message (the uncaught exception) or a generic error message."""))) registerChannelValue(supybot.reply.error, 'inPrivate', registry.Boolean(False, _("""Determines whether the bot will send error messages to users in private. You might want to do this in order to keep channel traffic to minimum. This can be used in combination with supybot.reply.error.withNotice."""))) registerChannelValue(supybot.reply.error, 'withNotice', registry.Boolean(False, _("""Determines whether the bot will send error messages to users via NOTICE instead of PRIVMSG. You might want to do this so users can ignore NOTICEs from the bot and not have to see error messages; or you might want to use it in combination with supybot.reply.errorInPrivate so private errors don't open a query window in most IRC clients."""))) registerChannelValue(supybot.reply.error, 'noCapability', registry.Boolean(False, _("""Determines whether the bot will *not* provide details in the error message to users who attempt to call a command for which they do not have the necessary capability. You may wish to make this True if you don't want users to understand the underlying security system preventing them from running certain commands."""))) registerChannelValue(supybot.reply, 'inPrivate', registry.Boolean(False, _("""Determines whether the bot will reply privately when replying in a channel, rather than replying to the whole channel."""))) registerChannelValue(supybot.reply, 'withNotice', registry.Boolean(False, _("""Determines whether the bot will reply with a notice when replying in a channel, rather than replying with a privmsg as normal."""))) # XXX: User value. registerGlobalValue(supybot.reply, 'withNoticeWhenPrivate', registry.Boolean(True, _("""Determines whether the bot will reply with a notice when it is sending a private message, in order not to open a /query window in clients."""))) registerChannelValue(supybot.reply, 'withNickPrefix', registry.Boolean(True, _("""Determines whether the bot will always prefix the user's nick to its reply to that user's command."""))) registerChannelValue(supybot.reply, 'whenNotAddressed', registry.Boolean(False, _("""Determines whether the bot should attempt to reply to all messages even if they don't address it (either via its nick or a prefix character). If you set this to True, you almost certainly want to set supybot.reply.whenNotCommand to False."""))) registerChannelValue(supybot.reply, 'requireChannelCommandsToBeSentInChannel', registry.Boolean(False, _("""Determines whether the bot will allow you to send channel-related commands outside of that channel. Sometimes people find it confusing if a channel-related command (like Filter.outfilter) changes the behavior of the channel but was sent outside the channel itself."""))) registerGlobalValue(supybot, 'followIdentificationThroughNickChanges', registry.Boolean(False, _("""Determines whether the bot will unidentify someone when that person changes their nick. Setting this to True will cause the bot to track such changes. It defaults to False for a little greater security."""))) registerChannelValue(supybot, 'alwaysJoinOnInvite', registry.Boolean(False, _("""Determines whether the bot will always join a channel when it's invited. If this value is False, the bot will only join a channel if the user inviting it has the 'admin' capability (or if it's explicitly told to join the channel using the Admin.join command)."""))) registerChannelValue(supybot.reply, 'showSimpleSyntax', registry.Boolean(False, _("""Supybot normally replies with the full help whenever a user misuses a command. If this value is set to True, the bot will only reply with the syntax of the command (the first line of the help) rather than the full help."""))) class ValidPrefixChars(registry.String): """Value must contain only ~!@#$%^&*()_-+=[{}]\\|'\";:,<.>/?""" __slots__ = () def setValue(self, v): if any([x not in '`~!@#$%^&*()_-+=[{}]\\|\'";:,<.>/?' for x in v]): self.error() registry.String.setValue(self, v) registerGroup(supybot.reply, 'whenAddressedBy') registerChannelValue(supybot.reply.whenAddressedBy, 'chars', ValidPrefixChars('', _("""Determines what prefix characters the bot will reply to. A prefix character is a single character that the bot will use to determine what messages are addressed to it; when there are no prefix characters set, it just uses its nick. Each character in this string is interpreted individually; you can have multiple prefix chars simultaneously, and if any one of them is used as a prefix the bot will assume it is being addressed."""))) registerChannelValue(supybot.reply.whenAddressedBy, 'strings', registry.SpaceSeparatedSetOfStrings([], _("""Determines what strings the bot will reply to when they are at the beginning of the message. Whereas prefix.chars can only be one character (although there can be many of them), this variable is a space-separated list of strings, so you can set something like '@@ ??' and the bot will reply when a message is prefixed by either @@ or ??."""))) registerChannelValue(supybot.reply.whenAddressedBy, 'nick', registry.Boolean(True, _("""Determines whether the bot will reply when people address it by its nick, rather than with a prefix character."""))) registerChannelValue(supybot.reply.whenAddressedBy.nick, 'atEnd', registry.Boolean(False, _("""Determines whether the bot will reply when people address it by its nick at the end of the message, rather than at the beginning."""))) registerChannelValue(supybot.reply.whenAddressedBy, 'nicks', registry.SpaceSeparatedSetOfStrings([], _("""Determines what extra nicks the bot will always respond to when addressed by, even if its current nick is something else."""))) ### # Replies ### registerGroup(supybot, 'replies') registerChannelValue(supybot.replies, 'success', registry.NormalizedString(_("""The operation succeeded."""), _("""Determines what message the bot replies with when a command succeeded. If this configuration variable is empty, no success message will be sent."""))) registerChannelValue(supybot.replies, 'error', registry.NormalizedString(_("""An error has occurred and has been logged. Please contact this bot's administrator for more information."""), _(""" Determines what error message the bot gives when it wants to be ambiguous."""))) registerChannelValue(supybot.replies, 'errorOwner', registry.NormalizedString(_("""An error has occurred and has been logged. Check the logs for more information."""), _("""Determines what error message the bot gives to the owner when it wants to be ambiguous."""))) registerChannelValue(supybot.replies, 'incorrectAuthentication', registry.NormalizedString(_("""Your hostmask doesn't match or your password is wrong."""), _("""Determines what message the bot replies with when someone tries to use a command that requires being identified or having a password and neither credential is correct."""))) # XXX: This should eventually check that there's one and only one %s here. registerChannelValue(supybot.replies, 'noUser', registry.NormalizedString(_("""I can't find %s in my user database. If you didn't give a user name, then I might not know what your user is, and you'll need to identify before this command might work."""), _("""Determines what error message the bot replies with when someone tries to accessing some information on a user the bot doesn't know about."""))) registerChannelValue(supybot.replies, 'notRegistered', registry.NormalizedString(_("""You must be registered to use this command. If you are already registered, you must either identify (using the identify command) or add a hostmask matching your current hostmask (using the "hostmask add" command)."""), _("""Determines what error message the bot replies with when someone tries to do something that requires them to be registered but they're not currently recognized."""))) registerChannelValue(supybot.replies, 'noCapability', registry.NormalizedString(_("""You don't have the %s capability. If you think that you should have this capability, be sure that you are identified before trying again. The 'whoami' command can tell you if you're identified."""), _("""Determines what error message is given when the bot is telling someone they aren't cool enough to use the command they tried to use."""))) registerChannelValue(supybot.replies, 'genericNoCapability', registry.NormalizedString(_("""You're missing some capability you need. This could be because you actually possess the anti-capability for the capability that's required of you, or because the channel provides that anti-capability by default, or because the global capabilities include that anti-capability. Or, it could be because the channel or supybot.capabilities.default is set to False, meaning that no commands are allowed unless explicitly in your capabilities. Either way, you can't do what you want to do."""), _("""Determines what generic error message is given when the bot is telling someone that they aren't cool enough to use the command they tried to use, and the author of the code calling errorNoCapability didn't provide an explicit capability for whatever reason."""))) registerChannelValue(supybot.replies, 'requiresPrivacy', registry.NormalizedString(_("""That operation cannot be done in a channel."""), _("""Determines what error messages the bot sends to people who try to do things in a channel that really should be done in private."""))) registerChannelValue(supybot.replies, 'possibleBug', registry.NormalizedString(_("""This may be a bug. If you think it is, please file a bug report at ."""), _("""Determines what message the bot sends when it thinks you've encountered a bug that the developers don't know about."""))) ### # End supybot.replies. ### registerGlobalValue(supybot, 'snarfThrottle', registry.Float(10.0, _("""A floating point number of seconds to throttle snarfed URLs, in order to prevent loops between two bots snarfing the same URLs and having the snarfed URL in the output of the snarf message."""))) registerGlobalValue(supybot, 'upkeepInterval', registry.PositiveInteger(3600, _("""Determines the number of seconds between running the upkeep function that flushes (commits) open databases, collects garbage, and records some useful statistics at the debugging level."""))) registerGlobalValue(supybot, 'flush', registry.Boolean(True, _("""Determines whether the bot will periodically flush data and configuration files to disk. Generally, the only time you'll want to set this to False is when you want to modify those configuration files by hand and don't want the bot to flush its current version over your modifications. Do note that if you change this to False inside the bot, your changes won't be flushed. To make this change permanent, you must edit the registry yourself."""))) ### # supybot.commands. For stuff relating to commands. ### registerGroup(supybot, 'commands') class ValidQuotes(registry.Value): """Value must consist solely of \", ', and ` characters.""" __slots__ = () def setValue(self, v): if [c for c in v if c not in '"`\'']: self.error() super(ValidQuotes, self).setValue(v) def __str__(self): return str(self.value) registerChannelValue(supybot.commands, 'quotes', ValidQuotes('"', _("""Determines what characters are valid for quoting arguments to commands in order to prevent them from being tokenized. """))) # This is a GlobalValue because bot owners should be able to say, "There will # be no nesting at all on this bot." Individual channels can just set their # brackets to the empty string. registerGlobalValue(supybot.commands, 'nested', registry.Boolean(True, _("""Determines whether the bot will allow nested commands, which rule. You definitely should keep this on."""))) registerGlobalValue(supybot.commands.nested, 'maximum', registry.PositiveInteger(10, _("""Determines what the maximum number of nested commands will be; users will receive an error if they attempt commands more nested than this."""))) class ValidBrackets(registry.OnlySomeStrings): __slots__ = () validStrings = ('', '[]', '<>', '{}', '()') registerChannelValue(supybot.commands.nested, 'brackets', ValidBrackets('[]', _("""Supybot allows you to specify what brackets are used for your nested commands. Valid sets of brackets include [], <>, {}, and (). [] has strong historical motivation, but <> or () might be slightly superior because they cannot occur in a nick. If this string is empty, nested commands will not be allowed in this channel."""))) registerChannelValue(supybot.commands.nested, 'pipeSyntax', registry.Boolean(False, _("""Supybot allows nested commands. Enabling this option will allow nested commands with a syntax similar to UNIX pipes, for example: 'bot: foo | bar'."""))) registerGroup(supybot.commands, 'defaultPlugins', orderAlphabetically=True, help=_("""Determines what commands have default plugins set, and which plugins are set to be the default for each of those commands.""")) registerGlobalValue(supybot.commands.defaultPlugins, 'importantPlugins', registry.SpaceSeparatedSetOfStrings( ['Admin', 'Channel', 'Config', 'Misc', 'Owner', 'User'], _("""Determines what plugins automatically get precedence over all other plugins when selecting a default plugin for a command. By default, this includes the standard loaded plugins. You probably shouldn't change this if you don't know what you're doing; if you do know what you're doing, then also know that this set is case-sensitive."""))) # For this config variable to make sense, it must no be writable via IRC. # Make sure it is always blacklisted from the Config plugin. registerGlobalValue(supybot.commands, 'allowShell', registry.Boolean(True, _("""Allows this bot's owner user to use commands that grants them shell access. This config variable exists in case you want to prevent MITM from the IRC network itself (vulnerable IRCd or IRCops) from gaining shell access to the bot's server by impersonating the owner. Setting this to False also disables plugins and commands that can be used to indirectly gain shell access."""))) # supybot.commands.disabled moved to callbacks for canonicalName. ### # supybot.abuse. For stuff relating to abuse of the bot. ### registerGroup(supybot, 'abuse') registerGroup(supybot.abuse, 'flood') registerGlobalValue(supybot.abuse.flood, 'interval', registry.PositiveInteger(60, _("""Determines the interval used for the history storage."""))) registerGlobalValue(supybot.abuse.flood, 'command', registry.Boolean(True, _("""Determines whether the bot will defend itself against command-flooding."""))) registerGlobalValue(supybot.abuse.flood.command, 'maximum', registry.PositiveInteger(12, _("""Determines how many commands users are allowed per minute. If a user sends more than this many commands in any 60 second period, they will be ignored for supybot.abuse.flood.command.punishment seconds."""))) registerGlobalValue(supybot.abuse.flood.command, 'punishment', registry.PositiveInteger(300, _("""Determines how many seconds the bot will ignore users who flood it with commands."""))) registerGlobalValue(supybot.abuse.flood.command, 'notify', registry.Boolean(True, _("""Determines whether the bot will notify people that they're being ignored for command flooding."""))) registerGlobalValue(supybot.abuse.flood.command, 'invalid', registry.Boolean(True, _("""Determines whether the bot will defend itself against invalid command-flooding."""))) registerGlobalValue(supybot.abuse.flood.command.invalid, 'maximum', registry.PositiveInteger(5, _("""Determines how many invalid commands users are allowed per minute. If a user sends more than this many invalid commands in any 60 second period, they will be ignored for supybot.abuse.flood.command.invalid.punishment seconds. Typically, this value is lower than supybot.abuse.flood.command.maximum, since it's far less likely (and far more annoying) for users to flood with invalid commands than for them to flood with valid commands."""))) registerGlobalValue(supybot.abuse.flood.command.invalid, 'punishment', registry.PositiveInteger(600, _("""Determines how many seconds the bot will ignore users who flood it with invalid commands. Typically, this value is higher than supybot.abuse.flood.command.punishment, since it's far less likely (and far more annoying) for users to flood with invalid commands than for them to flood with valid commands."""))) registerGlobalValue(supybot.abuse.flood.command.invalid, 'notify', registry.Boolean(True, _("""Determines whether the bot will notify people that they're being ignored for invalid command flooding."""))) ### # supybot.drivers. For stuff relating to Supybot's drivers (duh!) ### registerGroup(supybot, 'drivers') registerGlobalValue(supybot.drivers, 'poll', registry.PositiveFloat(1.0, _("""Determines the default length of time a driver should block waiting for input."""))) class ValidDriverModule(registry.OnlySomeStrings): __slots__ = () validStrings = ('default', 'Socket', 'Twisted') registerGlobalValue(supybot.drivers, 'module', ValidDriverModule('default', _("""Determines what driver module the bot will use. The default is Socket which is simple and stable and supports SSL. Twisted doesn't work if the IRC server which you are connecting to has IPv6 (most of them do)."""))) registerGlobalValue(supybot.drivers, 'maxReconnectWait', registry.PositiveFloat(300.0, _("""Determines the maximum time the bot will wait before attempting to reconnect to an IRC server. The bot may, of course, reconnect earlier if possible."""))) ### # supybot.directories, for stuff relating to directories. ### # XXX This shouldn't make directories willy-nilly. As it is now, if it's # configured, it'll still make the default directories, I think. class Directory(registry.String): __slots__ = () def __call__(self): # ??? Should we perhaps always return an absolute path here? v = super(Directory, self).__call__() if not os.path.exists(v): os.mkdir(v) return v def dirize(self, filename): myself = self() if os.path.isabs(filename): filename = os.path.abspath(filename) selfAbs = os.path.abspath(myself) commonPrefix = os.path.commonprefix([selfAbs, filename]) filename = filename[len(commonPrefix):] elif not os.path.isabs(myself): if filename.startswith(myself): filename = filename[len(myself):] filename = filename.lstrip(os.path.sep) # Stupid os.path.join! return os.path.join(myself, filename) class DataFilename(registry.String): __slots__ = () def __call__(self): v = super(DataFilename, self).__call__() dataDir = supybot.directories.data() if not v.startswith(dataDir): v = os.path.basename(v) v = os.path.join(dataDir, v) self.setValue(v) return v class DataFilenameDirectory(DataFilename, Directory): __slots__ = () def __call__(self): v = DataFilename.__call__(self) v = Directory.__call__(self) return v registerGroup(supybot, 'directories') registerGlobalValue(supybot.directories, 'conf', Directory('conf', _("""Determines what directory configuration data is put into."""))) registerGlobalValue(supybot.directories, 'data', Directory('data', _("""Determines what directory data is put into."""))) registerGlobalValue(supybot.directories, 'backup', Directory('backup', _("""Determines what directory backup data is put into. Set it to /dev/null to disable backup (it is a special value, so it also works on Windows and systems without /dev/null)."""))) registerGlobalValue(supybot.directories, 'log', Directory('logs', """Determines what directory the bot will store its logfiles in.""")) registerGlobalValue(supybot.directories.data, 'tmp', DataFilenameDirectory('tmp', _("""Determines what directory temporary files are put into."""))) registerGlobalValue(supybot.directories.data, 'web', DataFilenameDirectory('web', _("""Determines what directory files of the web server (templates, custom images, ...) are put into."""))) def _update_tmp(): utils.file.AtomicFile.default.tmpDir = supybot.directories.data.tmp supybot.directories.data.tmp.addCallback(_update_tmp) _update_tmp() def _update_backup(): utils.file.AtomicFile.default.backupDir = supybot.directories.backup supybot.directories.backup.addCallback(_update_backup) _update_backup() registerGlobalValue(supybot.directories, 'plugins', registry.CommaSeparatedListOfStrings([], _("""Determines what directories the bot will look for plugins in. Accepts a comma-separated list of strings. This means that to add another directory, you can nest the former value and add a new one. E.g. you can say: bot: 'config supybot.directories.plugins [config supybot.directories.plugins], newPluginDirectory'."""))) registerGlobalValue(supybot, 'plugins', registry.SpaceSeparatedSetOfStrings([], _("""Determines what plugins will be loaded."""), orderAlphabetically=True)) registerGlobalValue(supybot.plugins, 'alwaysLoadImportant', registry.Boolean(True, _("""Determines whether the bot will always load important plugins (Admin, Channel, Config, Misc, Owner, and User) regardless of what their configured state is. Generally, if these plugins are configured not to load, you didn't do it on purpose, and you still want them to load. Users who don't want to load these plugins are smart enough to change the value of this variable appropriately :)"""))) ### # supybot.databases. For stuff relating to Supybot's databases (duh!) ### class Databases(registry.SpaceSeparatedListOfStrings): __slots__ = () def __call__(self): v = super(Databases, self).__call__() if not v: v = ['anydbm', 'dbm', 'cdb', 'flat', 'pickle'] if 'sqlite' in sys.modules: v.insert(0, 'sqlite') if 'sqlite3' in sys.modules: v.insert(0, 'sqlite3') if 'sqlalchemy' in sys.modules: v.insert(0, 'sqlalchemy') return v def serialize(self): return ' '.join(self.value) registerGlobalValue(supybot, 'databases', Databases([], _("""Determines what databases are available for use. If this value is not configured (that is, if its value is empty) then sane defaults will be provided."""))) registerGroup(supybot.databases, 'users') registerGlobalValue(supybot.databases.users, 'filename', registry.String('users.conf', _("""Determines what filename will be used for the users database. This file will go into the directory specified by the supybot.directories.conf variable."""))) registerGlobalValue(supybot.databases.users, 'timeoutIdentification', registry.Integer(0, _("""Determines how long it takes identification to time out. If the value is less than or equal to zero, identification never times out."""))) registerGlobalValue(supybot.databases.users, 'allowUnregistration', registry.Boolean(False, _("""Determines whether the bot will allow users to unregister their users. This can wreak havoc with already-existing databases, so by default we don't allow it. Enable this at your own risk. (Do also note that this does not prevent the owner of the bot from using the unregister command.) """))) registerGroup(supybot.databases, 'ignores') registerGlobalValue(supybot.databases.ignores, 'filename', registry.String('ignores.conf', _("""Determines what filename will be used for the ignores database. This file will go into the directory specified by the supybot.directories.conf variable."""))) registerGroup(supybot.databases, 'channels') registerGlobalValue(supybot.databases.channels, 'filename', registry.String('channels.conf', _("""Determines what filename will be used for the channels database. This file will go into the directory specified by the supybot.directories.conf variable."""))) # TODO This will need to do more in the future (such as making sure link.allow # will let the link occur), but for now let's just leave it as this. class ChannelSpecific(registry.Boolean): __slots__ = () def getChannelLink(self, channel): channelSpecific = supybot.databases.plugins.channelSpecific channels = [channel] def hasLinkChannel(channel): if not get(channelSpecific, channel): lchannel = get(channelSpecific.link, channel) if not get(channelSpecific.link.allow, lchannel): return False return channel != lchannel return False lchannel = channel while hasLinkChannel(lchannel): lchannel = get(channelSpecific.link, lchannel) if lchannel not in channels: channels.append(lchannel) else: # Found a cyclic link. We'll just use the current channel lchannel = channel break return lchannel registerGroup(supybot.databases, 'plugins') registerChannelValue(supybot.databases.plugins, 'requireRegistration', registry.Boolean(True, _("""Determines whether the bot will require user registration to use 'add' commands in database-based Supybot plugins."""))) registerChannelValue(supybot.databases.plugins, 'channelSpecific', ChannelSpecific(True, _("""Determines whether database-based plugins that can be channel-specific will be so. This can be overridden by individual channels. Do note that the bot needs to be restarted immediately after changing this variable or your db plugins may not work for your channel; also note that you may wish to set supybot.databases.plugins.channelSpecific.link appropriately if you wish to share a certain channel's databases globally."""))) registerChannelValue(supybot.databases.plugins.channelSpecific, 'link', ValidChannel('#', _("""Determines what channel global (non-channel-specific) databases will be considered a part of. This is helpful if you've been running channel-specific for awhile and want to turn the databases for your primary channel into global databases. If supybot.databases.plugins.channelSpecific.link.allow prevents linking, the current channel will be used. Do note that the bot needs to be restarted immediately after changing this variable or your db plugins may not work for your channel."""))) registerChannelValue(supybot.databases.plugins.channelSpecific.link, 'allow', registry.Boolean(True, _("""Determines whether another channel's global (non-channel-specific) databases will be allowed to link to this channel's databases. Do note that the bot needs to be restarted immediately after changing this variable or your db plugins may not work for your channel. """))) class CDB(registry.Boolean): __slots__ = () def connect(self, filename): from . import cdb basename = os.path.basename(filename) journalName = supybot.directories.data.tmp.dirize(basename+'.journal') return cdb.open_db(filename, 'c', journalName=journalName, maxmods=self.maximumModifications()) registerGroup(supybot.databases, 'types') registerGlobalValue(supybot.databases.types, 'cdb', CDB(True, _("""Determines whether CDB databases will be allowed as a database implementation."""))) registerGlobalValue(supybot.databases.types.cdb, 'maximumModifications', registry.Probability(0.5, _("""Determines how often CDB databases will have their modifications flushed to disk. When the number of modified records is greater than this fraction of the total number of records, the database will be entirely flushed to disk."""))) # XXX Configuration variables for dbi, sqlite, flat, mysql, etc. ### # Protocol information. ### originalIsNick = ircutils.isNick def isNick(s, strictRfc=None, **kw): if strictRfc is None: strictRfc = supybot.protocols.irc.strictRfc() return originalIsNick(s, strictRfc=strictRfc, **kw) ircutils.isNick = isNick ### # supybot.protocols ### registerGroup(supybot, 'protocols') ### # supybot.protocols.irc ### registerGroup(supybot.protocols, 'irc') class Banmask(registry.SpaceSeparatedSetOfStrings): __slots__ = ('__parent', '__dict__') # __dict__ is needed to set __doc__ validStrings = ('exact', 'nick', 'user', 'host') def __init__(self, *args, **kwargs): assert self.validStrings, 'There must be some valid strings. ' \ 'This is a bug.' self.__parent = super(Banmask, self) self.__parent.__init__(*args, **kwargs) self.__doc__ = format('Valid values include %L.', list(map(repr, self.validStrings))) def help(self): strings = [s for s in self.validStrings if s] return format('%s Valid strings: %L.', self._help, strings) def normalize(self, s): lowered = s.lower() L = list(map(str.lower, self.validStrings)) try: i = L.index(lowered) except ValueError: return s # This is handled in setValue. return self.validStrings[i] def setValue(self, v): v = list(map(self.normalize, v)) for s in v: if s not in self.validStrings: self.error() self.__parent.setValue(self.List(v)) def makeBanmask(self, hostmask, options=None, channel=None, network=None): """Create a banmask from the given hostmask. If a style of banmask isn't specified via options, the value of conf.supybot.protocols.irc.banmask is used. options - A list specifying which parts of the hostmask should explicitly be matched: nick, user, host. If 'exact' is given, then only the exact hostmask will be used.""" if not channel: channel = dynamic.channel if not network: network = dynamic.irc.network (nick, user, host) = ircutils.splitHostmask(hostmask) bnick = '*' buser = '*' bhost = '*' if not options: options = supybot.protocols.irc.banmask.getSpecific( network, channel)() for option in options: if option == 'nick': bnick = nick elif option == 'user': buser = user elif option == 'host': bhost = host elif option == 'exact': return hostmask if (bnick, buser, bhost) == ('*', '*', '*') and \ ircutils.isUserHostmask(hostmask): return hostmask return ircutils.joinHostmask(bnick, buser, bhost) registerChannelValue(supybot.protocols.irc, 'banmask', Banmask(['host'], _("""Determines what will be used as the default banmask style."""))) registerGlobalValue(supybot.protocols.irc, 'strictRfc', registry.Boolean(False, _("""Determines whether the bot will strictly follow the RFC; currently this only affects what strings are considered to be nicks. If you're using a server or a network that requires you to message a nick such as services@this.network.server then you you should set this to False."""))) registerGlobalValue(supybot.protocols.irc, 'certfile', registry.String('', _("""Determines what certificate file (if any) the bot will use connect with SSL sockets by default."""))) registerGlobalValue(supybot.protocols.irc, 'umodes', registry.String('', _("""Determines what user modes the bot will request from the server when it first connects. Many people might choose +i; some networks allow +x, which indicates to the auth services on those networks that you should be given a fake host."""))) registerGlobalValue(supybot.protocols.irc, 'vhost', registry.String('', _("""Determines what vhost the bot will bind to before connecting a server (IRC, HTTP, ...) via IPv4."""))) registerGlobalValue(supybot.protocols.irc, 'vhostv6', registry.String('', _("""Determines what vhost the bot will bind to before connecting a server (IRC, HTTP, ...) via IPv6."""))) registerGlobalValue(supybot.protocols.irc, 'maxHistoryLength', registry.Integer(1000, _("""Determines how many old messages the bot will keep around in its history. Changing this variable will not take effect on a network until it is reconnected."""))) registerGlobalValue(supybot.protocols.irc, 'throttleTime', registry.Float(1.0, _("""A floating point number of seconds to throttle queued messages -- that is, messages will not be sent faster than once per throttleTime seconds."""))) registerGlobalValue(supybot.protocols.irc, 'ping', registry.Boolean(True, _("""Determines whether the bot will send PINGs to the server it's connected to in order to keep the connection alive and discover earlier when it breaks. Really, this option only exists for debugging purposes: you always should make it True unless you're testing some strange server issues."""))) registerGlobalValue(supybot.protocols.irc.ping, 'interval', registry.Integer(120, _("""Determines the number of seconds between sending pings to the server, if pings are being sent to the server."""))) registerGroup(supybot.protocols.irc, 'queuing') registerGlobalValue(supybot.protocols.irc.queuing, 'duplicates', registry.Boolean(False, _("""Determines whether the bot will refuse duplicated messages to be queued for delivery to the server. This is a safety mechanism put in place to prevent plugins from sending the same message multiple times; most of the time it doesn't matter, unless you're doing certain kinds of plugin hacking."""))) registerGroup(supybot.protocols.irc.queuing, 'rateLimit') registerGlobalValue(supybot.protocols.irc.queuing.rateLimit, 'join', registry.Float(0, _("""Determines how many seconds must elapse between JOINs sent to the server."""))) ### # supybot.protocols.http ### registerGroup(supybot.protocols, 'http') registerGlobalValue(supybot.protocols.http, 'peekSize', registry.PositiveInteger(8192, _("""Determines how many bytes the bot will 'peek' at when looking through a URL for a doctype or title or something similar. It'll give up after it reads this many bytes, even if it hasn't found what it was looking for."""))) class HttpProxy(registry.String): """Value must be a valid hostname:port string.""" __slots__ = () def setValue(self, v): proxies = {} if v != "": if isSocketAddress(v): proxies = { 'http': v, 'https': v } else: self.error() proxyHandler = ProxyHandler(proxies) proxyOpenerDirector = build_opener(proxyHandler) install_opener(proxyOpenerDirector) super(HttpProxy, self).setValue(v) registerGlobalValue(supybot.protocols.http, 'proxy', HttpProxy('', _("""Determines what HTTP proxy all HTTP requests should go through. The value should be of the form 'host:port'."""))) utils.web.proxy = supybot.protocols.http.proxy def defaultHttpHeaders(network, channel): """Returns the default HTTP headers to use for this channel/network.""" headers = utils.web.baseDefaultHeaders.copy() try: language = supybot.protocols.http.requestLanguage.getSpecific( network, channel)() except registry.NonExistentRegistryEntry: pass # Starting up; language will be set by HttpRequestLanguage later else: if language: headers['Accept-Language'] = language elif 'Accept-Language' in headers: del headers['Accept-Language'] return headers class HttpRequestLanguage(registry.String): """Must be a valid HTTP Accept-Language value.""" __slots__ = () def setValue(self, v): super(HttpRequestLanguage, self).setValue(v) utils.web.defaultHeaders = defaultHttpHeaders(None, None) registerChannelValue(supybot.protocols.http, 'requestLanguage', HttpRequestLanguage('', _("""If set, the Accept-Language HTTP header will be set to this value for requests. Useful for overriding the auto-detected language based on the server's location."""))) ### # supybot.protocols.ssl ### registerGroup(supybot.protocols, 'ssl') registerGlobalValue(supybot.protocols.ssl, 'verifyCertificates', registry.Boolean(False, _("""Determines whether server certificates will be verified, which checks whether the server certificate is signed by a known certificate authority, and aborts the connection if it is not."""))) ### # HTTP server ### registerGroup(supybot, 'servers') registerGroup(supybot.servers, 'http') class IP(registry.String): """Value must be a valid IP.""" __slots__ = () def setValue(self, v): if v and not utils.net.isIP(v): self.error() else: registry.String.setValue(self, v) class ListOfIPs(registry.SpaceSeparatedListOfStrings): __slots__ = () Value = IP registerGlobalValue(supybot.servers.http, 'singleStack', registry.Boolean(True, _("""If true, uses IPV6_V6ONLY to disable forwaring of IPv4 traffic to IPv6 sockets. On *nix, has the same effect as setting kernel variable net.ipv6.bindv6only to 1."""))) registerGlobalValue(supybot.servers.http, 'hosts4', ListOfIPs(['0.0.0.0'], _("""Space-separated list of IPv4 hosts the HTTP server will bind."""))) registerGlobalValue(supybot.servers.http, 'hosts6', ListOfIPs(['::0'], _("""Space-separated list of IPv6 hosts the HTTP server will bind."""))) registerGlobalValue(supybot.servers.http, 'port', registry.Integer(8080, _("""Determines what port the HTTP server will bind."""))) registerGlobalValue(supybot.servers.http, 'keepAlive', registry.Boolean(False, _("""Determines whether the server will stay alive if no plugin is using it. This also means that the server will start even if it is not used."""))) registerGlobalValue(supybot.servers.http, 'favicon', registry.String('', _("""Determines the path of the file served as favicon to browsers."""))) ### # Especially boring stuff. ### registerGlobalValue(supybot, 'defaultIgnore', registry.Boolean(False, _("""Determines whether the bot will ignore unidentified users by default. Of course, that'll make it particularly hard for those users to register or identify with the bot without adding their hostmasks, but that's your problem to solve."""))) registerGlobalValue(supybot, 'externalIP', IP('', _("""A string that is the external IP of the bot. If this is the empty string, the bot will attempt to find out its IP dynamically (though sometimes that doesn't work, hence this variable). This variable is not used by Limnoria and its built-in plugins: see supybot.protocols.irc.vhost / supybot.protocols.irc.vhost6 to set the IRC bind host, and supybot.servers.http.hosts4 / supybot.servers.http.hosts6 to set the HTTP server bind host."""))) class SocketTimeout(registry.PositiveInteger): """Value must be an integer greater than supybot.drivers.poll and must be greater than or equal to 1.""" __slots__ = () def setValue(self, v): if v < supybot.drivers.poll() or v < 1: self.error() registry.PositiveInteger.setValue(self, v) socket.setdefaulttimeout(self.value) registerGlobalValue(supybot, 'defaultSocketTimeout', SocketTimeout(10, _("""Determines what the default timeout for socket objects will be. This means that *all* sockets will timeout when this many seconds has gone by (unless otherwise modified by the author of the code that uses the sockets)."""))) registerGlobalValue(supybot, 'pidFile', registry.String('', _("""Determines what file the bot should write its PID (Process ID) to, so you can kill it more easily. If it's left unset (as is the default) then no PID file will be written. A restart is required for changes to this variable to take effect."""))) ### # Debugging options. ### registerGroup(supybot, 'debug') registerGlobalValue(supybot.debug, 'threadAllCommands', registry.Boolean(False, _("""Determines whether the bot will automatically thread all commands."""))) registerGlobalValue(supybot.debug, 'flushVeryOften', registry.Boolean(False, _("""Determines whether the bot will automatically flush all flushers *very* often. Useful for debugging when you don't know what's breaking or when, but think that it might be logged."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/dbi.py0000644000175000017500000003151613634634532015224 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Module for some slight database-independence for simple databases. """ import os import csv import math from . import cdb, utils from .utils import minisix from .utils.iter import ilen class Error(Exception): """General error for this module.""" class NoRecordError(KeyError): pass class InvalidDBError(Exception): pass class MappingInterface(object): """This is a class to represent the underlying representation of a map from integer keys to strings.""" def __init__(self, filename, **kwargs): """Feel free to ignore the filename.""" raise NotImplementedError def get(id): """Gets the record matching id. Raises NoRecordError otherwise.""" raise NotImplementedError def set(id, s): """Sets the record matching id to s.""" raise NotImplementedError def add(self, s): """Adds a new record, returning a new id for it.""" raise NotImplementedError def remove(self, id): "Returns and removes the record with the given id from the database." raise NotImplementedError def __iter__(self): "Return an iterator over (id, s) pairs. Not required to be ordered." raise NotImplementedError def flush(self): """Flushes current state to disk.""" raise NotImplementedError def close(self): """Flushes current state to disk and invalidates the Mapping.""" raise NotImplementedError def vacuum(self): "Cleans up in the database, if possible. Not required to do anything." pass class DirMapping(MappingInterface): def __init__(self, filename, **kwargs): self.dirname = filename if not os.path.exists(self.dirname): os.mkdir(self.dirname) if not os.path.exists(os.path.join(self.dirname, 'max')): self._setMax(1) def _setMax(self, id): fd = open(os.path.join(self.dirname, 'max'), 'w') try: fd.write(str(id)) finally: fd.close() def _getMax(self): fd = open(os.path.join(self.dirname, 'max')) try: i = int(fd.read()) return i finally: fd.close() def _makeFilename(self, id): return os.path.join(self.dirname, str(id)) def get(self, id): try: fd = open(self._makeFilename(id)) return fd.read() except EnvironmentError as e: exn = NoRecordError(id) exn.realException = e raise exn finally: fd.close() def set(self, id, s): fd = open(self._makeFilename(id), 'w') fd.write(s) fd.close() def add(self, s): id = self._getMax() fd = open(self._makeFilename(id), 'w') try: fd.write(s) return id finally: fd.close() def remove(self, id): try: os.remove(self._makeFilename(id)) except EnvironmentError: raise NoRecordError(id) class FlatfileMapping(MappingInterface): def __init__(self, filename, maxSize=10**6): self.filename = filename try: fd = open(self.filename) strId = fd.readline().rstrip() self.maxSize = len(strId) try: self.currentId = int(strId) except ValueError: raise Error('Invalid file for FlatfileMapping: %s' % filename) except EnvironmentError as e: # File couldn't be opened. self.maxSize = int(math.log10(maxSize)) self.currentId = 0 self._incrementCurrentId() finally: if 'fd' in locals(): fd.close() def _canonicalId(self, id): if id is not None: return str(id).zfill(self.maxSize) else: return '-'*self.maxSize def _incrementCurrentId(self, fd=None): fdWasNone = fd is None if fdWasNone: fd = open(self.filename, 'a') fd.seek(0) self.currentId += 1 fd.write(self._canonicalId(self.currentId)) fd.write('\n') if fdWasNone: fd.close() def _splitLine(self, line): line = line.rstrip('\r\n') (id, s) = line.split(':', 1) return (id, s) def _joinLine(self, id, s): return '%s:%s\n' % (self._canonicalId(id), s) def add(self, s): line = self._joinLine(self.currentId, s) fd = open(self.filename, 'r+') try: fd.seek(0, 2) # End. fd.write(line) return self.currentId finally: self._incrementCurrentId(fd) fd.close() def get(self, id): strId = self._canonicalId(id) try: fd = open(self.filename) fd.readline() # First line, nextId. for line in fd: (lineId, s) = self._splitLine(line) if lineId == strId: return s raise NoRecordError(id) finally: fd.close() # XXX This assumes it's not been given out. We should make sure that our # maximum id remains accurate if this is some value we've never given # out -- i.e., self.maxid = max(self.maxid, id) or something. def set(self, id, s): strLine = self._joinLine(id, s) try: fd = open(self.filename, 'r+') self.remove(id, fd) fd.seek(0, 2) # End. fd.write(strLine) finally: fd.close() def remove(self, id, fd=None): fdWasNone = fd is None strId = self._canonicalId(id) try: if fdWasNone: fd = open(self.filename, 'r+') fd.seek(0) fd.readline() # First line, nextId pos = fd.tell() line = fd.readline() while line: (lineId, _) = self._splitLine(line) if lineId == strId: fd.seek(pos) fd.write(self._canonicalId(None)) fd.seek(pos) fd.readline() # Same line we just rewrote the id for. pos = fd.tell() line = fd.readline() # We should be at the end. finally: if fdWasNone: fd.close() def __iter__(self): fd = open(self.filename) fd.readline() # First line, nextId. for line in fd: (id, s) = self._splitLine(line) if not id.startswith('-'): yield (int(id), s) fd.close() def vacuum(self): infd = open(self.filename) outfd = utils.file.AtomicFile(self.filename,makeBackupIfSmaller=False) outfd.write(infd.readline()) # First line, nextId. for line in infd: if not line.startswith('-'): outfd.write(line) infd.close() outfd.close() def flush(self): pass # No-op, we maintain no open files. def close(self): self.vacuum() # Should we do this? It should be fine. class CdbMapping(MappingInterface): def __init__(self, filename, **kwargs): self.filename = filename self._openCdb() # So it can be overridden later. if 'nextId' not in self.db: self.db['nextId'] = '1' def _openCdb(self, *args, **kwargs): self.db = cdb.open_db(self.filename, 'c', **kwargs) def _getNextId(self): i = int(self.db['nextId']) self.db['nextId'] = str(i+1) return i def get(self, id): try: return self.db[str(id)] except KeyError: raise NoRecordError(id) # XXX Same as above. def set(self, id, s): self.db[str(id)] = s def add(self, s): id = self._getNextId() self.set(id, s) return id def remove(self, id): del self.db[str(id)] def __iter__(self): for (id, s) in self.db.items(): if id != 'nextId': yield (int(id), s) def flush(self): self.db.flush() def close(self): self.db.close() class DB(object): Mapping = 'flat' # This is a good, sane default. Record = None def __init__(self, filename, Mapping=None, Record=None): if Record is not None: self.Record = Record if Mapping is not None: self.Mapping = Mapping if isinstance(self.Mapping, minisix.string_types): self.Mapping = Mappings[self.Mapping] self.map = self.Mapping(filename) def _newRecord(self, id, s): record = self.Record(id=id) record.deserialize(s) return record def get(self, id): s = self.map.get(id) return self._newRecord(id, s) def set(self, id, record): s = record.serialize() self.map.set(id, s) def add(self, record): s = record.serialize() id = self.map.add(s) record.id = id return id def remove(self, id): self.map.remove(id) def __iter__(self): for (id, s) in self.map: # We don't need to yield the id because it's in the record. yield self._newRecord(id, s) def select(self, p): for record in self: if p(record): yield record def random(self): try: return self._newRecord(*utils.iter.choice(self.map)) except IndexError: return None def size(self): return ilen(self.map) def flush(self): self.map.flush() def vacuum(self): self.map.vacuum() def close(self): self.map.close() Mappings = { 'cdb': CdbMapping, 'flat': FlatfileMapping, } class Record(object): def __init__(self, id=None, **kwargs): if id is not None: assert isinstance(id, int), 'id must be an integer.' self.id = id self.fields = [] self.defaults = {} self.converters = {} for name in self.__fields__: if isinstance(name, tuple): (name, spec) = name else: spec = utils.safeEval assert name != 'id' self.fields.append(name) if isinstance(spec, tuple): (converter, default) = spec else: converter = spec default = None self.defaults[name] = default self.converters[name] = converter seen = set() for (name, value) in kwargs.items(): assert name in self.fields, 'name must be a record value.' seen.add(name) setattr(self, name, value) for name in self.fields: if name not in seen: default = self.defaults[name] if callable(default): default = default() setattr(self, name, default) def serialize(self): return csv.join([repr(getattr(self, name)) for name in self.fields]) def deserialize(self, s): unseenRecords = set(self.fields) for (name, strValue) in zip(self.fields, csv.split(s)): setattr(self, name, self.converters[name](strValue)) unseenRecords.remove(name) for name in unseenRecords: setattr(self, name, self.defaults[name]) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/drivers/0000755000175000017500000000000013634634547015572 5ustar valval00000000000000limnoria-2020.03.17/src/drivers/Socket.py0000644000175000017500000004030113634634532017364 0ustar valval00000000000000## # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2010, 2013, James McCoy # 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. ### """ Contains simple socket drivers. Asyncore bugged (haha, pun!) me. """ from __future__ import division import os import time import errno import threading import select import socket import sys try: import ipaddress # Python >= 3.3 or backported ipaddress except ImportError: # Python < 3.3 ipaddress = None from .. import (conf, drivers, log, utils, world) from ..utils import minisix from ..utils.str import decode_raw_line try: import ssl SSLError = ssl.SSLError except: drivers.log.debug('ssl module is not available, ' 'cannot connect to SSL servers.') class SSLError(Exception): pass class SocketDriver(drivers.IrcDriver, drivers.ServersMixin): _instances = [] _selecting = threading.Lock() def __init__(self, irc): assert irc is not None self.irc = irc drivers.IrcDriver.__init__(self, irc) drivers.ServersMixin.__init__(self, irc) self.conn = None self._attempt = -1 self.servers = () self.eagains = 0 self.inbuffer = b'' self.outbuffer = '' self.zombie = False self.connected = False self.writeCheckTime = None self.nextReconnectTime = None self.resetDelay() if self.networkGroup.get('ssl').value and 'ssl' not in globals(): drivers.log.error('The Socket driver can not connect to SSL ' 'servers for your Python version. Try the ' 'Twisted driver instead, or install a Python' 'version that supports SSL (2.6 and greater).') self.ssl = False else: self.ssl = self.networkGroup.get('ssl').value self.connect() def getDelay(self): ret = self.currentDelay self.currentDelay = min(self.currentDelay * 2, conf.supybot.drivers.maxReconnectWait()) return ret def resetDelay(self): self.currentDelay = 10.0 def _getNextServer(self): oldServer = getattr(self, 'currentServer', None) server = drivers.ServersMixin._getNextServer(self) if self.currentServer != oldServer: self.resetDelay() return server def _handleSocketError(self, e): # (11, 'Resource temporarily unavailable') raised if connect # hasn't finished yet. We'll keep track of how many we get. if e.args[0] != 11 or self.eagains > 120: drivers.log.disconnect(self.currentServer, e) if self in self._instances: self._instances.remove(self) try: self.conn.close() except: pass self.connected = False self.scheduleReconnect() else: log.debug('Got EAGAIN, current count: %s.', self.eagains) self.eagains += 1 def _sendIfMsgs(self): if not self.connected: return if not self.zombie: msgs = [self.irc.takeMsg()] while msgs[-1] is not None: msgs.append(self.irc.takeMsg()) del msgs[-1] self.outbuffer += ''.join(map(str, msgs)) if self.outbuffer: try: if minisix.PY2: sent = self.conn.send(self.outbuffer) else: sent = self.conn.send(self.outbuffer.encode()) self.outbuffer = self.outbuffer[sent:] self.eagains = 0 except socket.error as e: self._handleSocketError(e) if self.zombie and not self.outbuffer: self._reallyDie() @classmethod def _select(cls): try: if not cls._selecting.acquire(blocking=False): # there's already a thread running this code, abort. return for inst in cls._instances: # Do not use a list comprehension here, we have to edit the list # and not to reassign it. if not inst.connected or \ (minisix.PY3 and inst.conn._closed) or \ (minisix.PY2 and inst.conn._sock.__class__ is socket._closedsocket): cls._instances.remove(inst) elif inst.conn.fileno() == -1: inst.reconnect() if not cls._instances: return rlist, wlist, xlist = select.select([x.conn for x in cls._instances], [], [], conf.supybot.drivers.poll()) for instance in cls._instances: if instance.conn in rlist: instance._read() except select.error as e: if e.args[0] != errno.EINTR: # 'Interrupted system call' raise finally: cls._selecting.release() for instance in cls._instances: if instance.irc and not instance.irc.zombie: instance._sendIfMsgs() def run(self): now = time.time() if self.nextReconnectTime is not None and now > self.nextReconnectTime: self.reconnect() elif self.writeCheckTime is not None and now > self.writeCheckTime: self._checkAndWriteOrReconnect() if not self.connected: # We sleep here because otherwise, if we're the only driver, we'll # spin at 100% CPU while we're disconnected. time.sleep(conf.supybot.drivers.poll()) return self._sendIfMsgs() self._select() def _read(self): """Called by _select() when we can read data.""" try: self.inbuffer += self.conn.recv(1024) self.eagains = 0 # If we successfully recv'ed, we can reset this. lines = self.inbuffer.split(b'\n') self.inbuffer = lines.pop() for line in lines: line = decode_raw_line(line) msg = drivers.parseMsg(line) if msg is not None and self.irc is not None: self.irc.feedMsg(msg) except socket.timeout: pass except SSLError as e: if e.args[0] == 'The read operation timed out': pass else: self._handleSocketError(e) return except socket.error as e: self._handleSocketError(e) return if self.irc and not self.irc.zombie: self._sendIfMsgs() def connect(self, **kwargs): self.reconnect(reset=False, **kwargs) def reconnect(self, wait=False, reset=True): self._attempt += 1 self.nextReconnectTime = None if self.connected: drivers.log.reconnect(self.irc.network) if self in self._instances: self._instances.remove(self) try: self.conn.shutdown(socket.SHUT_RDWR) except: # "Transport endpoint not connected" pass self.conn.close() self.connected = False if reset: drivers.log.debug('Resetting %s.', self.irc) self.irc.reset() else: drivers.log.debug('Not resetting %s.', self.irc) if wait: self.scheduleReconnect() return self.server = self._getNextServer() network_config = getattr(conf.supybot.networks, self.irc.network) socks_proxy = network_config.socksproxy() try: if socks_proxy: import socks except ImportError: log.error('Cannot use socks proxy (SocksiPy not installed), ' 'using direct connection instead.') socks_proxy = '' if socks_proxy: address = self.server[0] else: try: address = utils.net.getAddressFromHostname(self.server[0], attempt=self._attempt) except (socket.gaierror, socket.error) as e: drivers.log.connectError(self.currentServer, e) self.scheduleReconnect() return port = self.server[1] drivers.log.connect(self.currentServer) try: self.conn = utils.net.getSocket(address, port=port, socks_proxy=socks_proxy, vhost=conf.supybot.protocols.irc.vhost(), vhostv6=conf.supybot.protocols.irc.vhostv6(), ) except socket.error as e: drivers.log.connectError(self.currentServer, e) self.scheduleReconnect() return # We allow more time for the connect here, since it might take longer. # At least 10 seconds. self.conn.settimeout(max(10, conf.supybot.drivers.poll()*10)) try: # Connect before SSL, otherwise SSL is disabled if we use SOCKS. # See http://stackoverflow.com/q/16136916/539465 self.conn.connect((address, port)) if network_config.ssl(): self.starttls() # Suppress this warning for loopback IPs. targetip = address if sys.version_info[0] < 3: # Backported Python 2 ipaddress demands unicode instead of str targetip = targetip.decode('utf-8') elif (not network_config.requireStarttls()) and \ (not network_config.ssl()) and \ (ipaddress is None or not ipaddress.ip_address(targetip).is_loopback): drivers.log.warning(('Connection to network %s ' 'does not use SSL/TLS, which makes it vulnerable to ' 'man-in-the-middle attacks and passive eavesdropping. ' 'You should consider upgrading your connection to SSL/TLS ' '') % self.irc.network) def setTimeout(): self.conn.settimeout(conf.supybot.drivers.poll()) conf.supybot.drivers.poll.addCallback(setTimeout) setTimeout() self.connected = True self.resetDelay() except socket.error as e: if e.args[0] == 115: now = time.time() when = now + 60 whenS = log.timestamp(when) drivers.log.debug('Connection in progress, scheduling ' 'connectedness check for %s', whenS) self.writeCheckTime = when else: drivers.log.connectError(self.currentServer, e) self.scheduleReconnect() return self._instances.append(self) def _checkAndWriteOrReconnect(self): self.writeCheckTime = None drivers.log.debug('Checking whether we are connected.') (_, w, _) = select.select([], [self.conn], [], 0) if w: drivers.log.debug('Socket is writable, it might be connected.') self.connected = True self.resetDelay() else: drivers.log.connectError(self.currentServer, 'Timed out') self.reconnect() def scheduleReconnect(self): when = time.time() + self.getDelay() if not world.dying: drivers.log.reconnect(self.irc.network, when) if self.nextReconnectTime: drivers.log.error('Updating next reconnect time when one is ' 'already present. This is a bug; please ' 'report it, with an explanation of what caused ' 'this to happen.') self.nextReconnectTime = when def die(self): if self in self._instances: self._instances.remove(self) self.zombie = True if self.nextReconnectTime is not None: self.nextReconnectTime = None if self.writeCheckTime is not None: self.writeCheckTime = None drivers.log.die(self.irc) def _reallyDie(self): if self.conn is not None: self.conn.close() drivers.IrcDriver.die(self) # self.irc.die() Kill off the ircs yourself, jerk! def name(self): return '%s(%s)' % (self.__class__.__name__, self.irc) def starttls(self): assert 'ssl' in globals() network_config = getattr(conf.supybot.networks, self.irc.network) certfile = network_config.certfile() if not certfile: certfile = conf.supybot.protocols.irc.certfile() if not certfile: certfile = None elif not os.path.isfile(certfile): drivers.log.warning('Could not find cert file %s.' % certfile) certfile = None verifyCertificates = conf.supybot.protocols.ssl.verifyCertificates() if not verifyCertificates: drivers.log.warning('Not checking SSL certificates, connections ' 'are vulnerable to man-in-the-middle attacks. Set ' 'supybot.protocols.ssl.verifyCertificates to "true" ' 'to enable validity checks.') try: self.conn = utils.net.ssl_wrap_socket(self.conn, logger=drivers.log, hostname=self.server[0], certfile=certfile, verify=verifyCertificates, trusted_fingerprints=network_config.ssl.serverFingerprints(), ca_file=network_config.ssl.authorityCertificate(), ) except getattr(ssl, 'CertificateError', None) as e: # Default to None for old Python version, which do not have # CertificateError drivers.log.error(('Certificate validation failed when ' 'connecting to %s: %s\n' 'This means either someone is doing a man-in-the-middle ' 'attack on your connection, or the server\'s certificate is ' 'not in your trusted fingerprints list.') % (self.irc.network, e.args[0])) raise ssl.SSLError('Aborting because of failed certificate ' 'verification.') except ssl.SSLError as e: drivers.log.error(('Certificate validation failed when ' 'connecting to %s: %s\n' 'This means either someone is doing a man-in-the-middle ' 'attack on your connection, or the server\'s ' 'certificate is not trusted.') % (self.irc.network, e.args[1])) raise ssl.SSLError('Aborting because of failed certificate ' 'verification.') Driver = SocketDriver # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/drivers/Twisted.py0000644000175000017500000001406413634634532017566 0ustar valval00000000000000### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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 .. import conf, drivers from twisted.names import client from twisted.internet import reactor, error from twisted.protocols.basic import LineReceiver from twisted.internet.protocol import ReconnectingClientFactory # This hack prevents the standard Twisted resolver from starting any # threads, which allows for a clean shut-down in Twisted>=2.0 reactor.installResolver(client.createResolver()) try: from OpenSSL import SSL from twisted.internet import ssl except ImportError: drivers.log.debug('PyOpenSSL is not available, ' 'cannot connect to SSL servers.') SSL = None class TwistedRunnerDriver(drivers.IrcDriver): def name(self): return self.__class__.__name__ def run(self): try: reactor.iterate(conf.supybot.drivers.poll()) except: drivers.log.exception('Uncaught exception outside reactor:') class SupyIrcProtocol(LineReceiver): delimiter = '\n' MAX_LENGTH = 1024 def __init__(self): self.mostRecentCall = reactor.callLater(0.1, self.checkIrcForMsgs) def lineReceived(self, line): msg = drivers.parseMsg(line) if msg is not None: self.irc.feedMsg(msg) def checkIrcForMsgs(self): if self.connected: msg = self.irc.takeMsg() while msg: self.transport.write(str(msg)) msg = self.irc.takeMsg() self.mostRecentCall = reactor.callLater(0.1, self.checkIrcForMsgs) def connectionLost(self, r): self.mostRecentCall.cancel() if r.check(error.ConnectionDone): drivers.log.disconnect(self.factory.currentServer) else: drivers.log.disconnect(self.factory.currentServer, errorMsg(r)) if self.irc.zombie: self.factory.stopTrying() while self.irc.takeMsg(): continue else: self.irc.reset() def connectionMade(self): self.factory.resetDelay() self.irc.driver = self def die(self): drivers.log.die(self.irc) self.factory.stopTrying() self.transport.loseConnection() def reconnect(self, wait=None): # We ignore wait here, because we handled our own waiting. drivers.log.reconnect(self.irc.network) self.transport.loseConnection() def errorMsg(reason): return reason.getErrorMessage() class SupyReconnectingFactory(ReconnectingClientFactory, drivers.ServersMixin): maxDelay = property(lambda self: conf.supybot.drivers.maxReconnectWait()) protocol = SupyIrcProtocol def __init__(self, irc): drivers.log.warning('Twisted driver is deprecated. You should ' 'consider switching to Socket (set ' 'supybot.drivers.module to Socket).') self.irc = irc drivers.ServersMixin.__init__(self, irc) (server, port) = self._getNextServer() vhost = conf.supybot.protocols.irc.vhost() if self.networkGroup.get('ssl').value: self.connectSSL(server, port, vhost) else: self.connectTCP(server, port, vhost) def connectTCP(self, server, port, vhost): """Connect to the server with a standard TCP connection.""" reactor.connectTCP(server, port, self, bindAddress=(vhost, 0)) def connectSSL(self, server, port, vhost): """Connect to the server using an SSL socket.""" drivers.log.info('Attempting an SSL connection.') if SSL: reactor.connectSSL(server, port, self, ssl.ClientContextFactory(), bindAddress=(vhost, 0)) else: drivers.log.error('PyOpenSSL is not available. Not connecting.') def clientConnectionFailed(self, connector, r): drivers.log.connectError(self.currentServer, errorMsg(r)) (connector.host, connector.port) = self._getNextServer() ReconnectingClientFactory.clientConnectionFailed(self, connector,r) def clientConnectionLost(self, connector, r): (connector.host, connector.port) = self._getNextServer() ReconnectingClientFactory.clientConnectionLost(self, connector, r) def startedConnecting(self, connector): drivers.log.connect(self.currentServer) def buildProtocol(self, addr): protocol = ReconnectingClientFactory.buildProtocol(self, addr) protocol.irc = self.irc return protocol Driver = SupyReconnectingFactory poller = TwistedRunnerDriver() # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/drivers/__init__.py0000644000175000017500000001604013634634532017676 0ustar valval00000000000000### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2008-2009, James McCoy # 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. ### """ Contains various drivers (network, file, and otherwise) for using IRC objects. """ import socket from .. import conf, ircmsgs, log as supylog, utils from ..utils import minisix _drivers = {} _deadDrivers = set() _newDrivers = [] class IrcDriver(object): """Base class for drivers.""" def __init__(self, *args, **kwargs): add(self.name(), self) super(IrcDriver, self).__init__(*args, **kwargs) def run(self): raise NotImplementedError def die(self): # The end of any overrided die method should be # "super(Class, self).die()", in order to make # sure this (and anything else later added) is done. remove(self.name()) def reconnect(self, wait=False): raise NotImplementedError def name(self): return repr(self) class ServersMixin(object): def __init__(self, irc, servers=()): self.networkGroup = conf.supybot.networks.get(irc.network) self.servers = servers super(ServersMixin, self).__init__() def _getServers(self): # We do this, rather than utils.iter.cycle the servers in __init__, # because otherwise registry updates given as setValues or sets # wouldn't be visible until a restart. return self.networkGroup.servers()[:] # Be sure to copy! def _getNextServer(self): if not self.servers: self.servers = self._getServers() assert self.servers, 'Servers value for %s is empty.' % \ self.networkGroup._name server = self.servers.pop(0) self.currentServer = '%s:%s' % server return server def empty(): """Returns whether or not the driver loop is empty.""" return (len(_drivers) + len(_newDrivers)) == 0 def add(name, driver): """Adds a given driver the loop with the given name.""" _newDrivers.append((name, driver)) def remove(name): """Removes the driver with the given name from the loop.""" _deadDrivers.add(name) def run(): """Runs the whole driver loop.""" for (name, driver) in _drivers.items(): try: if name not in _deadDrivers: driver.run() except: log.exception('Uncaught exception in in drivers.run:') _deadDrivers.add(name) for name in _deadDrivers: try: driver = _drivers[name] if hasattr(driver, 'irc') and driver.irc is not None: # The Schedule driver has no irc object, or it's None. driver.irc.driver = None driver.irc = None log.info('Removing driver %s.', name) del _drivers[name] except KeyError: pass while _newDrivers: (name, driver) = _newDrivers.pop() log.debug('Adding new driver %s.', name) _deadDrivers.discard(name) if name in _drivers: log.warning('Driver %s already added, killing it.', name) _drivers[name].die() del _drivers[name] _drivers[name] = driver class Log(object): """This is used to have a nice, consistent interface for drivers to use.""" def connect(self, server): self.info('Connecting to %s.', server) def connectError(self, server, e): if isinstance(e, Exception): if isinstance(e, socket.gaierror): e = e.args[1] else: e = utils.exnToString(e) self.warning('Error connecting to %s: %s', server, e) def disconnect(self, server, e=None): if e: if isinstance(e, Exception): e = utils.exnToString(e) else: e = str(e) if not e.endswith('.'): e += '.' self.warning('Disconnect from %s: %s', server, e) else: self.info('Disconnect from %s.', server) def reconnect(self, network, when=None): s = 'Reconnecting to %s' % network if when is not None: if not isinstance(when, minisix.string_types): when = self.timestamp(when) s += ' at %s.' % when else: s += '.' self.info(s) def die(self, irc): self.info('Driver for %s dying.', irc) debug = staticmethod(supylog.debug) info = staticmethod(supylog.info) warning = staticmethod(supylog.warning) error = staticmethod(supylog.warning) critical = staticmethod(supylog.critical) timestamp = staticmethod(supylog.timestamp) exception = staticmethod(supylog.exception) log = Log() def newDriver(irc, moduleName=None): """Returns a new driver for the given server using the irc given and using conf.supybot.driverModule to determine what driver to pick.""" # XXX Eventually this should be made to load the drivers from a # configurable directory in addition to the installed one. if moduleName is None: moduleName = conf.supybot.drivers.module() if moduleName == 'default': moduleName = 'supybot.drivers.Socket' elif not moduleName.startswith('supybot.drivers.'): moduleName = 'supybot.drivers.' + moduleName driverModule = __import__(moduleName, {}, {}, ['not empty']) log.debug('Creating new driver (%s) for %s.', moduleName, irc) driver = driverModule.Driver(irc) irc.driver = driver return driver def parseMsg(s): s = s.strip() if s: msg = ircmsgs.IrcMsg(s) return msg else: return None # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/dynamicScope.py0000644000175000017500000000440213634634532017076 0ustar valval00000000000000### # Copyright (c) 2004-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import sys class DynamicScope(object): def _getLocals(self, name): f = sys._getframe().f_back.f_back # _getLocals <- __[gs]etattr__ <- ... while f: if name in f.f_locals: return f.f_locals f = f.f_back raise NameError(name) def __getattr__(self, name): try: return self._getLocals(name)[name] except (NameError, KeyError): return None def __setattr__(self, name, value): self._getLocals(name)[name] = value (__builtins__ if isinstance(__builtins__, dict) else __builtins__.__dict__)['dynamic'] = DynamicScope() # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/gpg.py0000644000175000017500000000566013634634532015244 0ustar valval00000000000000### # Copyright (c) 2012, Valentin Lorentz # 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 os import supybot.log as log import supybot.conf as conf found_gnupg_lib = False found_gnupg_bin = False try: import gnupg except ImportError: # As we do not want Supybot to depend on GnuPG, we will use it only if # it is available. Otherwise, we just don't allow user auth through GPG. log.debug('Cannot import gnupg, disabling GPG support.') gnupg = None try: if gnupg: gnupg.GPG(gnupghome=None) found_gnupg_lib = found_gnupg_bin = True except TypeError: # This is the 'gnupg' library, not 'python-gnupg'. gnupg = None log.error('Cannot use GPG. gnupg (a Python package) is installed, ' 'but python-gnupg (an other Python package) should be ' 'installed instead.') except OSError: gnupg = None found_gnupg_lib = True log.error('Cannot use GPG. python-gnupg is installed but cannot ' 'find the gnupg executable.') available = (gnupg is not None) def loadKeyring(): if not available: return global keyring path = os.path.abspath(conf.supybot.directories.data.dirize('GPGkeyring')) if not os.path.isdir(path): log.info('Creating directory %s' % path) os.mkdir(path, 0o700) assert os.path.isdir(path) keyring = gnupg.GPG(gnupghome=path) loadKeyring() # Reload the keyring if path changed conf.supybot.directories.data.addCallback(loadKeyring) limnoria-2020.03.17/src/httpserver.py0000644000175000017500000003676513634634532016707 0ustar valval00000000000000### # Copyright (c) 2011, Valentin Lorentz # 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. ### """ An embedded and centralized HTTP server for Supybot's plugins. """ import os import cgi import socket from threading import Thread import supybot.log as log import supybot.conf as conf import supybot.world as world import supybot.utils.minisix as minisix from supybot.i18n import PluginInternationalization _ = PluginInternationalization() if minisix.PY2: from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler else: from http.server import HTTPServer, BaseHTTPRequestHandler configGroup = conf.supybot.servers.http class RequestNotHandled(Exception): pass DEFAULT_TEMPLATES = { 'index.html': """\ """ + _('Supybot Web server index') + """

Supybot web server index

""" + _('Here is a list of the plugins that have a Web interface:') +\ """

%(list)s """, 'generic/error.html': """\ %(title)s

Error

%(error)s

""", 'default.css': """\ body { background-color: #F0F0F0; } /************************************ * Classes that plugins should use. * ************************************/ /* Error pages */ body.error { text-align: center; } body.error p { background-color: #FFE0E0; border: 1px #FFA0A0 solid; } /* Pages that only contain a list. */ .purelisting { text-align: center; } .purelisting ul { margin: 0; padding: 0; } .purelisting ul li { margin: 0; padding: 0; list-style-type: none; } /* Pages that only contain a table. */ .puretable { text-align: center; } .puretable table { width: 100%; border-collapse: collapse; text-align: center; } .puretable table th { /*color: #039;*/ padding: 10px 8px; border-bottom: 2px solid #6678b1; } .puretable table td { padding: 9px 8px 0px 8px; border-bottom: 1px solid #ccc; } """, 'robots.txt': """""", } def set_default_templates(defaults): for filename, content in defaults.items(): path = conf.supybot.directories.data.web.dirize(filename) if os.path.isfile(path + '.example'): os.unlink(path + '.example') if not os.path.isdir(os.path.dirname(path)): os.makedirs(os.path.dirname(path)) with open(path + '.example', 'a') as fd: fd.write(content) set_default_templates(DEFAULT_TEMPLATES) def get_template(filename): path = conf.supybot.directories.data.web.dirize(filename) if os.path.isfile(path): with open(path, 'r') as fd: return fd.read() else: assert os.path.isfile(path + '.example'), path + '.example' with open(path + '.example', 'r') as fd: return fd.read() class RealSupyHTTPServer(HTTPServer): # TODO: make this configurable timeout = 0.5 running = False def __init__(self, address, protocol, callback): self.protocol = protocol if protocol == 4: self.address_family = socket.AF_INET elif protocol == 6: self.address_family = socket.AF_INET6 else: raise AssertionError(protocol) HTTPServer.__init__(self, address, callback) self.callbacks = {} def server_bind(self): if self.protocol == 6: v = conf.supybot.servers.http.singleStack() self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, v) HTTPServer.server_bind(self) def hook(self, subdir, callback): if subdir in self.callbacks: log.warning(('The HTTP subdirectory `%s` was already hooked but ' 'has been claimed by another plugin (or maybe you ' 'reloaded the plugin and it didn\'t properly unhook. ' 'Forced unhook.') % subdir) self.callbacks[subdir] = callback callback.doHook(self, subdir) def unhook(self, subdir): callback = self.callbacks.pop(subdir, None) if callback: callback.doUnhook(self) return callback def __str__(self): return 'server at %s %i' % self.server_address[0:2] class TestSupyHTTPServer(RealSupyHTTPServer): def __init__(self, *args, **kwargs): self.callbacks = {} def serve_forever(self, *args, **kwargs): pass def shutdown(self, *args, **kwargs): pass if world.testing: SupyHTTPServer = TestSupyHTTPServer else: SupyHTTPServer = RealSupyHTTPServer class SupyHTTPRequestHandler(BaseHTTPRequestHandler): def do_X(self, callbackMethod, *args, **kwargs): if self.path == '/': callback = SupyIndex() elif self.path in ('/robots.txt',): callback = Static('text/plain; charset=utf-8') elif self.path in ('/default.css',): callback = Static('text/css') elif self.path == '/favicon.ico': callback = Favicon() else: subdir = self.path.split('/')[1] try: callback = self.server.callbacks[subdir] except KeyError: callback = Supy404() # Some shortcuts for name in ('send_response', 'send_header', 'end_headers', 'rfile', 'wfile', 'headers'): setattr(callback, name, getattr(self, name)) # We call doX, because this is more supybotic than do_X. path = self.path if not callback.fullpath: path = '/' + path.split('/', 2)[-1] getattr(callback, callbackMethod)(self, path, *args, **kwargs) def do_GET(self): self.do_X('doGet') def do_POST(self): if 'Content-Type' not in self.headers: self.headers['Content-Type'] = 'application/x-www-form-urlencoded' if self.headers['Content-Type'] == 'application/x-www-form-urlencoded': form = cgi.FieldStorage( fp=self.rfile, headers=self.headers, environ={'REQUEST_METHOD':'POST', 'CONTENT_TYPE':self.headers['Content-Type'], }) else: content_length = int(self.headers.get('Content-Length', '0')) form = self.rfile.read(content_length) self.do_X('doPost', form=form) def do_HEAD(self): self.do_X('doHead') def address_string(self): s = BaseHTTPRequestHandler.address_string(self) # Strip IPv4-mapped IPv6 addresses such as ::ffff:127.0.0.1 prefix = '::ffff:' if s.startswith(prefix): s = s[len(prefix):] return s def log_message(self, format, *args): log.info('HTTP request: %s - %s' % (self.address_string(), format % args)) class SupyHTTPServerCallback(log.Firewalled): """This is a base class that should be overriden by any plugin that want to have a Web interface.""" __firewalled__ = {'doGet': None, 'doPost': None, 'doHead': None, 'doPut': None, 'doDelete': None, } fullpath = False name = "Unnamed plugin" defaultResponse = _(""" This is a default response of the Supybot HTTP server. If you see this message, it probably means you are developing a plugin, and you have neither overriden this message or defined an handler for this query.""") if minisix.PY3: def write(self, b): if isinstance(b, str): b = b.encode() self.wfile.write(b) else: def write(self, s): self.wfile.write(s) def doGetOrHead(self, handler, path, write_content): response = self.defaultResponse.encode() handler.send_response(405) self.send_header('Content-Type', 'text/plain; charset=utf-8; charset=utf-8') self.send_header('Content-Length', len(response)) self.end_headers() if write_content: self.wfile.write(response) def doGet(self, handler, path): self.doGetOrHead(handler, path, write_content=True) def doHead(self, handler, path): self.doGetOrHead(handler, path, write_content=False) doPost = doGet def doHook(self, handler, subdir): """Method called when hooking this callback.""" pass def doUnhook(self, handler): """Method called when unhooking this callback.""" pass class Supy404(SupyHTTPServerCallback): """A 404 Not Found error.""" name = "Error 404" fullpath = True response = _(""" I am a pretty clever IRC bot, but I suck at serving Web pages, particulary if I don't know what to serve. What I'm saying is you just triggered a 404 Not Found, and I am not trained to help you in such a case.""") def doGetOrHead(self, handler, path, write_content): response = self.response if minisix.PY3: response = response.encode() handler.send_response(404) self.send_header('Content-Type', 'text/plain; charset=utf-8; charset=utf-8') self.send_header('Content-Length', len(self.response)) self.end_headers() if write_content: self.wfile.write(response) class SupyIndex(SupyHTTPServerCallback): """Displays the index of available plugins.""" name = "index" fullpath = True defaultResponse = _("Request not handled.") def doGetOrHead(self, handler, path, write_content): plugins = [x for x in handler.server.callbacks.items()] if plugins == []: plugins = _('No plugins available.') else: plugins = '
  • %s
' % '
  • '.join( ['%s' % (x,y.name) for x,y in plugins]) response = get_template('index.html') % {'list': plugins} if minisix.PY3: response = response.encode() handler.send_response(200) self.send_header('Content-Type', 'text/html; charset=utf-8') self.send_header('Content-Length', len(response)) self.end_headers() if write_content: self.wfile.write(response) class Static(SupyHTTPServerCallback): """Serves static files.""" fullpath = True name = 'static' defaultResponse = _('Request not handled') def __init__(self, mimetype='text/plain; charset=utf-8'): super(Static, self).__init__() self._mimetype = mimetype def doGetOrHead(self, handler, path, write_content): response = get_template(path) if minisix.PY3: response = response.encode() handler.send_response(200) self.send_header('Content-type', self._mimetype) self.send_header('Content-Length', len(response)) self.end_headers() if write_content: self.wfile.write(response) class Favicon(SupyHTTPServerCallback): """Services the favicon.ico file to browsers.""" name = 'favicon' defaultResponse = _('Request not handled') def doGetOrHead(self, handler, path, write_content): response = None file_path = conf.supybot.servers.http.favicon() if file_path: try: icon = open(file_path, 'rb') response = icon.read() except IOError: pass finally: icon.close() if response is not None: # I have no idea why, but this headers are already sent. # filename = file_path.rsplit(os.sep, 1)[1] # if '.' in filename: # ext = filename.rsplit('.', 1)[1] # else: # ext = 'ico' # self.send_header('Content-Length', len(response)) # self.send_header('Content-type', 'image/' + ext) # self.end_headers() if write_content: self.wfile.write(response) else: response = _('No favicon set.') if minisix.PY3: response = response.encode() handler.send_response(404) self.send_header('Content-type', 'text/plain; charset=utf-8') self.send_header('Content-Length', len(response)) self.end_headers() if write_content: self.wfile.write(response) http_servers = [] def startServer(): """Starts the HTTP server. Shouldn't be called from other modules. The callback should be an instance of a child of SupyHTTPServerCallback.""" global http_servers addresses4 = [(4, (x, configGroup.port())) for x in configGroup.hosts4() if x != ''] addresses6 = [(6, (x, configGroup.port())) for x in configGroup.hosts6() if x != ''] http_servers = [] for protocol, address in (addresses4 + addresses6): server = SupyHTTPServer(address, protocol, SupyHTTPRequestHandler) Thread(target=server.serve_forever, name='HTTP Server').start() http_servers.append(server) log.info('Starting HTTP server: %s' % str(server)) def stopServer(): """Stops the HTTP server. Should be run only from this module or from when the bot is dying (ie. from supybot.world)""" global http_servers for server in http_servers: log.info('Stopping HTTP server: %s' % str(server)) server.shutdown() server = None if configGroup.keepAlive(): startServer() def hook(subdir, callback): """Sets a callback for a given subdir.""" if not http_servers: startServer() assert isinstance(http_servers, list) for server in http_servers: server.hook(subdir, callback) def unhook(subdir): """Unsets the callback assigned to the given subdir, and return it.""" global http_servers assert isinstance(http_servers, list) for server in list(http_servers): server.unhook(subdir) if len(server.callbacks) <= 0 and not configGroup.keepAlive(): server.shutdown() http_servers.remove(server) limnoria-2020.03.17/src/i18n.py0000644000175000017500000003305013634634532015240 0ustar valval00000000000000### # Copyright (c) 2010, Valentin Lorentz # 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. ### """ Supybot internationalisation and localisation managment. """ __all__ = ['PluginInternationalization', 'internationalizeDocstring'] import os import sys import weakref conf = None # Don't import conf here ; because conf needs this module WAITING_FOR_MSGID = 1 IN_MSGID = 2 WAITING_FOR_MSGSTR = 3 IN_MSGSTR = 4 MSGID = 'msgid "' MSGSTR = 'msgstr "' currentLocale = 'en' class PluginNotFound(Exception): pass def getLocaleFromRegistryCache(): """Called by the 'supybot' script. Gets the locale name before conf is loaded.""" global currentLocale import supybot.registry as registry try: currentLocale = registry._cache['supybot.language'] except KeyError: pass else: reloadLocales() def import_conf(): """Imports the conf into this module""" global conf conf = __import__('supybot.conf').conf conf.registerGlobalValue(conf.supybot, 'language', conf.registry.String(currentLocale, """Determines the bot's default language if translations exist. Currently supported are 'de', 'en', 'es', 'fi', 'fr' and 'it'.""")) conf.supybot.language.addCallback(reloadLocalesIfRequired) def getPluginDir(plugin_name): """Gets the directory of the given plugin""" filename = None try: filename = sys.modules[plugin_name].__file__ except KeyError: # It sometimes happens with Owner pass if filename == None: 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] allowed_files = ['__init__.py', 'config.py', 'plugin.py', 'test.py'] for allowed_file in allowed_files: if filename.endswith(allowed_file): return filename[0:-len(allowed_file)] raise PluginNotFound() def getLocalePath(name, localeName, extension): """Gets the path of the locale file of the given plugin ('supybot' stands for the core).""" if name != 'supybot': base = getPluginDir(name) else: from . import ansi # Any Supybot plugin could fit base = ansi.__file__[0:-len('ansi.pyc')] directory = os.path.join(base, 'locales') return '%s/%s.%s' % (directory, localeName, extension) i18nClasses = weakref.WeakValueDictionary() internationalizedCommands = weakref.WeakValueDictionary() internationalizedFunctions = [] # No need to know their name def reloadLocalesIfRequired(): global currentLocale if conf is None: return if currentLocale != conf.supybot.language(): currentLocale = conf.supybot.language() reloadLocales() def reloadLocales(): for pluginClass in i18nClasses.values(): pluginClass.loadLocale() for command in list(internationalizedCommands.values()): internationalizeDocstring(command) for function in internationalizedFunctions: function.loadLocale() def normalize(string, removeNewline=False): import supybot.utils as utils string = string.replace('\\n\\n', '\n\n') string = string.replace('\\n', ' ') string = string.replace('\\"', '"') string = string.replace("\'", "'") string = utils.str.normalizeWhitespace(string, removeNewline) string = string.strip('\n') string = string.strip('\t') return string def parse(translationFile): step = WAITING_FOR_MSGID translations = set() for line in translationFile: line = line[0:-1] # Remove the ending \n line = line if line.startswith(MSGID): # Don't check if step is WAITING_FOR_MSGID untranslated = '' translated = '' data = line[len(MSGID):-1] if len(data) == 0: # Multiline mode step = IN_MSGID else: untranslated += data step = WAITING_FOR_MSGSTR elif step is IN_MSGID and line.startswith('"') and \ line.endswith('"'): untranslated += line[1:-1] elif step is IN_MSGID and untranslated == '': # Empty MSGID step = WAITING_FOR_MSGID elif step is IN_MSGID: # the MSGID is finished step = WAITING_FOR_MSGSTR if step is WAITING_FOR_MSGSTR and line.startswith(MSGSTR): data = line[len(MSGSTR):-1] if len(data) == 0: # Multiline mode step = IN_MSGSTR else: translations |= set([(untranslated, data)]) step = WAITING_FOR_MSGID elif step is IN_MSGSTR and line.startswith('"') and \ line.endswith('"'): translated += line[1:-1] elif step is IN_MSGSTR: # the MSGSTR is finished step = WAITING_FOR_MSGID if translated == '': translated = untranslated translations |= set([(untranslated, translated)]) if step is IN_MSGSTR: if translated == '': translated = untranslated translations |= set([(untranslated, translated)]) return translations i18nSupybot = None def PluginInternationalization(name='supybot'): # This is a proxy that prevents having several objects for the same plugin if name in i18nClasses: return i18nClasses[name] else: return _PluginInternationalization(name) class _PluginInternationalization: """Internationalization managment for a plugin.""" def __init__(self, name='supybot'): self.name = name self.translations = {} self.currentLocaleName = None i18nClasses.update({name: self}) self.loadLocale() def loadLocale(self, localeName=None): """(Re)loads the locale used by this class.""" self.translations = {} if localeName is None: localeName = currentLocale self.currentLocaleName = localeName self._loadL10nCode() try: try: translationFile = open(getLocalePath(self.name, localeName, 'po'), 'ru') except ValueError: # We are using Windows translationFile = open(getLocalePath(self.name, localeName, 'po'), 'r') self._parse(translationFile) except (IOError, PluginNotFound): # The translation is unavailable pass finally: if 'translationFile' in locals(): translationFile.close() def _parse(self, translationFile): """A .po files parser. Give it a file object.""" self.translations = {} for translation in parse(translationFile): self._addToDatabase(*translation) def _addToDatabase(self, untranslated, translated): untranslated = normalize(untranslated, True) translated = normalize(translated) if translated: self.translations.update({untranslated: translated}) def __call__(self, untranslated): """Main function. This is the function which is called when a plugin runs _()""" normalizedUntranslated = normalize(untranslated, True) try: string = self._translate(normalizedUntranslated) return self._addTracker(string, untranslated) except KeyError: pass if untranslated.__class__ is InternationalizedString: return untranslated._original else: return untranslated def _translate(self, string): """Translate the string. C the string internationalizer if any; else, use the local database""" if string.__class__ == InternationalizedString: return string._internationalizer(string.untranslated) else: return self.translations[string] def _addTracker(self, string, untranslated): """Add a kind of 'tracker' on the string, in order to keep the untranslated string (used when changing the locale)""" if string.__class__ == InternationalizedString: return string else: string = InternationalizedString(string) string._original = untranslated string._internationalizer = self return string def _loadL10nCode(self): """Open the file containing the code specific to this locale, and load its functions.""" if self.name != 'supybot': return path = self._getL10nCodePath() try: with open(path) as fd: exec(compile(fd.read(), path, 'exec')) except IOError: # File doesn't exist pass functions = locals() functions.pop('self') self._l10nFunctions = functions # Remove old functions and come back to the native language def _getL10nCodePath(self): """Returns the path to the code localization file. It contains functions that needs to by fully (code + strings) localized""" if self.name != 'supybot': return return getLocalePath('supybot', self.currentLocaleName, 'py') def localizeFunction(self, name): """Returns the localized version of the function. Should be used only by the InternationalizedFunction class""" if self.name != 'supybot': return if hasattr(self, '_l10nFunctions') and \ name in self._l10nFunctions: return self._l10nFunctions[name] def internationalizeFunction(self, name): """Decorates functions and internationalize their code. Only useful for Supybot core functions""" if self.name != 'supybot': return class FunctionInternationalizer: def __init__(self, parent, name): self._parent = parent self._name = name def __call__(self, obj): obj = InternationalizedFunction(self._parent, self._name, obj) obj.loadLocale() return obj return FunctionInternationalizer(self, name) class InternationalizedFunction: """Proxy for functions that need to be fully localized. The localization code is in locales/LOCALE.py""" def __init__(self, internationalizer, name, function): self._internationalizer = internationalizer self._name = name self._origin = function internationalizedFunctions.append(self) def loadLocale(self): self.__call__ = self._internationalizer.localizeFunction(self._name) if self.__call__ == None: self.restore() def restore(self): self.__call__ = self._origin def __call__(self, *args, **kwargs): return self._origin(*args, **kwargs) try: class InternationalizedString(str): """Simple subclass to str, that allow to add attributes. Also used to know if a string is already localized""" __slots__ = ('_original', '_internationalizer') except TypeError: # Fallback for CPython 2.x: # TypeError: Error when calling the metaclass bases # nonempty __slots__ not supported for subtype of 'str' class InternationalizedString(str): """Simple subclass to str, that allow to add attributes. Also used to know if a string is already localized""" pass def internationalizeDocstring(obj): """Decorates functions and internationalize their docstring. Only useful for commands (commands' docstring is displayed on IRC)""" if obj.__doc__ == None: return obj plugin_module = sys.modules[obj.__module__] if '_' in plugin_module.__dict__: internationalizedCommands.update({hash(obj): obj}) try: obj.__doc__ = plugin_module._.__call__(obj.__doc__) # We use _.__call__() instead of _() because of a pygettext warning. except AttributeError: # attribute '__doc__' of 'type' objects is not writable pass return obj limnoria-2020.03.17/src/ircdb.py0000644000175000017500000013274113634634532015553 0ustar valval00000000000000### # Copyright (c) 2002-2009, Jeremiah Fincher # Copyright (c) 2011, Valentin Lorentz # Copyright (c) 2009,2013, James McCoy # 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 os import time import operator from . import conf, ircutils, log, registry, unpreserve, utils, world from .utils import minisix def isCapability(capability): return len(capability.split(None, 1)) == 1 def fromChannelCapability(capability): """Returns a (channel, capability) tuple from a channel capability.""" assert isChannelCapability(capability), 'got %s' % capability return capability.split(',', 1) def isChannelCapability(capability): """Returns True if capability is a channel capability; False otherwise.""" if ',' in capability: (channel, capability) = capability.split(',', 1) return ircutils.isChannel(channel) and isCapability(capability) else: return False def makeChannelCapability(channel, capability): """Makes a channel capability given a channel and a capability.""" assert isCapability(capability), 'got %s' % capability assert ircutils.isChannel(channel), 'got %s' % channel return '%s,%s' % (channel, capability) def isAntiCapability(capability): """Returns True if capability is an anticapability; False otherwise.""" if isChannelCapability(capability): (_, capability) = fromChannelCapability(capability) return isCapability(capability) and capability[0] == '-' def makeAntiCapability(capability): """Returns the anticapability of a given capability.""" assert isCapability(capability), 'got %s' % capability assert not isAntiCapability(capability), \ 'makeAntiCapability does not work on anticapabilities. ' \ 'You probably want invertCapability; got %s.' % capability if isChannelCapability(capability): (channel, capability) = fromChannelCapability(capability) return makeChannelCapability(channel, '-' + capability) else: return '-' + capability def unAntiCapability(capability): """Takes an anticapability and returns the non-anti form.""" assert isCapability(capability), 'got %s' % capability if not isAntiCapability(capability): raise ValueError('%s is not an anti capability' % capability) if isChannelCapability(capability): (channel, capability) = fromChannelCapability(capability) return ','.join((channel, capability[1:])) else: return capability[1:] def invertCapability(capability): """Make a capability into an anticapability and vice versa.""" assert isCapability(capability), 'got %s' % capability if isAntiCapability(capability): return unAntiCapability(capability) else: return makeAntiCapability(capability) def canonicalCapability(capability): if callable(capability): capability = capability() assert isCapability(capability), 'got %s' % capability return capability.lower() _unwildcard_remover = utils.str.MultipleRemover('!@*?') def unWildcardHostmask(hostmask): return _unwildcard_remover(hostmask) _invert = invertCapability class CapabilitySet(set): """A subclass of set handling basic capability stuff.""" __slots__ = ('__parent',) def __init__(self, capabilities=()): self.__parent = super(CapabilitySet, self) self.__parent.__init__() for capability in capabilities: self.add(capability) def add(self, capability): """Adds a capability to the set.""" capability = ircutils.toLower(capability) inverted = _invert(capability) if self.__parent.__contains__(inverted): self.__parent.remove(inverted) self.__parent.add(capability) def remove(self, capability): """Removes a capability from the set.""" capability = ircutils.toLower(capability) self.__parent.remove(capability) def __contains__(self, capability): capability = ircutils.toLower(capability) if self.__parent.__contains__(capability): return True if self.__parent.__contains__(_invert(capability)): return True else: return False def check(self, capability, ignoreOwner=False): """Returns the appropriate boolean for whether a given capability is 'allowed' given its (or its anticapability's) presence in the set. """ capability = ircutils.toLower(capability) if self.__parent.__contains__(capability): return True elif self.__parent.__contains__(_invert(capability)): return False else: raise KeyError def __repr__(self): return '%s([%s])' % (self.__class__.__name__, ', '.join(map(repr, self))) antiOwner = makeAntiCapability('owner') class UserCapabilitySet(CapabilitySet): """A subclass of CapabilitySet to handle the owner capability correctly.""" __slots__ = ('__parent',) def __init__(self, *args, **kwargs): self.__parent = super(UserCapabilitySet, self) self.__parent.__init__(*args, **kwargs) def __contains__(self, capability, ignoreOwner=False): capability = ircutils.toLower(capability) if not ignoreOwner and capability == 'owner' or capability == antiOwner: return True elif not ignoreOwner and self.__parent.__contains__('owner'): return True else: return self.__parent.__contains__(capability) def check(self, capability, ignoreOwner=False): """Returns the appropriate boolean for whether a given capability is 'allowed' given its (or its anticapability's) presence in the set. Differs from CapabilitySet in that it handles the 'owner' capability appropriately. """ capability = ircutils.toLower(capability) if capability == 'owner' or capability == antiOwner: if self.__parent.__contains__('owner'): return not isAntiCapability(capability) else: return isAntiCapability(capability) elif not ignoreOwner and self.__parent.__contains__('owner'): if isAntiCapability(capability): return False else: return True else: return self.__parent.check(capability) def add(self, capability): """Adds a capability to the set. Just make sure it's not -owner.""" capability = ircutils.toLower(capability) assert capability != '-owner', '"-owner" disallowed.' self.__parent.add(capability) class IrcUser(object): """This class holds the capabilities and authentications for a user.""" __slots__ = ('id', 'auth', 'name', 'ignore', 'secure', 'hashed', 'password', 'capabilities', 'hostmasks', 'nicks', 'gpgkeys') def __init__(self, ignore=False, password='', name='', capabilities=(), hostmasks=None, nicks=None, secure=False, hashed=False): self.id = None self.auth = [] # The (time, hostmask) list of auth crap. self.name = name # The name of the user. self.ignore = ignore # A boolean deciding if the person is ignored. self.secure = secure # A boolean describing if hostmasks *must* match. self.hashed = hashed # True if the password is hashed on disk. self.password = password # password (plaintext? hashed?) self.capabilities = UserCapabilitySet() for capability in capabilities: self.capabilities.add(capability) if hostmasks is None: self.hostmasks = ircutils.IrcSet() # hostmasks used for recognition else: self.hostmasks = hostmasks if nicks is None: self.nicks = {} # {'network1': ['foo', 'bar'], 'network': ['baz']} else: self.nicks = nicks self.gpgkeys = [] # GPG key ids def __repr__(self): return format('%s(id=%s, ignore=%s, password="", name=%q, hashed=%r, ' 'capabilities=%r, hostmasks=[], secure=%r)\n', self.__class__.__name__, self.id, self.ignore, self.name, self.hashed, self.capabilities, self.secure) def __hash__(self): return hash(self.id) def addCapability(self, capability): """Gives the user the given capability.""" self.capabilities.add(capability) def removeCapability(self, capability): """Takes from the user the given capability.""" self.capabilities.remove(capability) def _checkCapability(self, capability, ignoreOwner=False): """Checks the user for a given capability.""" if self.ignore: if isAntiCapability(capability): return True else: return False else: return self.capabilities.check(capability, ignoreOwner=ignoreOwner) def setPassword(self, password, hashed=False): """Sets the user's password.""" if hashed or self.hashed: self.hashed = True self.password = utils.saltHash(password) else: self.password = password def checkPassword(self, password): """Checks the user's password.""" if password is None: return False if self.hashed: (salt, _) = self.password.split('|') return (self.password == utils.saltHash(password, salt=salt)) else: return (self.password == password) def checkHostmask(self, hostmask, useAuth=True): """Checks a given hostmask against the user's hostmasks or current authentication. If useAuth is False, only checks against the user's hostmasks. """ if useAuth: timeout = conf.supybot.databases.users.timeoutIdentification() removals = [] try: for (when, authmask) in self.auth: if timeout and when+timeout < time.time(): removals.append((when, authmask)) elif hostmask == authmask: return True finally: while removals: self.auth.remove(removals.pop()) for pat in self.hostmasks: if ircutils.hostmaskPatternEqual(pat, hostmask): return pat return False def addHostmask(self, hostmask): """Adds a hostmask to the user's hostmasks.""" assert ircutils.isUserHostmask(hostmask), 'got %s' % hostmask if len(unWildcardHostmask(hostmask)) < 3: raise ValueError('Hostmask must contain at least 3 non-wildcard characters.') self.hostmasks.add(hostmask) def removeHostmask(self, hostmask): """Removes a hostmask from the user's hostmasks.""" self.hostmasks.remove(hostmask) def checkNick(self, network, nick): """Checks a given nick against the user's nicks.""" return nick in self.nicks[network] def addNick(self, network, nick): """Adds a nick to the user's registered nicks on the network.""" global users assert isinstance(network, minisix.string_types) assert ircutils.isNick(nick), 'got %s' % nick if users.getUserFromNick(network, nick) is not None: raise KeyError if network not in self.nicks: self.nicks[network] = [] if nick not in self.nicks[network]: self.nicks[network].append(nick) def removeNick(self, network, nick): """Removes a nick from the user's registered nicks on the network.""" assert isinstance(network, minisix.string_types) if nick not in self.nicks[network]: raise KeyError self.nicks[network].remove(nick) def addAuth(self, hostmask): """Sets a user's authenticated hostmask. This times out according to conf.supybot.timeoutIdentification. If hostmask exactly matches an existing, known hostmask, the previous entry is removed.""" if self.checkHostmask(hostmask, useAuth=False) or not self.secure: self.auth.append((time.time(), hostmask)) knownHostmasks = set() def uniqueHostmask(auth): (_, mask) = auth if mask not in knownHostmasks: knownHostmasks.add(mask) return True return False uniqued = list(filter(uniqueHostmask, reversed(self.auth))) self.auth = list(reversed(uniqued)) else: raise ValueError('secure flag set, unmatched hostmask') def clearAuth(self): """Unsets a user's authenticated hostmask.""" for (when, hostmask) in self.auth: users.invalidateCache(hostmask=hostmask) self.auth = [] def preserve(self, fd, indent=''): def write(s): fd.write(indent) fd.write(s) fd.write(os.linesep) write('name %s' % self.name) write('ignore %s' % self.ignore) write('secure %s' % self.secure) if self.password: write('hashed %s' % self.hashed) write('password %s' % self.password) for capability in self.capabilities: write('capability %s' % capability) for hostmask in self.hostmasks: write('hostmask %s' % hostmask) for network, nicks in self.nicks.items(): write('nicks %s %s' % (network, ' '.join(nicks))) for key in self.gpgkeys: write('gpgkey %s' % key) fd.write(os.linesep) class IrcChannel(object): """This class holds the capabilities, bans, and ignores of a channel.""" __slots__ = ('defaultAllow', 'expiredBans', 'bans', 'ignores', 'silences', 'exceptions', 'capabilities', 'lobotomized') defaultOff = ('op', 'halfop', 'voice', 'protected') def __init__(self, bans=None, silences=None, exceptions=None, ignores=None, capabilities=None, lobotomized=False, defaultAllow=True): self.defaultAllow = defaultAllow self.expiredBans = [] self.bans = bans or {} self.ignores = ignores or {} self.silences = silences or [] self.exceptions = exceptions or [] self.capabilities = capabilities or CapabilitySet() for capability in self.defaultOff: if capability not in self.capabilities: self.capabilities.add(makeAntiCapability(capability)) self.lobotomized = lobotomized def __repr__(self): return '%s(bans=%r, ignores=%r, capabilities=%r, ' \ 'lobotomized=%r, defaultAllow=%s, ' \ 'silences=%r, exceptions=%r)\n' % \ (self.__class__.__name__, self.bans, self.ignores, self.capabilities, self.lobotomized, self.defaultAllow, self.silences, self.exceptions) def addBan(self, hostmask, expiration=0): """Adds a ban to the channel banlist.""" assert not conf.supybot.protocols.irc.strictRfc() or \ ircutils.isUserHostmask(hostmask), 'got %s' % hostmask self.bans[hostmask] = int(expiration) def removeBan(self, hostmask): """Removes a ban from the channel banlist.""" assert not conf.supybot.protocols.irc.strictRfc() or \ ircutils.isUserHostmask(hostmask), 'got %s' % hostmask return self.bans.pop(hostmask) def checkBan(self, hostmask): """Checks whether a given hostmask is banned by the channel banlist.""" assert ircutils.isUserHostmask(hostmask), 'got %s' % hostmask now = time.time() for (pattern, expiration) in list(self.bans.items()): if now < expiration or not expiration: if ircutils.hostmaskPatternEqual(pattern, hostmask): return True else: self.expiredBans.append((pattern, expiration)) del self.bans[pattern] return False def addIgnore(self, hostmask, expiration=0): """Adds an ignore to the channel ignore list.""" assert ircutils.isUserHostmask(hostmask), 'got %s' % hostmask self.ignores[hostmask] = int(expiration) def removeIgnore(self, hostmask): """Removes an ignore from the channel ignore list.""" assert ircutils.isUserHostmask(hostmask), 'got %s' % hostmask return self.ignores.pop(hostmask) def addCapability(self, capability): """Adds a capability to the channel's default capabilities.""" assert isCapability(capability), 'got %s' % capability self.capabilities.add(capability) def removeCapability(self, capability): """Removes a capability from the channel's default capabilities.""" assert isCapability(capability), 'got %s' % capability self.capabilities.remove(capability) def setDefaultCapability(self, b): """Sets the default capability in the channel.""" self.defaultAllow = b def _checkCapability(self, capability, ignoreOwner=False): """Checks whether a certain capability is allowed by the channel.""" assert isCapability(capability), 'got %s' % capability if capability in self.capabilities: return self.capabilities.check(capability, ignoreOwner=ignoreOwner) else: if isAntiCapability(capability): return not self.defaultAllow else: return self.defaultAllow def checkIgnored(self, hostmask): """Checks whether a given hostmask is to be ignored by the channel.""" if self.lobotomized: return True if world.testing: return False assert ircutils.isUserHostmask(hostmask), 'got %s' % hostmask if self.checkBan(hostmask): return True now = time.time() for (pattern, expiration) in list(self.ignores.items()): if now < expiration or not expiration: if ircutils.hostmaskPatternEqual(pattern, hostmask): return True else: del self.ignores[pattern] # Later we may wish to keep expiredIgnores, but not now. return False def preserve(self, fd, indent=''): def write(s): fd.write(indent) fd.write(s) fd.write(os.linesep) write('lobotomized %s' % self.lobotomized) write('defaultAllow %s' % self.defaultAllow) for capability in self.capabilities: write('capability ' + capability) bans = list(self.bans.items()) utils.sortBy(operator.itemgetter(1), bans) for (ban, expiration) in bans: write('ban %s %d' % (ban, expiration)) ignores = list(self.ignores.items()) utils.sortBy(operator.itemgetter(1), ignores) for (ignore, expiration) in ignores: write('ignore %s %d' % (ignore, expiration)) fd.write(os.linesep) class Creator(object): __slots__ = () def badCommand(self, command, rest, lineno): raise ValueError('Invalid command on line %s: %s' % (lineno, command)) class IrcUserCreator(Creator): __slots__ = ('users') u = None def __init__(self, users): if self.u is None: IrcUserCreator.u = IrcUser() self.users = users def user(self, rest, lineno): if self.u.id is not None: raise ValueError('Unexpected user command on line %s.' % lineno) self.u.id = int(rest) def _checkId(self): if self.u.id is None: raise ValueError('Unexpected user description without user.') def name(self, rest, lineno): self._checkId() self.u.name = rest def ignore(self, rest, lineno): self._checkId() self.u.ignore = bool(utils.gen.safeEval(rest)) def secure(self, rest, lineno): self._checkId() self.u.secure = bool(utils.gen.safeEval(rest)) def hashed(self, rest, lineno): self._checkId() self.u.hashed = bool(utils.gen.safeEval(rest)) def password(self, rest, lineno): self._checkId() self.u.password = rest def hostmask(self, rest, lineno): self._checkId() self.u.hostmasks.add(rest) def nicks(self, rest, lineno): self._checkId() network, nicks = rest.split(' ', 1) self.u.nicks[network] = nicks.split(' ') def capability(self, rest, lineno): self._checkId() self.u.capabilities.add(rest) def gpgkey(self, rest, lineno): self._checkId() self.u.gpgkeys.append(rest) def finish(self): if self.u.name: try: self.users.setUser(self.u) except DuplicateHostmask: log.error('Hostmasks for %s collided with another user\'s. ' 'Resetting hostmasks for %s.', self.u.name, self.u.name) # Some might argue that this is arbitrary, and perhaps it is. # But we've got to do *something*, so we'll show some deference # to our lower-numbered users. self.u.hostmasks.clear() self.users.setUser(self.u) IrcUserCreator.u = None class IrcChannelCreator(Creator): __slots__ = ('c', 'channels', 'hadChannel') name = None def __init__(self, channels): self.c = IrcChannel() self.channels = channels self.hadChannel = bool(self.name) def channel(self, rest, lineno): if self.name is not None: raise ValueError('Unexpected channel command on line %s' % lineno) IrcChannelCreator.name = rest def _checkId(self): if self.name is None: raise ValueError('Unexpected channel description without channel.') def lobotomized(self, rest, lineno): self._checkId() self.c.lobotomized = bool(utils.gen.safeEval(rest)) def defaultallow(self, rest, lineno): self._checkId() self.c.defaultAllow = bool(utils.gen.safeEval(rest)) def capability(self, rest, lineno): self._checkId() self.c.capabilities.add(rest) def ban(self, rest, lineno): self._checkId() (pattern, expiration) = rest.split() self.c.bans[pattern] = int(float(expiration)) def ignore(self, rest, lineno): self._checkId() (pattern, expiration) = rest.split() self.c.ignores[pattern] = int(float(expiration)) def finish(self): if self.hadChannel: self.channels.setChannel(self.name, self.c) IrcChannelCreator.name = None class DuplicateHostmask(ValueError): pass class UsersDictionary(utils.IterableMap): """A simple serialized-to-file User Database.""" __slots__ = ('noFlush', 'filename', 'users', '_nameCache', '_hostmaskCache') def __init__(self): self.noFlush = False self.filename = None self.users = {} self.nextId = 0 self._nameCache = utils.structures.CacheDict(1000) self._hostmaskCache = utils.structures.CacheDict(1000) # This is separate because the Creator has to access our instance. def open(self, filename): self.filename = filename reader = unpreserve.Reader(IrcUserCreator, self) try: self.noFlush = True try: reader.readFile(filename) self.noFlush = False self.flush() except EnvironmentError as e: log.error('Invalid user dictionary file, resetting to empty.') log.error('Exact error: %s', utils.exnToString(e)) except Exception as e: log.exception('Exact error:') finally: self.noFlush = False def reload(self): """Reloads the database from its file.""" if self.filename is not None: self.nextId = 0 self.users.clear() self._nameCache.clear() self._hostmaskCache.clear() try: self.open(self.filename) except EnvironmentError as e: log.warning('UsersDictionary.reload failed: %s', e) else: log.error('UsersDictionary.reload called with no filename.') def flush(self): """Flushes the database to its file.""" if not self.noFlush: if self.filename is not None: L = list(self.users.items()) L.sort() fd = utils.file.AtomicFile(self.filename) for (id, u) in L: fd.write('user %s' % id) fd.write(os.linesep) u.preserve(fd, indent=' ') fd.close() else: log.error('UsersDictionary.flush called with no filename.') else: log.debug('Not flushing UsersDictionary because of noFlush.') def close(self): self.flush() if self.flush in world.flushers: world.flushers.remove(self.flush) self.users.clear() def items(self): return self.users.items() def getUserId(self, s): """Returns the user ID of a given name or hostmask.""" if ircutils.isUserHostmask(s): try: return self._hostmaskCache[s] except KeyError: ids = {} for (id, user) in self.users.items(): x = user.checkHostmask(s) if x: ids[id] = x if len(ids) == 1: id = list(ids.keys())[0] self._hostmaskCache[s] = id try: self._hostmaskCache[id].add(s) except KeyError: self._hostmaskCache[id] = set([s]) return id elif len(ids) == 0: raise KeyError(s) else: log.error('Multiple matches found in user database. ' 'Removing the offending hostmasks.') for (id, hostmask) in ids.items(): log.error('Removing %q from user %s.', hostmask, id) self.users[id].removeHostmask(hostmask) raise DuplicateHostmask('Ids %r matched.' % ids) else: # Not a hostmask, must be a name. s = s.lower() try: return self._nameCache[s] except KeyError: for (id, user) in self.users.items(): if s == user.name.lower(): self._nameCache[s] = id self._nameCache[id] = s return id else: raise KeyError(s) def getUser(self, id): """Returns a user given its id, name, or hostmask.""" if not isinstance(id, int): # Must be a string. Get the UserId first. id = self.getUserId(id) u = self.users[id] while isinstance(u, int): id = u u = self.users[id] u.id = id return u def getUserFromNick(self, network, nick): """Return a user given its nick.""" for user in self.users.values(): try: if nick in user.nicks[network]: return user except KeyError: pass return None def hasUser(self, id): """Returns the database has a user given its id, name, or hostmask.""" try: self.getUser(id) return True except KeyError: return False def numUsers(self): return len(self.users) def invalidateCache(self, id=None, hostmask=None, name=None): if hostmask is not None: if hostmask in self._hostmaskCache: id = self._hostmaskCache.pop(hostmask) self._hostmaskCache[id].remove(hostmask) if not self._hostmaskCache[id]: del self._hostmaskCache[id] if name is not None: del self._nameCache[self._nameCache[id]] del self._nameCache[id] if id is not None: if id in self._nameCache: del self._nameCache[self._nameCache[id]] del self._nameCache[id] if id in self._hostmaskCache: for hostmask in self._hostmaskCache[id]: del self._hostmaskCache[hostmask] del self._hostmaskCache[id] def setUser(self, user, flush=True): """Sets a user (given its id) to the IrcUser given it.""" self.nextId = max(self.nextId, user.id) try: if self.getUserId(user.name) != user.id: raise DuplicateHostmask(user.name, user.name) except KeyError: pass for hostmask in user.hostmasks: for (i, u) in self.items(): if i == user.id: continue elif u.checkHostmask(hostmask): # We used to remove the hostmask here, but it's not # appropriate for us both to remove the hostmask and to # raise an exception. So instead, we'll raise an # exception, but be nice and give the offending hostmask # back at the same time. raise DuplicateHostmask(u.name, hostmask) for otherHostmask in u.hostmasks: if ircutils.hostmaskPatternEqual(hostmask, otherHostmask): raise DuplicateHostmask(u.name, hostmask) self.invalidateCache(user.id) self.users[user.id] = user if flush: self.flush() def delUser(self, id): """Removes a user from the database.""" del self.users[id] if id in self._nameCache: del self._nameCache[self._nameCache[id]] del self._nameCache[id] if id in self._hostmaskCache: for hostmask in list(self._hostmaskCache[id]): del self._hostmaskCache[hostmask] del self._hostmaskCache[id] self.flush() def newUser(self): """Allocates a new user in the database and returns it and its id.""" user = IrcUser(hashed=True) self.nextId += 1 id = self.nextId self.users[id] = user self.flush() user.id = id return user class ChannelsDictionary(utils.IterableMap): __slots__ = ('noFlush', 'filename', 'channels') def __init__(self): self.noFlush = False self.filename = None self.channels = ircutils.IrcDict() def open(self, filename): self.noFlush = True try: self.filename = filename reader = unpreserve.Reader(IrcChannelCreator, self) try: reader.readFile(filename) self.noFlush = False self.flush() except EnvironmentError as e: log.error('Invalid channel database, resetting to empty.') log.error('Exact error: %s', utils.exnToString(e)) except Exception as e: log.error('Invalid channel database, resetting to empty.') log.exception('Exact error:') finally: self.noFlush = False def flush(self): """Flushes the channel database to its file.""" if not self.noFlush: if self.filename is not None: fd = utils.file.AtomicFile(self.filename) for (channel, c) in self.channels.items(): fd.write('channel %s' % channel) fd.write(os.linesep) c.preserve(fd, indent=' ') fd.close() else: log.warning('ChannelsDictionary.flush without self.filename.') else: log.debug('Not flushing ChannelsDictionary because of noFlush.') def close(self): self.flush() if self.flush in world.flushers: world.flushers.remove(self.flush) self.channels.clear() def reload(self): """Reloads the channel database from its file.""" if self.filename is not None: self.channels.clear() try: self.open(self.filename) except EnvironmentError as e: log.warning('ChannelsDictionary.reload failed: %s', e) else: log.warning('ChannelsDictionary.reload without self.filename.') def getChannel(self, channel): """Returns an IrcChannel object for the given channel.""" channel = channel.lower() if channel in self.channels: return self.channels[channel] else: c = IrcChannel() self.channels[channel] = c return c def setChannel(self, channel, ircChannel): """Sets a given channel to the IrcChannel object given.""" channel = channel.lower() self.channels[channel] = ircChannel self.flush() def items(self): return self.channels.items() class IgnoresDB(object): __slots__ = ('filename', 'hostmasks') def __init__(self): self.filename = None self.hostmasks = {} def open(self, filename): self.filename = filename fd = open(self.filename) for line in utils.file.nonCommentNonEmptyLines(fd): try: line = line.rstrip('\r\n') L = line.split() hostmask = L.pop(0) if L: expiration = int(float(L.pop(0))) else: expiration = 0 self.add(hostmask, expiration) except Exception: log.error('Invalid line in ignores database: %q', line) fd.close() def flush(self): if self.filename is not None: fd = utils.file.AtomicFile(self.filename) now = time.time() for (hostmask, expiration) in self.hostmasks.items(): if now < expiration or not expiration: fd.write('%s %s' % (hostmask, expiration)) fd.write(os.linesep) fd.close() else: log.warning('IgnoresDB.flush called without self.filename.') def close(self): if self.flush in world.flushers: world.flushers.remove(self.flush) self.flush() self.hostmasks.clear() def reload(self): if self.filename is not None: oldhostmasks = self.hostmasks.copy() self.hostmasks.clear() try: self.open(self.filename) except EnvironmentError as e: log.warning('IgnoresDB.reload failed: %s', e) # Let's be somewhat transactional. self.hostmasks.update(oldhostmasks) else: log.warning('IgnoresDB.reload called without self.filename.') def checkIgnored(self, prefix): now = time.time() for (hostmask, expiration) in list(self.hostmasks.items()): if expiration and now > expiration: del self.hostmasks[hostmask] else: if ircutils.hostmaskPatternEqual(hostmask, prefix): return True return False def add(self, hostmask, expiration=0): assert ircutils.isUserHostmask(hostmask), 'got %s' % hostmask self.hostmasks[hostmask] = expiration def remove(self, hostmask): del self.hostmasks[hostmask] confDir = conf.supybot.directories.conf() try: userFile = os.path.join(confDir, conf.supybot.databases.users.filename()) users = UsersDictionary() users.open(userFile) except EnvironmentError as e: log.warning('Couldn\'t open user database: %s', e) try: channelFile = os.path.join(confDir, conf.supybot.databases.channels.filename()) channels = ChannelsDictionary() channels.open(channelFile) except EnvironmentError as e: log.warning('Couldn\'t open channel database: %s', e) try: ignoreFile = os.path.join(confDir, conf.supybot.databases.ignores.filename()) ignores = IgnoresDB() ignores.open(ignoreFile) except EnvironmentError as e: log.warning('Couldn\'t open ignore database: %s', e) world.flushers.append(users.flush) world.flushers.append(ignores.flush) world.flushers.append(channels.flush) ### # Useful functions for checking credentials. ### def checkIgnored(hostmask, recipient='', users=users, channels=channels): """checkIgnored(hostmask, recipient='') -> True/False Checks if the user is ignored by the recipient of the message. """ try: id = users.getUserId(hostmask) user = users.getUser(id) if user._checkCapability('owner'): # Owners shouldn't ever be ignored. return False elif user.ignore: log.debug('Ignoring %s due to their IrcUser ignore flag.', hostmask) return True except KeyError: # If there's no user... if conf.supybot.defaultIgnore(): log.debug('Ignoring %s due to conf.supybot.defaultIgnore', hostmask) return True if ignores.checkIgnored(hostmask): log.debug('Ignoring %s due to ignore database.', hostmask) return True if ircutils.isChannel(recipient): channel = channels.getChannel(recipient) if channel.checkIgnored(hostmask): log.debug('Ignoring %s due to the channel ignores.', hostmask) return True return False def _x(capability, ret): if isAntiCapability(capability): return not ret else: return ret def _checkCapabilityForUnknownUser(capability, users=users, channels=channels, ignoreDefaultAllow=False): if isChannelCapability(capability): (channel, capability) = fromChannelCapability(capability) try: c = channels.getChannel(channel) if capability in c.capabilities: return c._checkCapability(capability) else: return _x(capability, (not ignoreDefaultAllow) and c.defaultAllow) except KeyError: pass defaultCapabilities = conf.supybot.capabilities() if capability in defaultCapabilities: return defaultCapabilities.check(capability) elif ignoreDefaultAllow: return _x(capability, False) else: return _x(capability, conf.supybot.capabilities.default()) def checkCapability(hostmask, capability, users=users, channels=channels, ignoreOwner=False, ignoreChannelOp=False, ignoreDefaultAllow=False): """Checks that the user specified by name/hostmask has the capability given. ``users`` and ``channels`` default to ``ircdb.users`` and ``ircdb.channels``. ``ignoreOwner``, ``ignoreChannelOp``, and ``ignoreDefaultAllow`` are used to override default behavior of the capability system in special cases (actually, in the AutoMode plugin): * ``ignoreOwner`` disables the behavior "owners have all capabilites" * ``ignoreChannelOp`` disables the behavior "channel ops have all channel capabilities" * ``ignoreDefaultAllow`` disables the behavior "if a user does not have a capability or the associated anticapability, then they have the capability" """ if world.testing and (not isinstance(hostmask, str) or '@' not in hostmask or '__no_testcap__' not in hostmask.split('@')[1]): return _x(capability, True) try: u = users.getUser(hostmask) if u.secure and not u.checkHostmask(hostmask, useAuth=False): raise KeyError except KeyError: # Raised when no hostmasks match. return _checkCapabilityForUnknownUser(capability, users=users, channels=channels, ignoreDefaultAllow=ignoreDefaultAllow) except ValueError as e: # Raised when multiple hostmasks match. log.warning('%s: %s', hostmask, e) return _checkCapabilityForUnknownUser(capability, users=users, channels=channels, ignoreDefaultAllow=ignoreDefaultAllow) if capability in u.capabilities: try: return u._checkCapability(capability, ignoreOwner) except KeyError: pass if isChannelCapability(capability): (channel, capability) = fromChannelCapability(capability) if not ignoreChannelOp: try: chanop = makeChannelCapability(channel, 'op') if u._checkCapability(chanop): return _x(capability, True) except KeyError: pass c = channels.getChannel(channel) if capability in c.capabilities: return c._checkCapability(capability) elif not ignoreDefaultAllow: return _x(capability, c.defaultAllow) else: return False defaultCapabilities = conf.supybot.capabilities() defaultCapabilitiesRegistered = conf.supybot.capabilities.registeredUsers() if capability in defaultCapabilities: return defaultCapabilities.check(capability) elif capability in defaultCapabilitiesRegistered: return defaultCapabilitiesRegistered.check(capability) elif ignoreDefaultAllow: return _x(capability, False) else: return _x(capability, conf.supybot.capabilities.default()) def checkCapabilities(hostmask, capabilities, requireAll=False): """Checks that a user has capabilities in a list. requireAll is True if *all* capabilities in the list must be had, False if *any* of the capabilities in the list must be had. """ for capability in capabilities: if requireAll: if not checkCapability(hostmask, capability): return False else: if checkCapability(hostmask, capability): return True return requireAll ### # supybot.capabilities ### class SpaceSeparatedListOfCapabilities(registry.SpaceSeparatedListOfStrings): __slots__ = () List = CapabilitySet class DefaultCapabilities(SpaceSeparatedListOfCapabilities): __slots__ = () # We use a keyword argument trick here to prevent eval'ing of code that # changes allowDefaultOwner from affecting this. It's not perfect, but # it's still an improvement, raising the bar for potential crackers. def setValue(self, v, allowDefaultOwner=conf.allowDefaultOwner): registry.SpaceSeparatedListOfStrings.setValue(self, v) if '-owner' not in self.value and not allowDefaultOwner: print('*** You must run supybot with the --allow-default-owner') print('*** option in order to allow a default capability of owner.') print('*** Don\'t do that, it\'s dumb.') self.value.add('-owner') conf.registerGlobalValue(conf.supybot, 'capabilities', DefaultCapabilities([ '-owner', '-admin', '-trusted', '-aka.add', '-aka.set', '-aka.remove', '-alias.add', '-alias.remove', '-scheduler.add', '-scheduler.remove', ], """These are the capabilities that are given to everyone by default. If they are normal capabilities, then the user will have to have the appropriate anti-capability if you want to override these capabilities; if they are anti-capabilities, then the user will have to have the actual capability to override these capabilities. See docs/CAPABILITIES if you don't understand why these default to what they do.""")) conf.registerGlobalValue(conf.supybot.capabilities, 'registeredUsers', SpaceSeparatedListOfCapabilities([], """These are the capabilities that are given to every authenticated user by default. You probably want to use supybot.capabilities instead, to give these capabilities both to registered and non-registered users.""")) conf.registerGlobalValue(conf.supybot.capabilities, 'default', registry.Boolean(True, """Determines whether the bot by default will allow users to have a capability. If this is disabled, a user must explicitly have the capability for whatever command they wish to run. To set this in a channel-specific way, use the 'channel capability setdefault' command.""")) conf.registerGlobalValue(conf.supybot.capabilities, 'private', registry.SpaceSeparatedListOfStrings([], """Determines what capabilities the bot will never tell to a non-admin whether or not a user has them.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/irclib.py0000644000175000017500000017127413634634532015740 0ustar valval00000000000000### # Copyright (c) 2002-2005 Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import re import copy import time import random import base64 import textwrap import warnings import collections try: class crypto: import cryptography from cryptography.hazmat.primitives.serialization \ import load_pem_private_key from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric.ec import ECDSA from cryptography.hazmat.primitives.asymmetric.utils import Prehashed from cryptography.hazmat.primitives.hashes import SHA256 except ImportError: crypto = None try: import pyxmpp2_scram as scram except ImportError: scram = None from . import conf, ircdb, ircmsgs, ircutils, log, utils, world from .utils.str import rsplit from .utils.iter import chain from .utils.structures import smallqueue, RingBuffer MAX_LINE_SIZE = 512 # Including \r\n ### # The base class for a callback to be registered with an Irc object. Shows # the required interface for callbacks -- name(), # inFilter(irc, msg), outFilter(irc, msg), and __call__(irc, msg) [used so as # to make functions used as callbacks conceivable, and so if refactoring ever # changes the nature of the callbacks from classes to functions, syntactical # changes elsewhere won't be required.] ### class IrcCommandDispatcher(object): """Base class for classes that must dispatch on a command.""" def dispatchCommand(self, command, args=None): """Given a string 'command', dispatches to doCommand.""" if args is None: warnings.warn( "dispatchCommand now takes an 'args' attribute, which is " "a list of the command's arguments (ie. IrcMsg.args).", DeprecationWarning) args = [] command = command.upper() subcommand = None method = None # Dispatch on command + subcommand, if there is a subcommand, and # a method with the matching name exists if command in ('FAIL', 'WARN', 'NOTE') and len(args) >= 1: subcommand = args[0] elif command in ('CAP',) and len(args) >= 2: # Note: this only covers the server-to-client format subcommand = args[1] command = command.capitalize() if subcommand is not None: subcommand = subcommand.capitalize() method = getattr(self, 'do' + command + subcommand, None) # If not dispatched on command + subcommand, then dispatch on command if method is None: method = getattr(self, 'do' + command, None) return method class IrcCallback(IrcCommandDispatcher, log.Firewalled): """Base class for standard callbacks. Callbacks derived from this class should have methods of the form "doCommand" -- doPrivmsg, doNick, do433, etc. These will be called on matching messages. """ callAfter = () callBefore = () __firewalled__ = {'die': None, 'reset': None, '__call__': None, 'inFilter': lambda self, irc, msg: msg, 'outFilter': lambda self, irc, msg: msg, 'name': lambda self: self.__class__.__name__, 'callPrecedence': lambda self, irc: ([], []), } def __init__(self, *args, **kwargs): #object doesn't take any args, so the buck stops here. #super(IrcCallback, self).__init__(*args, **kwargs) pass def __repr__(self): return '<%s %s %s>' % \ (self.__class__.__name__, self.name(), object.__repr__(self)) def name(self): """Returns the name of the callback.""" return self.__class__.__name__ def callPrecedence(self, irc): """Returns a pair of (callbacks to call before me, callbacks to call after me)""" after = [] before = [] for name in self.callBefore: cb = irc.getCallback(name) if cb is not None: after.append(cb) for name in self.callAfter: cb = irc.getCallback(name) if cb is not None: before.append(cb) assert self not in after, '%s was in its own after.' % self.name() assert self not in before, '%s was in its own before.' % self.name() return (before, after) def inFilter(self, irc, msg): """Used for filtering/modifying messages as they're entering. ircmsgs.IrcMsg objects are immutable, so this method is expected to return another ircmsgs.IrcMsg object. Obviously the same IrcMsg can be returned. """ return msg def outFilter(self, irc, msg): """Used for filtering/modifying messages as they're leaving. As with inFilter, an IrcMsg is returned. """ return msg def __call__(self, irc, msg): """Used for handling each message.""" method = self.dispatchCommand(msg.command, msg.args) if method is not None: method(irc, msg) def reset(self): """Resets the callback. Called when reconnecting to the server.""" pass def die(self): """Makes the callback die. Called when the parent Irc object dies.""" pass ### # Basic queue for IRC messages. It doesn't presently (but should at some # later point) reorder messages based on priority or penalty calculations. ### _high = frozenset(['MODE', 'KICK', 'PONG', 'NICK', 'PASS', 'CAPAB', 'REMOVE']) _low = frozenset(['PRIVMSG', 'PING', 'WHO', 'NOTICE', 'JOIN']) class IrcMsgQueue(object): """Class for a queue of IrcMsgs. Eventually, it should be smart. Probably smarter than it is now, though it's gotten quite a bit smarter than it originally was. A method to "score" methods, and a heapq to maintain a priority queue of the messages would be the ideal way to do intelligent queuing. As it stands, however, we simply keep track of 'high priority' messages, 'low priority' messages, and normal messages, and just make sure to return the 'high priority' ones before the normal ones before the 'low priority' ones. """ __slots__ = ('msgs', 'highpriority', 'normal', 'lowpriority', 'lastJoin') def __init__(self, iterable=()): self.reset() for msg in iterable: self.enqueue(msg) def reset(self): """Clears the queue.""" self.lastJoin = 0 self.highpriority = smallqueue() self.normal = smallqueue() self.lowpriority = smallqueue() def enqueue(self, msg): """Enqueues a given message.""" if msg in self and \ conf.supybot.protocols.irc.queuing.duplicates(): s = str(msg).strip() log.info('Not adding message %q to queue, already added.', s) return False else: if msg.command in _high: self.highpriority.enqueue(msg) elif msg.command in _low: self.lowpriority.enqueue(msg) else: self.normal.enqueue(msg) return True def dequeue(self): """Dequeues a given message.""" msg = None if self.highpriority: msg = self.highpriority.dequeue() elif self.normal: msg = self.normal.dequeue() elif self.lowpriority: msg = self.lowpriority.dequeue() if msg.command == 'JOIN': limit = conf.supybot.protocols.irc.queuing.rateLimit.join() now = time.time() if self.lastJoin + limit <= now: self.lastJoin = now else: self.lowpriority.enqueue(msg) msg = None return msg def __contains__(self, msg): return msg in self.normal or \ msg in self.lowpriority or \ msg in self.highpriority def __bool__(self): return bool(self.highpriority or self.normal or self.lowpriority) __nonzero__ = __bool__ def __len__(self): return len(self.highpriority)+len(self.lowpriority)+len(self.normal) def __repr__(self): name = self.__class__.__name__ return '%s(%r)' % (name, list(chain(self.highpriority, self.normal, self.lowpriority))) __str__ = __repr__ ### # Maintains the state of IRC connection -- the most recent messages, the # status of various modes (especially ops/halfops/voices) in channels, etc. ### class ChannelState(utils.python.Object): __slots__ = ('users', 'ops', 'halfops', 'bans', 'voices', 'topic', 'modes', 'created') def __init__(self): self.topic = '' self.created = 0 self.ops = ircutils.IrcSet() self.bans = ircutils.IrcSet() self.users = ircutils.IrcSet() self.voices = ircutils.IrcSet() self.halfops = ircutils.IrcSet() self.modes = {} def isOp(self, nick): return nick in self.ops def isOpPlus(self, nick): return nick in self.ops def isVoice(self, nick): return nick in self.voices def isVoicePlus(self, nick): return nick in self.voices or nick in self.halfops or nick in self.ops def isHalfop(self, nick): return nick in self.halfops def isHalfopPlus(self, nick): return nick in self.halfops or nick in self.ops def addUser(self, user): "Adds a given user to the ChannelState. Power prefixes are handled." nick = user.lstrip('@%+&~!') if not nick: return # & is used to denote protected users in UnrealIRCd # ~ is used to denote channel owner in UnrealIRCd # ! is used to denote protected users in UltimateIRCd while user and user[0] in '@%+&~!': (marker, user) = (user[0], user[1:]) assert user, 'Looks like my caller is passing chars, not nicks.' if marker in '@&~!': self.ops.add(nick) elif marker == '%': self.halfops.add(nick) elif marker == '+': self.voices.add(nick) self.users.add(nick) def replaceUser(self, oldNick, newNick): """Changes the user oldNick to newNick; used for NICK changes.""" # Note that this doesn't have to have the sigil (@%+) that users # have to have for addUser; it just changes the name of the user # without changing any of their categories. for s in (self.users, self.ops, self.halfops, self.voices): if oldNick in s: s.remove(oldNick) s.add(newNick) def removeUser(self, user): """Removes a given user from the channel.""" self.users.discard(user) self.ops.discard(user) self.halfops.discard(user) self.voices.discard(user) def setMode(self, mode, value=None): assert mode not in 'ovhbeq' self.modes[mode] = value def unsetMode(self, mode): assert mode not in 'ovhbeq' if mode in self.modes: del self.modes[mode] def doMode(self, msg): def getSet(c): if c == 'o': Set = self.ops elif c == 'v': Set = self.voices elif c == 'h': Set = self.halfops elif c == 'b': Set = self.bans else: # We don't care yet, so we'll just return an empty set. Set = set() return Set for (mode, value) in ircutils.separateModes(msg.args[1:]): (action, modeChar) = mode if modeChar in 'ovhbeq': # We don't handle e or q yet. Set = getSet(modeChar) if action == '-': Set.discard(value) elif action == '+': Set.add(value) else: if action == '+': self.setMode(modeChar, value) else: assert action == '-' self.unsetMode(modeChar) def __getstate__(self): return [getattr(self, name) for name in self.__slots__] def __setstate__(self, t): for (name, value) in zip(self.__slots__, t): setattr(self, name, value) def __eq__(self, other): ret = True for name in self.__slots__: ret = ret and getattr(self, name) == getattr(other, name) return ret Batch = collections.namedtuple('Batch', 'type arguments messages') class IrcState(IrcCommandDispatcher, log.Firewalled): """Maintains state of the Irc connection. Should also become smarter. """ __firewalled__ = {'addMsg': None} def __init__(self, history=None, supported=None, nicksToHostmasks=None, channels=None, capabilities_ack=None, capabilities_nak=None, capabilities_ls=None): if history is None: history = RingBuffer(conf.supybot.protocols.irc.maxHistoryLength()) if supported is None: supported = utils.InsensitivePreservingDict() if nicksToHostmasks is None: nicksToHostmasks = ircutils.IrcDict() if channels is None: channels = ircutils.IrcDict() self.capabilities_ack = capabilities_ack or set() self.capabilities_nak = capabilities_nak or set() self.capabilities_ls = capabilities_ls or {} self.ircd = None self.supported = supported self.history = history self.channels = channels self.nicksToHostmasks = nicksToHostmasks self.batches = {} def reset(self): """Resets the state to normal, unconnected state.""" self.history.reset() self.history.resize(conf.supybot.protocols.irc.maxHistoryLength()) self.ircd = None self.channels.clear() self.supported.clear() self.nicksToHostmasks.clear() self.batches = {} self.capabilities_ack = set() self.capabilities_nak = set() self.capabilities_ls = {} def __reduce__(self): return (self.__class__, (self.history, self.supported, self.nicksToHostmasks, self.channels)) def __eq__(self, other): return self.history == other.history and \ self.channels == other.channels and \ self.supported == other.supported and \ self.nicksToHostmasks == other.nicksToHostmasks and \ self.batches == other.batches def __ne__(self, other): return not self == other def copy(self): ret = self.__class__() ret.history = copy.deepcopy(self.history) ret.nicksToHostmasks = copy.deepcopy(self.nicksToHostmasks) ret.channels = copy.deepcopy(self.channels) ret.batches = copy.deepcopy(self.batches) return ret def addMsg(self, irc, msg): """Updates the state based on the irc object and the message.""" self.history.append(msg) if ircutils.isUserHostmask(msg.prefix) and not msg.command == 'NICK': self.nicksToHostmasks[msg.nick] = msg.prefix if 'batch' in msg.server_tags: batch = msg.server_tags['batch'] assert batch in self.batches, \ 'Server references undeclared batch %s' % batch self.batches[batch].messages.append(msg) method = self.dispatchCommand(msg.command, msg.args) if method is not None: method(irc, msg) def getTopic(self, channel): """Returns the topic for a given channel.""" return self.channels[channel].topic def nickToHostmask(self, nick): """Returns the hostmask for a given nick.""" return self.nicksToHostmasks[nick] def do004(self, irc, msg): """Handles parsing the 004 reply Supported user and channel modes are cached""" # msg.args = [nick, server, ircd-version, umodes, modes, # modes that require arguments? (non-standard)] self.ircd = msg.args[2] if len(msg.args) >= 3 else msg.args[1] self.supported['umodes'] = frozenset(msg.args[3]) self.supported['chanmodes'] = frozenset(msg.args[4]) _005converters = utils.InsensitivePreservingDict({ 'modes': int, 'keylen': int, 'nicklen': int, 'userlen': int, 'hostlen': int, 'kicklen': int, 'awaylen': int, 'silence': int, 'topiclen': int, 'channellen': int, 'maxtargets': int, 'maxnicklen': int, 'maxchannels': int, 'watch': int, # DynastyNet, EnterTheGame }) def _prefixParser(s): if ')' in s: (left, right) = s.split(')') assert left[0] == '(', 'Odd PREFIX in 005: %s' % s left = left[1:] assert len(left) == len(right), 'Odd PREFIX in 005: %s' % s return dict(list(zip(left, right))) else: return dict(list(zip('ovh', s))) _005converters['prefix'] = _prefixParser del _prefixParser def _maxlistParser(s): modes = '' limits = [] pairs = s.split(',') for pair in pairs: (mode, limit) = pair.split(':', 1) modes += mode limits += (int(limit),) * len(mode) return dict(list(zip(modes, limits))) _005converters['maxlist'] = _maxlistParser del _maxlistParser def _maxbansParser(s): # IRCd using a MAXLIST style string (IRCNet) if ':' in s: modes = '' limits = [] pairs = s.split(',') for pair in pairs: (mode, limit) = pair.split(':', 1) modes += mode limits += (int(limit),) * len(mode) d = dict(list(zip(modes, limits))) assert 'b' in d return d['b'] else: return int(s) _005converters['maxbans'] = _maxbansParser del _maxbansParser def do005(self, irc, msg): for arg in msg.args[1:-1]: # 0 is nick, -1 is "are supported" if '=' in arg: (name, value) = arg.split('=', 1) converter = self._005converters.get(name, lambda x: x) try: self.supported[name] = converter(value) except Exception: log.exception('Uncaught exception in 005 converter:') log.error('Name: %s, Converter: %s', name, converter) else: self.supported[arg] = None def do352(self, irc, msg): # WHO reply. (nick, user, host) = (msg.args[5], msg.args[2], msg.args[3]) hostmask = '%s!%s@%s' % (nick, user, host) self.nicksToHostmasks[nick] = hostmask def do354(self, irc, msg): # WHOX reply. if len(msg.args) != 9 or msg.args[1] != '1': return # irc.nick 1 user ip host nick status account gecos (n, t, user, ip, host, nick, status, account, gecos) = msg.args hostmask = '%s!%s@%s' % (nick, user, host) self.nicksToHostmasks[nick] = hostmask def do353(self, irc, msg): # NAMES reply. (__, type, channel, items) = msg.args if channel not in self.channels: self.channels[channel] = ChannelState() c = self.channels[channel] for item in items.split(): if ircutils.isUserHostmask(item): name = ircutils.nickFromHostmask(item) self.nicksToHostmasks[name] = name else: name = item c.addUser(name) if type == '@': c.modes['s'] = None def doChghost(self, irc, msg): (user, host) = msg.args nick = msg.nick hostmask = '%s!%s@%s' % (nick, user, host) self.nicksToHostmasks[nick] = hostmask def doJoin(self, irc, msg): for channel in msg.args[0].split(','): if channel in self.channels: self.channels[channel].addUser(msg.nick) elif msg.nick: # It must be us. chan = ChannelState() chan.addUser(msg.nick) self.channels[channel] = chan # I don't know why this assert was here. #assert msg.nick == irc.nick, msg def do367(self, irc, msg): # Example: # :server 367 user #chan some!random@user evil!channel@op 1356276459 try: state = self.channels[msg.args[1]] except KeyError: # We have been kicked of the channel before the server replied to # the MODE +b command. pass else: state.bans.add(msg.args[2]) def doMode(self, irc, msg): channel = msg.args[0] if irc.isChannel(channel): # There can be user modes, as well. try: chan = self.channels[channel] except KeyError: chan = ChannelState() self.channels[channel] = chan chan.doMode(msg) def do324(self, irc, msg): channel = msg.args[1] try: chan = self.channels[channel] except KeyError: chan = ChannelState() self.channels[channel] = chan for (mode, value) in ircutils.separateModes(msg.args[2:]): modeChar = mode[1] if mode[0] == '+' and mode[1] not in 'ovh': chan.setMode(modeChar, value) elif mode[0] == '-' and mode[1] not in 'ovh': chan.unsetMode(modeChar) def do329(self, irc, msg): # This is the last part of an empty mode. channel = msg.args[1] try: chan = self.channels[channel] except KeyError: chan = ChannelState() self.channels[channel] = chan chan.created = int(msg.args[2]) def doPart(self, irc, msg): for channel in msg.args[0].split(','): try: chan = self.channels[channel] except KeyError: continue if ircutils.strEqual(msg.nick, irc.nick): del self.channels[channel] else: chan.removeUser(msg.nick) def doKick(self, irc, msg): (channel, users) = msg.args[:2] chan = self.channels[channel] for user in users.split(','): if ircutils.strEqual(user, irc.nick): del self.channels[channel] return else: chan.removeUser(user) def doQuit(self, irc, msg): channel_names = ircutils.IrcSet() for (name, channel) in self.channels.items(): if msg.nick in channel.users: channel_names.add(name) channel.removeUser(msg.nick) # Remember which channels the user was on msg.tag('channels', channel_names) if msg.nick in self.nicksToHostmasks: # If we're quitting, it may not be. del self.nicksToHostmasks[msg.nick] def doTopic(self, irc, msg): if len(msg.args) == 1: return # Empty TOPIC for information. Does not affect state. try: chan = self.channels[msg.args[0]] chan.topic = msg.args[1] except KeyError: pass # We don't have to be in a channel to send a TOPIC. def do332(self, irc, msg): chan = self.channels[msg.args[1]] chan.topic = msg.args[2] def doNick(self, irc, msg): newNick = msg.args[0] oldNick = msg.nick try: if msg.user and msg.host: # Nick messages being handed out from the bot itself won't # have the necessary prefix to make a hostmask. newHostmask = ircutils.joinHostmask(newNick,msg.user,msg.host) self.nicksToHostmasks[newNick] = newHostmask del self.nicksToHostmasks[oldNick] except KeyError: pass channel_names = ircutils.IrcSet() for (name, channel) in self.channels.items(): if msg.nick in channel.users: channel_names.add(name) channel.replaceUser(oldNick, newNick) msg.tag('channels', channel_names) def doBatch(self, irc, msg): batch_name = msg.args[0][1:] if msg.args[0].startswith('+'): batch_type = msg.args[1] batch_arguments = tuple(msg.args[2:]) self.batches[batch_name] = Batch(type=batch_type, arguments=batch_arguments, messages=[]) elif msg.args[0].startswith('-'): batch = self.batches.pop(batch_name) msg.tag('batch', batch) else: assert False, msg.args[0] def doAway(self, irc, msg): channel_names = ircutils.IrcSet() for (name, channel) in self.channels.items(): if msg.nick in channel.users: channel_names.add(name) msg.tag('channels', channel_names) ### # The basic class for handling a connection to an IRC server. Accepts # callbacks of the IrcCallback interface. Public attributes include 'driver', # 'queue', and 'state', in addition to the standard nick/user/ident attributes. ### _callbacks = [] class Irc(IrcCommandDispatcher, log.Firewalled): """The base class for an IRC connection. Handles PING commands already. """ __firewalled__ = {'die': None, 'feedMsg': None, 'takeMsg': None,} _nickSetters = set(['001', '002', '003', '004', '250', '251', '252', '254', '255', '265', '266', '372', '375', '376', '333', '353', '332', '366', '005']) # We specifically want these callbacks to be common between all Ircs, # that's why we don't do the normal None default with a check. def __init__(self, network, callbacks=_callbacks): self.zombie = False world.ircs.append(self) self.network = network self.startedAt = time.time() self.callbacks = callbacks self.state = IrcState() self.queue = IrcMsgQueue() self.fastqueue = smallqueue() self.driver = None # The driver should set this later. self._setNonResettingVariables() self._queueConnectMessages() self.startedSync = ircutils.IrcDict() self.monitoring = ircutils.IrcDict() def isChannel(self, s): """Helper function to check whether a given string is a channel on the network this Irc object is connected to.""" kw = {} if 'chantypes' in self.state.supported: kw['chantypes'] = self.state.supported['chantypes'] if 'channellen' in self.state.supported: kw['channellen'] = self.state.supported['channellen'] return ircutils.isChannel(s, **kw) def isNick(self, s): kw = {} if 'nicklen' in self.state.supported: kw['nicklen'] = self.state.supported['nicklen'] return ircutils.isNick(s, **kw) # This *isn't* threadsafe! def addCallback(self, callback): """Adds a callback to the callbacks list. :param callback: A callback object :type callback: supybot.irclib.IrcCallback """ assert not self.getCallback(callback.name()) self.callbacks.append(callback) # This is the new list we're building, which will be tsorted. cbs = [] # The vertices are self.callbacks itself. Now we make the edges. edges = set() for cb in self.callbacks: (before, after) = cb.callPrecedence(self) assert cb not in after, 'cb was in its own after.' assert cb not in before, 'cb was in its own before.' for otherCb in before: edges.add((otherCb, cb)) for otherCb in after: edges.add((cb, otherCb)) def getFirsts(): firsts = set(self.callbacks) - set(cbs) for (before, after) in edges: firsts.discard(after) return firsts firsts = getFirsts() while firsts: # Then we add these to our list of cbs, and remove all edges that # originate with these cbs. for cb in firsts: cbs.append(cb) edgesToRemove = [] for edge in edges: if edge[0] is cb: edgesToRemove.append(edge) for edge in edgesToRemove: edges.remove(edge) firsts = getFirsts() assert len(cbs) == len(self.callbacks), \ 'cbs: %s, self.callbacks: %s' % (cbs, self.callbacks) self.callbacks[:] = cbs def getCallback(self, name): """Gets a given callback by name.""" name = name.lower() for callback in self.callbacks: if callback.name().lower() == name: return callback else: return None def removeCallback(self, name): """Removes a callback from the callback list.""" name = name.lower() def nameMatches(cb): return cb.name().lower() == name (bad, good) = utils.iter.partition(nameMatches, self.callbacks) self.callbacks[:] = good return bad def queueMsg(self, msg): """Queues a message to be sent to the server.""" if not self.zombie: return self.queue.enqueue(msg) else: log.warning('Refusing to queue %r; %s is a zombie.', msg, self) return False def sendMsg(self, msg): """Queues a message to be sent to the server *immediately*""" if not self.zombie: self.fastqueue.enqueue(msg) else: log.warning('Refusing to send %r; %s is a zombie.', msg, self) def takeMsg(self): """Called by the IrcDriver; takes a message to be sent.""" if not self.callbacks: log.critical('No callbacks in %s.', self) now = time.time() msg = None if self.fastqueue: msg = self.fastqueue.dequeue() elif self.queue: if now-self.lastTake <= conf.supybot.protocols.irc.throttleTime(): log.debug('Irc.takeMsg throttling.') else: self.lastTake = now msg = self.queue.dequeue() elif self.afterConnect and \ conf.supybot.protocols.irc.ping() and \ now > self.lastping + conf.supybot.protocols.irc.ping.interval(): if self.outstandingPing: s = 'Ping sent at %s not replied to.' % \ log.timestamp(self.lastping) log.warning(s) self.feedMsg(ircmsgs.error(s)) self.driver.reconnect() elif not self.zombie: self.lastping = now now = str(int(now)) self.outstandingPing = True self.queueMsg(ircmsgs.ping(now)) if msg: for callback in reversed(self.callbacks): self._setMsgChannel(msg) msg = callback.outFilter(self, msg) if msg is None: log.debug('%s.outFilter returned None.', callback.name()) return self.takeMsg() world.debugFlush() if len(str(msg)) > MAX_LINE_SIZE: # Yes, this violates the contract, but at this point it doesn't # matter. That's why we gotta go munging in private attributes # # I'm changing this to a log.debug to fix a possible loop in # the LogToIrc plugin. Since users can't do anything about # this issue, there's no fundamental reason to make it a # warning. log.debug('Truncating %r, message is too long.', msg) msg._str = msg._str[:MAX_LINE_SIZE-2] + '\r\n' msg._len = len(str(msg)) # I don't think we should do this. Why should it matter? If it's # something important, then the server will send it back to us, # and if it's just a privmsg/notice/etc., we don't care. # On second thought, we need this for testing. if world.testing: self.state.addMsg(self, msg) log.debug('Outgoing message (%s): %s', self.network, str(msg).rstrip('\r\n')) return msg elif self.zombie: # We kill the driver here so it doesn't continue to try to # take messages from us. self.driver.die() self._reallyDie() else: return None def _tagMsg(self, msg): """Sets attribute on an incoming IRC message. Will usually only be called by feedMsg, but may be useful in tests as well.""" msg.tag('receivedBy', self) msg.tag('receivedOn', self.network) msg.tag('receivedAt', time.time()) self._setMsgChannel(msg) def _setMsgChannel(self, msg): channel = None if msg.args: channel = msg.args[0] if msg.command in ('NOTICE', 'PRIVMSG') and \ not conf.supybot.protocols.irc.strictRfc(): channel = self.stripChannelPrefix(channel) if not self.isChannel(channel): channel = None msg.channel = channel def stripChannelPrefix(self, channel): statusmsg_chars = self.state.supported.get('statusmsg', '') return channel.lstrip(statusmsg_chars) _numericErrorCommandRe = re.compile(r'^[45][0-9][0-9]$') def feedMsg(self, msg): """Called by the IrcDriver; feeds a message received.""" self._tagMsg(msg) channel = msg.channel preInFilter = str(msg).rstrip('\r\n') log.debug('Incoming message (%s): %s', self.network, preInFilter) # Yeah, so this is odd. Some networks (oftc) seem to give us certain # messages with our nick instead of our prefix. We'll fix that here. if msg.prefix == self.nick: log.debug('Got one of those odd nick-instead-of-prefix msgs.') msg = ircmsgs.IrcMsg(prefix=self.prefix, msg=msg) # This catches cases where we know our own nick (from sending it to the # server) but we don't yet know our prefix. if msg.nick == self.nick and self.prefix != msg.prefix: self.prefix = msg.prefix # This keeps our nick and server attributes updated. if msg.command in self._nickSetters: if msg.args[0] != self.nick: self.nick = msg.args[0] log.debug('Updating nick attribute to %s.', self.nick) if msg.prefix != self.server: self.server = msg.prefix log.debug('Updating server attribute to %s.', self.server) # Dispatch to specific handlers for commands. method = self.dispatchCommand(msg.command, msg.args) if method is not None: method(msg) elif self._numericErrorCommandRe.search(msg.command): log.error('Unhandled error message from server: %r' % msg) # Now update the IrcState object. try: self.state.addMsg(self, msg) except: log.exception('Exception in update of IrcState object:') # Now call the callbacks. world.debugFlush() for callback in self.callbacks: try: m = callback.inFilter(self, msg) if not m: log.debug('%s.inFilter returned None', callback.name()) return msg = m except: log.exception('Uncaught exception in inFilter:') world.debugFlush() postInFilter = str(msg).rstrip('\r\n') if postInFilter != preInFilter: log.debug('Incoming message (post-inFilter): %s', postInFilter) for callback in self.callbacks: try: if callback is not None: callback(self, msg) except: log.exception('Uncaught exception in callback:') world.debugFlush() def die(self): """Makes the Irc object *promise* to die -- but it won't die (of its own volition) until all its queues are clear. Isn't that cool?""" self.zombie = True if not self.afterConnect: self._reallyDie() # This is useless because it's in world.ircs, so it won't be deleted until # the program exits. Just figured you might want to know. #def __del__(self): # self._reallyDie() def reset(self): """Resets the Irc object. Called when the driver reconnects.""" self._setNonResettingVariables() self.state.reset() self.queue.reset() self.fastqueue.reset() self.startedSync.clear() for callback in self.callbacks: callback.reset() self._queueConnectMessages() def _setNonResettingVariables(self): # Configuration stuff. network_config = conf.supybot.networks.get(self.network) def get_value(name): return getattr(network_config, name)() or \ getattr(conf.supybot, name)() self.nick = get_value('nick') # Expand variables like $version in realname. self.user = ircutils.standardSubstitute(self, None, get_value('user')) self.ident = get_value('ident') self.alternateNicks = conf.supybot.nick.alternates()[:] self.triedNicks = ircutils.IrcSet() self.password = network_config.password() self.prefix = '%s!%s@%s' % (self.nick, self.ident, 'unset.domain') # The rest. self.lastTake = 0 self.server = 'unset' self.afterConnect = False self.startedAt = time.time() self.lastping = time.time() self.outstandingPing = False self.capNegociationEnded = False self.requireStarttls = not network_config.ssl() and \ network_config.requireStarttls() if self.requireStarttls: log.error(('STARTTLS is no longer supported. Set ' 'supybot.networks.%s.requireStarttls to False ' 'to disable it, and use supybot.networks.%s.ssl ' 'instead.') % (self.network, self.network)) self.driver.die() self._reallyDie() return self.resetSasl() def resetSasl(self): network_config = conf.supybot.networks.get(self.network) self.sasl_authenticated = False self.sasl_username = network_config.sasl.username() self.sasl_password = network_config.sasl.password() self.sasl_ecdsa_key = network_config.sasl.ecdsa_key() self.sasl_scram_state = {'step': 'uninitialized'} self.authenticate_decoder = None self.sasl_next_mechanisms = [] self.sasl_current_mechanism = None for mechanism in network_config.sasl.mechanisms(): if mechanism == 'ecdsa-nist256p-challenge' and \ crypto and self.sasl_username and \ self.sasl_ecdsa_key: self.sasl_next_mechanisms.append(mechanism) elif mechanism == 'external' and ( network_config.certfile() or conf.supybot.protocols.irc.certfile()): self.sasl_next_mechanisms.append(mechanism) elif mechanism.startswith('scram-') and scram and \ self.sasl_username and self.sasl_password: self.sasl_next_mechanisms.append(mechanism) elif mechanism == 'plain' and \ self.sasl_username and self.sasl_password: self.sasl_next_mechanisms.append(mechanism) if self.sasl_next_mechanisms: self.REQUEST_CAPABILITIES.add('sasl') REQUEST_CAPABILITIES = set(['account-notify', 'extended-join', 'multi-prefix', 'metadata-notify', 'account-tag', 'userhost-in-names', 'invite-notify', 'server-time', 'chghost', 'batch', 'away-notify', 'message-tags', 'msgid']) def _queueConnectMessages(self): if self.zombie: self.driver.die() self._reallyDie() return self.sendMsg(ircmsgs.IrcMsg(command='CAP', args=('LS', '302'))) self.sendAuthenticationMessages() def sendAuthenticationMessages(self): # Notes: # * using sendMsg instead of queueMsg because these messages cannot # be throttled. if self.password: log.info('%s: Queuing PASS command, not logging the password.', self.network) self.sendMsg(ircmsgs.password(self.password)) log.debug('%s: Sending NICK command, nick is %s.', self.network, self.nick) self.sendMsg(ircmsgs.nick(self.nick)) log.debug('%s: Sending USER command, ident is %s, user is %s.', self.network, self.ident, self.user) self.sendMsg(ircmsgs.user(self.ident, self.user)) def endCapabilityNegociation(self): if not self.capNegociationEnded: self.capNegociationEnded = True self.sendMsg(ircmsgs.IrcMsg(command='CAP', args=('END',))) def sendSaslString(self, string): for chunk in ircutils.authenticate_generator(string): self.sendMsg(ircmsgs.IrcMsg(command='AUTHENTICATE', args=(chunk,))) def tryNextSaslMechanism(self): if self.sasl_next_mechanisms: self.sasl_current_mechanism = self.sasl_next_mechanisms.pop(0) self.sendMsg(ircmsgs.IrcMsg(command='AUTHENTICATE', args=(self.sasl_current_mechanism.upper(),))) elif conf.supybot.networks.get(self.network).sasl.required(): log.error('None of the configured SASL mechanisms succeeded, ' 'aborting connection.') else: self.sasl_current_mechanism = None self.endCapabilityNegociation() def filterSaslMechanisms(self, available): available = set(map(str.lower, available)) self.sasl_next_mechanisms = [ x for x in self.sasl_next_mechanisms if x.lower() in available] def doAuthenticate(self, msg): if not self.authenticate_decoder: self.authenticate_decoder = ircutils.AuthenticateDecoder() self.authenticate_decoder.feed(msg) if not self.authenticate_decoder.ready: return # Waiting for other messages string = self.authenticate_decoder.get() self.authenticate_decoder = None mechanism = self.sasl_current_mechanism if mechanism == 'ecdsa-nist256p-challenge': self._doAuthenticateEcdsa(string) elif mechanism == 'external': self.sendSaslString(b'') elif mechanism.startswith('scram-'): step = self.sasl_scram_state['step'] try: if step == 'uninitialized': log.debug('%s: starting SCRAM.', self.network) self._doAuthenticateScramFirst(mechanism) elif step == 'first-sent': log.debug('%s: received SCRAM challenge.', self.network) self._doAuthenticateScramChallenge(string) elif step == 'final-sent': log.debug('%s: finishing SCRAM.', self.network) self._doAuthenticateScramFinish(string) else: assert False except scram.ScramException: self.sendMsg(ircmsgs.IrcMsg(command='AUTHENTICATE', args=('*',))) self.tryNextSaslMechanism() elif mechanism == 'plain': authstring = b'\0'.join([ self.sasl_username.encode('utf-8'), self.sasl_username.encode('utf-8'), self.sasl_password.encode('utf-8'), ]) self.sendSaslString(authstring) def _doAuthenticateEcdsa(self, string): if string == b'': self.sendSaslString(self.sasl_username.encode('utf-8')) return try: with open(self.sasl_ecdsa_key, 'rb') as fd: private_key = crypto.load_pem_private_key( fd.read(),password=None, backend=crypto.default_backend()) authstring = private_key.sign( string, crypto.ECDSA(crypto.Prehashed(crypto.SHA256()))) self.sendSaslString(authstring) except (OSError, ValueError): self.sendMsg(ircmsgs.IrcMsg(command='AUTHENTICATE', args=('*',))) self.tryNextSaslMechanism() def _doAuthenticateScramFirst(self, mechanism): """Handle sending the client-first message of SCRAM auth.""" hash_name = mechanism[len('scram-'):] if hash_name.endswith('-plus'): hash_name = hash_name[:-len('-plus')] hash_name = hash_name.upper() if hash_name not in scram.HASH_FACTORIES: log.debug('%s: SCRAM hash %r not supported, aborting.', self.network, hash_name) self.tryNextSaslMechanism() return authenticator = scram.SCRAMClientAuthenticator(hash_name, channel_binding=False) self.sasl_scram_state['authenticator'] = authenticator client_first = authenticator.start({ 'username': self.sasl_username, 'password': self.sasl_password, }) self.sendSaslString(client_first) self.sasl_scram_state['step'] = 'first-sent' def _doAuthenticateScramChallenge(self, challenge): client_final = self.sasl_scram_state['authenticator'] \ .challenge(challenge) self.sendSaslString(client_final) self.sasl_scram_state['step'] = 'final-sent' def _doAuthenticateScramFinish(self, data): try: res = self.sasl_scram_state['authenticator'] \ .finish(data) except scram.BadSuccessException as e: log.warning('%s: SASL authentication failed with SCRAM error: %e', self.network, e) self.tryNextSaslMechanism() else: self.sendSaslString(b'') self.sasl_scram_state['step'] = 'authenticated' def do903(self, msg): log.info('%s: SASL authentication successful', self.network) self.sasl_authenticated = True self.endCapabilityNegociation() def do904(self, msg): log.warning('%s: SASL authentication failed (mechanism: %s)', self.network, self.sasl_current_mechanism) self.tryNextSaslMechanism() def do905(self, msg): log.warning('%s: SASL authentication failed because the username or ' 'password is too long.', self.network) self.tryNextSaslMechanism() def do906(self, msg): log.warning('%s: SASL authentication aborted', self.network) self.tryNextSaslMechanism() def do907(self, msg): log.warning('%s: Attempted SASL authentication when we were already ' 'authenticated.', self.network) self.tryNextSaslMechanism() def do908(self, msg): log.info('%s: Supported SASL mechanisms: %s', self.network, msg.args[1]) self.filterSaslMechanisms(set(msg.args[1].split(','))) def doCapAck(self, msg): if len(msg.args) != 3: log.warning('Bad CAP ACK from server: %r', msg) return caps = msg.args[2].split() assert caps, 'Empty list of capabilities' log.debug('%s: Server acknowledged capabilities: %L', self.network, caps) self.state.capabilities_ack.update(caps) if 'sasl' in caps: self.tryNextSaslMechanism() else: self.endCapabilityNegociation() def doCapNak(self, msg): if len(msg.args) != 3: log.warning('Bad CAP NAK from server: %r', msg) return caps = msg.args[2].split() assert caps, 'Empty list of capabilities' self.state.capabilities_nak.update(caps) log.warning('%s: Server refused capabilities: %L', self.network, caps) self.endCapabilityNegociation() def _addCapabilities(self, capstring): for item in capstring.split(): while item.startswith(('=', '~')): item = item[1:] if '=' in item: (cap, value) = item.split('=', 1) self.state.capabilities_ls[cap] = value else: self.state.capabilities_ls[item] = None def doCapLs(self, msg): if len(msg.args) == 4: # Multi-line LS if msg.args[2] != '*': log.warning('Bad CAP LS from server: %r', msg) return self._addCapabilities(msg.args[3]) elif len(msg.args) == 3: # End of LS self._addCapabilities(msg.args[2]) if 'sasl' in self.state.capabilities_ls: s = self.state.capabilities_ls['sasl'] if s is not None: self.filterSaslMechanisms(set(s.split(','))) # Normally at this point, self.state.capabilities_ack should be # empty; but let's just make sure we're not requesting the same # caps twice for no reason. new_caps = ( set(self.state.capabilities_ls) & self.REQUEST_CAPABILITIES - self.state.capabilities_ack) # NOTE: Capabilities are requested in alphabetic order, because # sets are unordered, and their "order" is nondeterministic. # This is needed for the tests. if new_caps: self._requestCaps(new_caps) else: self.endCapabilityNegociation() else: log.warning('Bad CAP LS from server: %r', msg) return def doCapDel(self, msg): if len(msg.args) != 3: log.warning('Bad CAP DEL from server: %r', msg) return caps = msg.args[2].split() assert caps, 'Empty list of capabilities' for cap in caps: # The spec says "If capability negotiation 3.2 was used, extensions # listed MAY contain values." for CAP NEW and CAP DEL cap = cap.split('=')[0] try: del self.state.capabilities_ls[cap] except KeyError: pass try: self.state.capabilities_ack.remove(cap) except KeyError: pass def doCapNew(self, msg): if len(msg.args) != 3: log.warning('Bad CAP NEW from server: %r', msg) return caps = msg.args[2].split() assert caps, 'Empty list of capabilities' self._addCapabilities(msg.args[2]) if not self.sasl_authenticated and 'sasl' in self.state.capabilities_ls: self.resetSasl() s = self.state.capabilities_ls['sasl'] if s is not None: self.filterSaslMechanisms(set(s.split(','))) common_supported_unrequested_capabilities = ( set(self.state.capabilities_ls) & self.REQUEST_CAPABILITIES - self.state.capabilities_ack) if common_supported_unrequested_capabilities: self._requestCaps(common_supported_unrequested_capabilities) def _requestCaps(self, caps): caps = ' '.join(sorted(caps)) # textwrap works here because in ASCII, all chars are 1 bytes: cap_lines = textwrap.wrap(caps, MAX_LINE_SIZE-len('CAP REQ :')) for cap_line in cap_lines: self.sendMsg(ircmsgs.IrcMsg(command='CAP', args=('REQ', cap_line))) def monitor(self, targets): """Increment a counter of how many callbacks monitor each target; and send a MONITOR + to the server if the target is not yet monitored.""" if isinstance(targets, str): targets = [targets] not_yet_monitored = set() for target in targets: if target in self.monitoring: self.monitoring[target] += 1 else: not_yet_monitored.add(target) self.monitoring[target] = 1 if not_yet_monitored: self.queueMsg(ircmsgs.monitor('+', not_yet_monitored)) return not_yet_monitored def unmonitor(self, targets): """Decrements a counter of how many callbacks monitor each target; and send a MONITOR - to the server if the counter drops to 0.""" if isinstance(targets, str): targets = [targets] should_be_unmonitored = set() for target in targets: self.monitoring[target] -= 1 if self.monitoring[target] == 0: del self.monitoring[target] should_be_unmonitored.add(target) if should_be_unmonitored: self.queueMsg(ircmsgs.monitor('-', should_be_unmonitored)) return should_be_unmonitored def _getNextNick(self): if self.alternateNicks: nick = self.alternateNicks.pop(0) if '%s' in nick: network_nick = conf.supybot.networks.get(self.network).nick() if network_nick == '': nick %= conf.supybot.nick() else: nick %= network_nick if nick not in self.triedNicks: self.triedNicks.add(nick) return nick nick = conf.supybot.nick() network_nick = conf.supybot.networks.get(self.network).nick() if network_nick != '': nick = network_nick ret = nick L = list(nick) while len(L) <= 3: L.append('`') while ret in self.triedNicks: L[random.randrange(len(L))] = utils.iter.choice('0123456789') ret = ''.join(L) self.triedNicks.add(ret) return ret def do002(self, msg): """Logs the ircd version.""" (beginning, version) = rsplit(msg.args[-1], maxsplit=1) log.info('Server %s has version %s', self.server, version) def doPing(self, msg): """Handles PING messages.""" self.sendMsg(ircmsgs.pong(msg.args[0])) def doPong(self, msg): """Handles PONG messages.""" self.outstandingPing = False def do376(self, msg): log.info('Got end of MOTD from %s', self.server) self.afterConnect = True # Let's reset nicks in case we had to use a weird one. self.alternateNicks = conf.supybot.nick.alternates()[:] umodes = conf.supybot.networks.get(self.network).umodes() if umodes == '': umodes = conf.supybot.protocols.irc.umodes() supported = self.state.supported.get('umodes') if supported: acceptedchars = supported.union('+-') umodes = ''.join([m for m in umodes if m in acceptedchars]) if umodes: log.info('Sending user modes to %s: %s', self.network, umodes) self.sendMsg(ircmsgs.mode(self.nick, umodes)) do377 = do422 = do376 def do43x(self, msg, problem): if not self.afterConnect: newNick = self._getNextNick() assert newNick != self.nick log.info('Got %s: %s %s. Trying %s.', msg.command, self.nick, problem, newNick) self.sendMsg(ircmsgs.nick(newNick)) def do437(self, msg): self.do43x(msg, 'is temporarily unavailable') def do433(self, msg): self.do43x(msg, 'is in use') def do432(self, msg): self.do43x(msg, 'is not a valid nickname') def doJoin(self, msg): if msg.nick == self.nick: channel = msg.args[0] self.queueMsg(ircmsgs.who(channel, args=('%tuhnairf,1',))) # Ends with 315. self.queueMsg(ircmsgs.mode(channel)) # Ends with 329. for channel in msg.args[0].split(','): self.queueMsg(ircmsgs.mode(channel, '+b')) self.startedSync[channel] = time.time() def do315(self, msg): channel = msg.args[1] if channel in self.startedSync: now = time.time() started = self.startedSync.pop(channel) elapsed = now - started log.info('Join to %s on %s synced in %.2f seconds.', channel, self.network, elapsed) def doError(self, msg): """Handles ERROR messages.""" log.warning('Error message from %s: %s', self.network, msg.args[0]) if not self.zombie: if msg.args[0].lower().startswith('closing link'): self.driver.reconnect() elif 'too fast' in msg.args[0]: # Connecting too fast. self.driver.reconnect(wait=True) def doNick(self, msg): """Handles NICK messages.""" if msg.nick == self.nick: newNick = msg.args[0] self.nick = newNick (nick, user, domain) = ircutils.splitHostmask(msg.prefix) self.prefix = ircutils.joinHostmask(self.nick, user, domain) elif conf.supybot.followIdentificationThroughNickChanges(): # We use elif here because this means it's someone else's nick # change, not our own. try: id = ircdb.users.getUserId(msg.prefix) u = ircdb.users.getUser(id) except KeyError: return if u.auth: (_, user, host) = ircutils.splitHostmask(msg.prefix) newhostmask = ircutils.joinHostmask(msg.args[0], user, host) for (i, (when, authmask)) in enumerate(u.auth[:]): if ircutils.strEqual(msg.prefix, authmask): log.info('Following identification for %s: %s -> %s', u.name, authmask, newhostmask) u.auth[i] = (u.auth[i][0], newhostmask) ircdb.users.setUser(u) def _reallyDie(self): """Makes the Irc object die. Dead.""" log.info('Irc object for %s dying.', self.network) # XXX This hasattr should be removed, I'm just putting it here because # we're so close to a release. After 0.80.0 we should remove this # and fix whatever AttributeErrors arise in the drivers themselves. if self.driver is not None and hasattr(self.driver, 'die'): self.driver.die() if self in world.ircs: world.ircs.remove(self) # Only kill the callbacks if we're the last Irc. if not world.ircs: for cb in self.callbacks: cb.die() # If we shared our list of callbacks, this ensures that # cb.die() is only called once for each callback. It's # not really necessary since we already check to make sure # we're the only Irc object, but a little robustitude never # hurt anybody. log.debug('Last Irc, clearing callbacks.') self.callbacks[:] = [] else: log.warning('Irc object killed twice: %s', utils.stackTrace()) def __hash__(self): return id(self) def __eq__(self, other): # We check isinstance here, so that if some proxy object (like those # defined in callbacks.py) has overridden __eq__, it takes precedence. if isinstance(other, self.__class__): return id(self) == id(other) else: return other.__eq__(self) def __ne__(self, other): return not (self == other) def __str__(self): return 'Irc object for %s' % self.network def __repr__(self): return '' % self.network # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/ircmsgs.py0000644000175000017500000010470213634634532016133 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2010, James McCoy # 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. ### """ This module provides the basic IrcMsg object used throughout the bot to represent the actual messages. It also provides several helper functions to construct such messages in an easier way than the constructor for the IrcMsg object (which, as you'll read later, is quite...full-featured :)) """ import re import time import base64 import datetime import warnings import functools from . import conf, ircutils, utils from .utils.iter import all from .utils import minisix ### # IrcMsg class -- used for representing IRC messages acquired from a network. ### class MalformedIrcMsg(ValueError): pass # http://ircv3.net/specs/core/message-tags-3.2.html#escaping-values SERVER_TAG_ESCAPE = [ ('\\', '\\\\'), # \ -> \\ (' ', r'\s'), (';', r'\:'), ('\r', r'\r'), ('\n', r'\n'), ] escape_server_tag_value = utils.str.MultipleReplacer( dict(SERVER_TAG_ESCAPE)) unescape_server_tag_value = utils.str.MultipleReplacer( dict(map(lambda x:(x[1],x[0]), SERVER_TAG_ESCAPE))) def parse_server_tags(s): server_tags = {} for tag in s.split(';'): if '=' not in tag: server_tags[tag] = None else: (key, value) = tag.split('=', 1) value = unescape_server_tag_value(value) if value == '': # "Implementations MUST interpret empty tag values (e.g. foo=) # as equivalent to missing tag values (e.g. foo)." value = None server_tags[key] = value return server_tags def format_server_tags(server_tags): parts = [] for (key, value) in server_tags.items(): if value is None: parts.append(key) else: parts.append('%s=%s' % (key, escape_server_tag_value(value))) return '@' + ';'.join(parts) class IrcMsg(object): """Class to represent an IRC message. As usual, ignore attributes that begin with an underscore. They simply don't exist. Instances of this class are *not* to be modified, since they are hashable. Public attributes of this class are .prefix, .command, .args, .nick, .user, and .host. The constructor for this class is pretty intricate. It's designed to take any of three major (sets of) arguments. Called with no keyword arguments, it takes a single string that is a raw IRC message (such as one taken straight from the network). Called with keyword arguments, it *requires* a command parameter. Args is optional, but with most commands will be necessary. Prefix is obviously optional, since clients aren't allowed (well, technically, they are, but only in a completely useless way) to send prefixes to the server. Since this class isn't to be modified, the constructor also accepts a 'msg' keyword argument representing a message from which to take all the attributes not provided otherwise as keyword arguments. So, for instance, if a programmer wanted to take a PRIVMSG they'd gotten and simply redirect it to a different source, they could do this: IrcMsg(prefix='', args=(newSource, otherMsg.args[1]), msg=otherMsg) """ # It's too useful to be able to tag IrcMsg objects with extra, unforeseen # data. Goodbye, __slots__. # On second thought, let's use methods for tagging. __slots__ = ('args', 'command', 'host', 'nick', 'prefix', 'user', '_hash', '_str', '_repr', '_len', 'tags', 'reply_env', 'server_tags', 'time', 'channel') def __init__(self, s='', command='', args=(), prefix='', msg=None, reply_env=None): assert not (msg and s), 'IrcMsg.__init__ cannot accept both s and msg' if not s and not command and not msg: raise MalformedIrcMsg('IRC messages require a command.') self._str = None self._repr = None self._hash = None self._len = None self.reply_env = reply_env self.tags = {} if s: originalString = s try: if not s.endswith('\n'): s += '\n' self._str = s if s[0] == '@': (server_tags, s) = s.split(' ', 1) self.server_tags = parse_server_tags(server_tags[1:]) else: self.server_tags = {} if s[0] == ':': self.prefix, s = s[1:].split(None, 1) else: self.prefix = '' if ' :' in s: # Note the space: IPV6 addresses are bad w/o it. s, last = s.split(' :', 1) self.args = s.split() self.args.append(last.rstrip('\r\n')) else: self.args = s.split() self.command = self.args.pop(0) if 'time' in self.server_tags: s = self.server_tags['time'] date = datetime.datetime.strptime(s, '%Y-%m-%dT%H:%M:%S.%fZ') date = minisix.make_datetime_utc(date) self.time = minisix.datetime__timestamp(date) else: self.time = time.time() except (IndexError, ValueError): raise MalformedIrcMsg(repr(originalString)) else: if msg is not None: if prefix: self.prefix = prefix else: self.prefix = msg.prefix if command: self.command = command else: self.command = msg.command if args: self.args = args else: self.args = msg.args if reply_env: self.reply_env = reply_env elif msg.reply_env: self.reply_env = msg.reply_env.copy() else: self.reply_env = None self.tags = msg.tags.copy() self.server_tags = msg.server_tags self.time = msg.time else: self.prefix = prefix self.command = command assert all(ircutils.isValidArgument, args), args self.args = args self.time = None self.server_tags = {} self.args = tuple(self.args) if isUserHostmask(self.prefix): (self.nick,self.user,self.host)=ircutils.splitHostmask(self.prefix) else: (self.nick, self.user, self.host) = (self.prefix,)*3 def __str__(self): if self._str is not None: return self._str if self.prefix: if len(self.args) > 1: self._str = ':%s %s %s :%s\r\n' % \ (self.prefix, self.command, ' '.join(self.args[:-1]), self.args[-1]) else: if self.args: self._str = ':%s %s :%s\r\n' % \ (self.prefix, self.command, self.args[0]) else: self._str = ':%s %s\r\n' % (self.prefix, self.command) else: if len(self.args) > 1: self._str = '%s %s :%s\r\n' % \ (self.command, ' '.join(self.args[:-1]), self.args[-1]) else: if self.args: self._str = '%s :%s\r\n' % (self.command, self.args[0]) else: self._str = '%s\r\n' % self.command return self._str def __len__(self): return len(str(self)) def __eq__(self, other): return isinstance(other, self.__class__) and \ hash(self) == hash(other) and \ self.command == other.command and \ self.prefix == other.prefix and \ self.args == other.args __req__ = __eq__ # I don't know exactly what this does, but it can't hurt. def __ne__(self, other): return not (self == other) __rne__ = __ne__ # Likewise as above. def __hash__(self): if self._hash is not None: return self._hash self._hash = hash(self.command) ^ \ hash(self.prefix) ^ \ hash(repr(self.args)) return self._hash def __repr__(self): if self._repr is not None: return self._repr self._repr = format('IrcMsg(prefix=%q, command=%q, args=%r)', self.prefix, self.command, self.args) return self._repr def __reduce__(self): return (self.__class__, (str(self),)) def tag(self, tag, value=True): """Affect a key:value pair to this message.""" self.tags[tag] = value def tagged(self, tag): """Get the value affected to a tag.""" return self.tags.get(tag) # Returns None if it's not there. def __getattr__(self, attr): if attr.startswith('__'): # Since PEP 487, Python calls __set_name__ raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, attr)) if attr in self.tags: warnings.warn("msg. is deprecated. Use " "msg.tagged('') or msg.tags['']" "instead.", DeprecationWarning) return self.tags[attr] else: # TODO: make this raise AttributeError return None def isCtcp(msg): """Returns whether or not msg is a CTCP message.""" return msg.command in ('PRIVMSG', 'NOTICE') and \ msg.args[1].startswith('\x01') and \ msg.args[1].endswith('\x01') and \ len(msg.args[1]) >= 2 def isAction(msg): """A predicate returning true if the PRIVMSG in question is an ACTION""" if isCtcp(msg): s = msg.args[1] payload = s[1:-1] # Chop off \x01. command = payload.split(None, 1)[0] return command == 'ACTION' else: return False def isSplit(msg): if msg.command == 'QUIT': # It's a quit. quitmsg = msg.args[0] if not quitmsg.startswith('"') and not quitmsg.endswith('"'): # It's not a user-generated quitmsg. servers = quitmsg.split() if len(servers) == 2: # We could check if domains match, or if the hostnames actually # resolve, but we're going to put that off for now. return True return False _unactionre = re.compile(r'^\x01ACTION\s+(.*)\x01$') def unAction(msg): """Returns the payload (i.e., non-ACTION text) of an ACTION msg.""" assert isAction(msg) return _unactionre.match(msg.args[1]).group(1) def _escape(s): s = s.replace('&', '&') s = s.replace('"', '"') s = s.replace('<', '<') s = s.replace('>', '>') return s def toXml(msg, pretty=True, includeTime=True): assert msg.command == _escape(msg.command) L = [] L.append('') if pretty: L.append('\n') for arg in msg.args: if pretty: L.append(' ') L.append('%s' % _escape(arg)) if pretty: L.append('\n') L.append('\n') return ''.join(L) def prettyPrint(msg, addRecipients=False, timestampFormat=None, showNick=True): """Provides a client-friendly string form for messages. IIRC, I copied BitchX's (or was it XChat's?) format for messages. """ def nickorprefix(): return msg.nick or msg.prefix def nick(): if addRecipients: return '%s/%s' % (msg.nick, msg.args[0]) else: return msg.nick if msg.command == 'PRIVMSG': m = _unactionre.match(msg.args[1]) if m: s = '* %s %s' % (nick(), m.group(1)) else: if not showNick: s = '%s' % msg.args[1] else: s = '<%s> %s' % (nick(), msg.args[1]) elif msg.command == 'NOTICE': if not showNick: s = '%s' % msg.args[1] else: s = '-%s- %s' % (nick(), msg.args[1]) elif msg.command == 'JOIN': prefix = msg.prefix if msg.nick: prefix = '%s <%s>' % (msg.nick, prefix) s = '*** %s has joined %s' % (prefix, msg.args[0]) elif msg.command == 'PART': if len(msg.args) > 1: partmsg = ' (%s)' % msg.args[1] else: partmsg = '' s = '*** %s <%s> has parted %s%s' % (msg.nick, msg.prefix, msg.args[0], partmsg) elif msg.command == 'KICK': if len(msg.args) > 2: kickmsg = ' (%s)' % msg.args[1] else: kickmsg = '' s = '*** %s was kicked by %s%s' % (msg.args[1], msg.nick, kickmsg) elif msg.command == 'MODE': s = '*** %s sets mode: %s' % (nickorprefix(), ' '.join(msg.args)) elif msg.command == 'QUIT': if msg.args: quitmsg = ' (%s)' % msg.args[0] else: quitmsg = '' s = '*** %s <%s> has quit IRC%s' % (msg.nick, msg.prefix, quitmsg) elif msg.command == 'TOPIC': s = '*** %s changes topic to %s' % (nickorprefix(), msg.args[1]) elif msg.command == 'NICK': s = '*** %s is now known as %s' % (msg.nick, msg.args[0]) else: s = utils.str.format('--- Unknown command %q', ' '.join(msg.args)) at = msg.tagged('receivedAt') if timestampFormat and at: s = '%s %s' % (time.strftime(timestampFormat, time.localtime(at)), s) return s ### # Various IrcMsg functions ### isNick = ircutils.isNick areNicks = ircutils.areNicks isChannel = ircutils.isChannel areChannels = ircutils.areChannels areReceivers = ircutils.areReceivers isUserHostmask = ircutils.isUserHostmask def pong(payload, prefix='', msg=None): """Takes a payload and returns the proper PONG IrcMsg.""" if conf.supybot.protocols.irc.strictRfc(): assert payload, 'PONG requires a payload' if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='PONG', args=(payload,), msg=msg) def ping(payload, prefix='', msg=None): """Takes a payload and returns the proper PING IrcMsg.""" if conf.supybot.protocols.irc.strictRfc(): assert payload, 'PING requires a payload' if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='PING', args=(payload,), msg=msg) def op(channel, nick, prefix='', msg=None): """Returns a MODE to op nick on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert isNick(nick), repr(nick) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', args=(channel, '+o', nick), msg=msg) def ops(channel, nicks, prefix='', msg=None): """Returns a MODE to op each of nicks on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert nicks, 'Nicks must not be empty.' assert all(isNick, nicks), nicks if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', args=(channel, '+' + ('o'*len(nicks))) + tuple(nicks), msg=msg) def deop(channel, nick, prefix='', msg=None): """Returns a MODE to deop nick on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert isNick(nick), repr(nick) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', args=(channel, '-o', nick), msg=msg) def deops(channel, nicks, prefix='', msg=None): """Returns a MODE to deop each of nicks on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert nicks, 'Nicks must not be empty.' assert all(isNick, nicks), nicks if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', msg=msg, args=(channel, '-' + ('o'*len(nicks))) + tuple(nicks)) def halfop(channel, nick, prefix='', msg=None): """Returns a MODE to halfop nick on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert isNick(nick), repr(nick) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', args=(channel, '+h', nick), msg=msg) def halfops(channel, nicks, prefix='', msg=None): """Returns a MODE to halfop each of nicks on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert nicks, 'Nicks must not be empty.' assert all(isNick, nicks), nicks if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', msg=msg, args=(channel, '+' + ('h'*len(nicks))) + tuple(nicks)) def dehalfop(channel, nick, prefix='', msg=None): """Returns a MODE to dehalfop nick on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert isNick(nick), repr(nick) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', args=(channel, '-h', nick), msg=msg) def dehalfops(channel, nicks, prefix='', msg=None): """Returns a MODE to dehalfop each of nicks on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert nicks, 'Nicks must not be empty.' assert all(isNick, nicks), nicks if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', msg=msg, args=(channel, '-' + ('h'*len(nicks))) + tuple(nicks)) def voice(channel, nick, prefix='', msg=None): """Returns a MODE to voice nick on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert isNick(nick), repr(nick) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', args=(channel, '+v', nick), msg=msg) def voices(channel, nicks, prefix='', msg=None): """Returns a MODE to voice each of nicks on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert nicks, 'Nicks must not be empty.' assert all(isNick, nicks) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', msg=msg, args=(channel, '+' + ('v'*len(nicks))) + tuple(nicks)) def devoice(channel, nick, prefix='', msg=None): """Returns a MODE to devoice nick on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert isNick(nick), repr(nick) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', args=(channel, '-v', nick), msg=msg) def devoices(channel, nicks, prefix='', msg=None): """Returns a MODE to devoice each of nicks on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert nicks, 'Nicks must not be empty.' assert all(isNick, nicks), nicks if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', msg=msg, args=(channel, '-' + ('v'*len(nicks))) + tuple(nicks)) def ban(channel, hostmask, exception='', prefix='', msg=None): """Returns a MODE to ban nick on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert isUserHostmask(hostmask), repr(hostmask) modes = [('+b', hostmask)] if exception: modes.append(('+e', exception)) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', args=[channel] + ircutils.joinModes(modes), msg=msg) def bans(channel, hostmasks, exceptions=(), prefix='', msg=None): """Returns a MODE to ban each of nicks on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert all(isUserHostmask, hostmasks), hostmasks modes = [('+b', s) for s in hostmasks] + [('+e', s) for s in exceptions] if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', args=[channel] + ircutils.joinModes(modes), msg=msg) def unban(channel, hostmask, prefix='', msg=None): """Returns a MODE to unban nick on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert isUserHostmask(hostmask), repr(hostmask) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', args=(channel, '-b', hostmask), msg=msg) def unbans(channel, hostmasks, prefix='', msg=None): """Returns a MODE to unban each of nicks on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert all(isUserHostmask, hostmasks), hostmasks modes = [('-b', s) for s in hostmasks] if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', args=[channel] + ircutils.joinModes(modes), msg=msg) def kick(channel, nick, s='', prefix='', msg=None): """Returns a KICK to kick nick from channel with the message msg.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert isNick(nick), repr(nick) if msg and not prefix: prefix = msg.prefix if minisix.PY2 and isinstance(s, unicode): s = s.encode('utf8') assert isinstance(s, str) if s: return IrcMsg(prefix=prefix, command='KICK', args=(channel, nick, s), msg=msg) else: return IrcMsg(prefix=prefix, command='KICK', args=(channel, nick), msg=msg) def kicks(channels, nicks, s='', prefix='', msg=None): """Returns a KICK to kick each of nicks from channel with the message msg. """ if isinstance(channels, str): # Backward compatibility channels = [channels] if conf.supybot.protocols.irc.strictRfc(): assert areChannels(channels), repr(channels) assert areNicks(nicks), repr(nicks) if msg and not prefix: prefix = msg.prefix if minisix.PY2 and isinstance(s, unicode): s = s.encode('utf8') assert isinstance(s, str) if s: for channel in channels: return IrcMsg(prefix=prefix, command='KICK', args=(channel, ','.join(nicks), s), msg=msg) else: for channel in channels: return IrcMsg(prefix=prefix, command='KICK', args=(channel, ','.join(nicks)), msg=msg) def privmsg(recipient, s, prefix='', msg=None): """Returns a PRIVMSG to recipient with the message msg.""" if conf.supybot.protocols.irc.strictRfc(): assert (areReceivers(recipient)), repr(recipient) assert s, 's must not be empty.' if minisix.PY2 and isinstance(s, unicode): s = s.encode('utf8') assert isinstance(s, str) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='PRIVMSG', args=(recipient, s), msg=msg) def dcc(recipient, kind, *args, **kwargs): # Stupid Python won't allow (recipient, kind, *args, prefix=''), so we have # to use the **kwargs form. Blech. assert isNick(recipient), 'Can\'t DCC a channel.' kind = kind.upper() assert kind in ('SEND', 'CHAT', 'RESUME', 'ACCEPT'), 'Invalid DCC command.' args = (kind,) + args return IrcMsg(prefix=kwargs.get('prefix', ''), command='PRIVMSG', args=(recipient, ' '.join(args))) def action(recipient, s, prefix='', msg=None): """Returns a PRIVMSG ACTION to recipient with the message msg.""" if conf.supybot.protocols.irc.strictRfc(): assert (isChannel(recipient) or isNick(recipient)), repr(recipient) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='PRIVMSG', args=(recipient, '\x01ACTION %s\x01' % s), msg=msg) def notice(recipient, s, prefix='', msg=None): """Returns a NOTICE to recipient with the message msg.""" if conf.supybot.protocols.irc.strictRfc(): assert areReceivers(recipient), repr(recipient) assert s, 'msg must not be empty.' if minisix.PY2 and isinstance(s, unicode): s = s.encode('utf8') assert isinstance(s, str) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='NOTICE', args=(recipient, s), msg=msg) def join(channel, key=None, prefix='', msg=None): """Returns a JOIN to a channel""" if conf.supybot.protocols.irc.strictRfc(): assert areChannels(channel), repr(channel) if msg and not prefix: prefix = msg.prefix if key is None: return IrcMsg(prefix=prefix, command='JOIN', args=(channel,), msg=msg) else: if conf.supybot.protocols.irc.strictRfc(): chars = '\x00\r\n\f\t\v ' assert not any([(ord(x) >= 128 or x in chars) for x in key]) return IrcMsg(prefix=prefix, command='JOIN', args=(channel, key), msg=msg) def joins(channels, keys=None, prefix='', msg=None): """Returns a JOIN to each of channels.""" if conf.supybot.protocols.irc.strictRfc(): assert all(isChannel, channels), channels if msg and not prefix: prefix = msg.prefix if keys is None: keys = [] assert len(keys) <= len(channels), 'Got more keys than channels.' if not keys: return IrcMsg(prefix=prefix, command='JOIN', args=(','.join(channels),), msg=msg) else: if conf.supybot.protocols.irc.strictRfc(): chars = '\x00\r\n\f\t\v ' for key in keys: assert not any([(ord(x) >= 128 or x in chars) for x in key]) return IrcMsg(prefix=prefix, command='JOIN', args=(','.join(channels), ','.join(keys)), msg=msg) def part(channel, s='', prefix='', msg=None): """Returns a PART from channel with the message msg.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) if msg and not prefix: prefix = msg.prefix if minisix.PY2 and isinstance(s, unicode): s = s.encode('utf8') assert isinstance(s, str) if s: return IrcMsg(prefix=prefix, command='PART', args=(channel, s), msg=msg) else: return IrcMsg(prefix=prefix, command='PART', args=(channel,), msg=msg) def parts(channels, s='', prefix='', msg=None): """Returns a PART from each of channels with the message msg.""" if conf.supybot.protocols.irc.strictRfc(): assert all(isChannel, channels), channels if msg and not prefix: prefix = msg.prefix if minisix.PY2 and isinstance(s, unicode): s = s.encode('utf8') assert isinstance(s, str) if s: return IrcMsg(prefix=prefix, command='PART', args=(','.join(channels), s), msg=msg) else: return IrcMsg(prefix=prefix, command='PART', args=(','.join(channels),), msg=msg) def quit(s='', prefix='', msg=None): """Returns a QUIT with the message msg.""" if msg and not prefix: prefix = msg.prefix if s: return IrcMsg(prefix=prefix, command='QUIT', args=(s,), msg=msg) else: return IrcMsg(prefix=prefix, command='QUIT', msg=msg) def topic(channel, topic=None, prefix='', msg=None): """Returns a TOPIC for channel with the topic topic.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) if msg and not prefix: prefix = msg.prefix if topic is None: return IrcMsg(prefix=prefix, command='TOPIC', args=(channel,), msg=msg) else: if minisix.PY2 and isinstance(topic, unicode): topic = topic.encode('utf8') assert isinstance(topic, str) return IrcMsg(prefix=prefix, command='TOPIC', args=(channel, topic), msg=msg) def nick(nick, prefix='', msg=None): """Returns a NICK with nick nick.""" if conf.supybot.protocols.irc.strictRfc(): assert isNick(nick), repr(nick) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='NICK', args=(nick,), msg=msg) def user(ident, user, prefix='', msg=None): """Returns a USER with ident ident and user user.""" if conf.supybot.protocols.irc.strictRfc(): assert '\x00' not in ident and \ '\r' not in ident and \ '\n' not in ident and \ ' ' not in ident and \ '@' not in ident if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='USER', args=(ident, '0', '*', user), msg=msg) def who(hostmaskOrChannel, prefix='', msg=None, args=()): """Returns a WHO for the hostmask or channel hostmaskOrChannel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(hostmaskOrChannel) or \ isUserHostmask(hostmaskOrChannel), repr(hostmaskOrChannel) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='WHO', args=(hostmaskOrChannel,) + args, msg=msg) def _whois(COMMAND, nick, mask='', prefix='', msg=None): """Returns a WHOIS for nick.""" if conf.supybot.protocols.irc.strictRfc(): assert areNicks(nick), repr(nick) if msg and not prefix: prefix = msg.prefix args = (nick,) if mask: args = (nick, mask) return IrcMsg(prefix=prefix, command=COMMAND, args=args, msg=msg) whois = functools.partial(_whois, 'WHOIS') whowas = functools.partial(_whois, 'WHOWAS') def names(channel=None, prefix='', msg=None): if conf.supybot.protocols.irc.strictRfc(): assert areChannels(channel) if msg and not prefix: prefix = msg.prefix if channel is not None: return IrcMsg(prefix=prefix, command='NAMES', args=(channel,), msg=msg) else: return IrcMsg(prefix=prefix, command='NAMES', msg=msg) def mode(channel, args=(), prefix='', msg=None): if msg and not prefix: prefix = msg.prefix if isinstance(args, minisix.string_types): args = (args,) else: args = tuple(map(str, args)) return IrcMsg(prefix=prefix, command='MODE', args=(channel,)+args, msg=msg) def modes(channel, args=(), prefix='', msg=None): """Returns a MODE message for the channel for all the (mode, targetOrNone) 2-tuples in 'args'.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) modes = args if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', args=[channel] + ircutils.joinModes(modes), msg=msg) def limit(channel, limit, prefix='', msg=None): return mode(channel, ['+l', limit], prefix=prefix, msg=msg) def unlimit(channel, limit, prefix='', msg=None): return mode(channel, ['-l', limit], prefix=prefix, msg=msg) def invite(nick, channel, prefix='', msg=None): """Returns an INVITE for nick.""" if conf.supybot.protocols.irc.strictRfc(): assert isNick(nick), repr(nick) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='INVITE', args=(nick, channel), msg=msg) def password(password, prefix='', msg=None): """Returns a PASS command for accessing a server.""" if conf.supybot.protocols.irc.strictRfc(): assert password, 'password must not be empty.' if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='PASS', args=(password,), msg=msg) def ison(nick, prefix='', msg=None): if conf.supybot.protocols.irc.strictRfc(): assert isNick(nick), repr(nick) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='ISON', args=(nick,), msg=msg) def monitor(subcommand, nicks=None, prefix='', msg=None): if conf.supybot.protocols.irc.strictRfc(): for nick in nicks: assert isNick(nick), repr(nick) assert subcommand in '+-CLS' if subcommand in 'CLS': assert nicks is None if msg and not prefix: prefix = msg.prefix if not isinstance(nicks, str): nicks = ','.join(nicks) return IrcMsg(prefix=prefix, command='MONITOR', args=(subcommand, nicks), msg=msg) def error(s, msg=None): return IrcMsg(command='ERROR', args=(s,), msg=msg) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/ircutils.py0000644000175000017500000011100713634634532016316 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009,2011,2015 James McCoy # 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. ### """ Provides a great number of useful utility functions for IRC. Things to muck around with hostmasks, set bold or color on strings, IRC-case-insensitive dicts, a nick class to handle nicks (so comparisons and hashing and whatnot work in an IRC-case-insensitive fashion), and numerous other things. """ from __future__ import division from __future__ import print_function import re import sys import time import base64 import random import string import textwrap import functools from . import utils from .utils import minisix from .version import version from .i18n import PluginInternationalization _ = PluginInternationalization() def debug(s, *args): """Prints a debug string. Most likely replaced by our logging debug.""" print('***', s % args) userHostmaskRe = re.compile(r'^\S+!\S+@\S+$') def isUserHostmask(s): """Returns whether or not the string s is a valid User hostmask.""" return userHostmaskRe.match(s) is not None def isServerHostmask(s): """s => bool Returns True if s is a valid server hostmask.""" return not isUserHostmask(s) def nickFromHostmask(hostmask): """hostmask => nick Returns the nick from a user hostmask.""" assert isUserHostmask(hostmask) return splitHostmask(hostmask)[0] def userFromHostmask(hostmask): """hostmask => user Returns the user from a user hostmask.""" assert isUserHostmask(hostmask) return splitHostmask(hostmask)[1] def hostFromHostmask(hostmask): """hostmask => host Returns the host from a user hostmask.""" assert isUserHostmask(hostmask) return splitHostmask(hostmask)[2] def splitHostmask(hostmask): """hostmask => (nick, user, host) Returns the nick, user, host of a user hostmask.""" assert isUserHostmask(hostmask) nick, rest = hostmask.rsplit('!', 1) user, host = rest.rsplit('@', 1) return (minisix.intern(nick), minisix.intern(user), minisix.intern(host)) def joinHostmask(nick, ident, host): """nick, user, host => hostmask Joins the nick, ident, host into a user hostmask.""" assert nick and ident and host return minisix.intern('%s!%s@%s' % (nick, ident, host)) _rfc1459trans = utils.str.MultipleReplacer(dict(list(zip( string.ascii_uppercase + r'\[]~', string.ascii_lowercase + r'|{}^')))) def toLower(s, casemapping=None): """s => s Returns the string s lowered according to IRC case rules.""" if casemapping is None or casemapping == 'rfc1459': return _rfc1459trans(s) elif casemapping == 'ascii': # freenode return s.lower() else: raise ValueError('Invalid casemapping: %r' % casemapping) def strEqual(nick1, nick2): """s1, s2 => bool Returns True if nick1 == nick2 according to IRC case rules.""" assert isinstance(nick1, minisix.string_types) assert isinstance(nick2, minisix.string_types) return toLower(nick1) == toLower(nick2) nickEqual = strEqual _nickchars = r'[]\`_^{|}' nickRe = re.compile(r'^[A-Za-z%s][-0-9A-Za-z%s]*$' % (re.escape(_nickchars), re.escape(_nickchars))) def isNick(s, strictRfc=True, nicklen=None): """s => bool Returns True if s is a valid IRC nick.""" if strictRfc: ret = bool(nickRe.match(s)) if ret and nicklen is not None: ret = len(s) <= nicklen return ret else: return not isChannel(s) and \ not isUserHostmask(s) and \ not ' ' in s and not '!' in s def areNicks(s, strictRfc=True, nicklen=None): """Like 'isNick(x)' but for comma-separated list.""" nick = functools.partial(isNick, strictRfc=strictRfc, nicklen=nicklen) return all(map(nick, s.split(','))) def isChannel(s, chantypes='#&!', channellen=50): """s => bool Returns True if s is a valid IRC channel name.""" return s and \ ',' not in s and \ '\x07' not in s and \ s[0] in chantypes and \ len(s) <= channellen and \ len(s.split(None, 1)) == 1 def areChannels(s, chantypes='#&!', channellen=50): """Like 'isChannel(x)' but for comma-separated list.""" chan = functools.partial(isChannel, chantypes=chantypes, channellen=channellen) return all(map(chan, s.split(','))) def areReceivers(s, strictRfc=True, nicklen=None, chantypes='#&!', channellen=50): """Like 'isNick(x) or isChannel(x)' but for comma-separated list.""" nick = functools.partial(isNick, strictRfc=strictRfc, nicklen=nicklen) chan = functools.partial(isChannel, chantypes=chantypes, channellen=channellen) return all([nick(x) or chan(x) for x in s.split(',')]) _patternCache = utils.structures.CacheDict(1000) def _hostmaskPatternEqual(pattern, hostmask): try: return _patternCache[pattern](hostmask) is not None except KeyError: # We make our own regexps, rather than use fnmatch, because fnmatch's # case-insensitivity is not IRC's case-insensitity. fd = minisix.io.StringIO() for c in pattern: if c == '*': fd.write('.*') elif c == '?': fd.write('.') elif c in '[{': fd.write(r'[\[{]') elif c in '}]': fd.write(r'[}\]]') elif c in '|\\': fd.write(r'[|\\]') elif c in '^~': fd.write('[~^]') else: fd.write(re.escape(c)) fd.write('$') f = re.compile(fd.getvalue(), re.I).match _patternCache[pattern] = f return f(hostmask) is not None _hostmaskPatternEqualCache = utils.structures.CacheDict(1000) def hostmaskPatternEqual(pattern, hostmask): """pattern, hostmask => bool Returns True if hostmask matches the hostmask pattern pattern.""" try: return _hostmaskPatternEqualCache[(pattern, hostmask)] except KeyError: b = _hostmaskPatternEqual(pattern, hostmask) _hostmaskPatternEqualCache[(pattern, hostmask)] = b return b def banmask(hostmask): """Returns a properly generic banning hostmask for a hostmask. >>> banmask('nick!user@host.domain.tld') '*!*@*.domain.tld' >>> banmask('nick!user@10.0.0.1') '*!*@10.0.0.*' """ assert isUserHostmask(hostmask) host = hostFromHostmask(hostmask) if utils.net.isIPV4(host): L = host.split('.') L[-1] = '*' return '*!*@' + '.'.join(L) elif utils.net.isIPV6(host): L = host.split(':') L[-1] = '*' return '*!*@' + ':'.join(L) else: if len(host.split('.')) > 2: # If it is a subdomain return '*!*@*%s' % host[host.find('.'):] else: return '*!*@' + host _plusRequireArguments = 'ovhblkqeI' _minusRequireArguments = 'ovhbkqeI' def separateModes(args): """Separates modelines into single mode change tuples. Basically, you should give it the .args of a MODE IrcMsg. Examples: >>> separateModes(['+ooo', 'jemfinch', 'StoneTable', 'philmes']) [('+o', 'jemfinch'), ('+o', 'StoneTable'), ('+o', 'philmes')] >>> separateModes(['+o-o', 'jemfinch', 'PeterB']) [('+o', 'jemfinch'), ('-o', 'PeterB')] >>> separateModes(['+s-o', 'test']) [('+s', None), ('-o', 'test')] >>> separateModes(['+sntl', '100']) [('+s', None), ('+n', None), ('+t', None), ('+l', 100)] """ if not args: return [] modes = args[0] args = list(args[1:]) ret = [] last = '+' for c in modes: if c in '+-': last = c else: if last == '+': requireArguments = _plusRequireArguments else: requireArguments = _minusRequireArguments if c in requireArguments: if not args: # It happens, for example with "MODE #channel +b", which # is used for getting the list of all bans. continue arg = args.pop(0) try: arg = int(arg) except ValueError: pass ret.append((last + c, arg)) else: ret.append((last + c, None)) return ret def joinModes(modes): """[(mode, targetOrNone), ...] => args Joins modes of the same form as returned by separateModes.""" args = [] modeChars = [] currentMode = '\x00' for (mode, arg) in modes: if arg is not None: args.append(arg) if not mode.startswith(currentMode): currentMode = mode[0] modeChars.append(mode[0]) modeChars.append(mode[1]) args.insert(0, ''.join(modeChars)) return args def bold(s): """Returns the string s, bolded.""" return '\x02%s\x02' % s def italic(s): """Returns the string s, italicised.""" return '\x1D%s\x1D' % s def reverse(s): """Returns the string s, reverse-videoed.""" return '\x16%s\x16' % s def underline(s): """Returns the string s, underlined.""" return '\x1F%s\x1F' % s # Definition of mircColors dictionary moved below because it became an IrcDict. def mircColor(s, fg=None, bg=None): """Returns s with the appropriate mIRC color codes applied.""" if fg is None and bg is None: return s elif bg is None: if str(fg) in mircColors: fg = mircColors[str(fg)] elif len(str(fg)) > 1: fg = mircColors[str(fg)[:-1]] else: # Should not happen pass return '\x03%s%s\x03' % (fg.zfill(2), s) elif fg is None: bg = mircColors[str(bg)] # According to the mirc color doc, a fg color MUST be specified if a # background color is specified. So, we'll specify 00 (white) if the # user doesn't specify one. return '\x0300,%s%s\x03' % (bg.zfill(2), s) else: fg = mircColors[str(fg)] bg = mircColors[str(bg)] # No need to zfill fg because the comma delimits. return '\x03%s,%s%s\x03' % (fg, bg.zfill(2), s) def canonicalColor(s, bg=False, shift=0): """Assigns an (fg, bg) canonical color pair to a string based on its hash value. This means it might change between Python versions. This pair can be used as a *parameter to mircColor. The shift parameter is how much to right-shift the hash value initially. """ h = hash(s) >> shift fg = h % 14 + 2 # The + 2 is to rule out black and white. if bg: bg = (h >> 4) & 3 # The 5th, 6th, and 7th least significant bits. if fg < 8: bg += 8 else: bg += 2 return (fg, bg) else: return (fg, None) def stripBold(s): """Returns the string s, with bold removed.""" return s.replace('\x02', '') def stripItalic(s): """Returns the string s, with italics removed.""" return s.replace('\x1d', '') _stripColorRe = re.compile(r'\x03(?:\d{1,2},\d{1,2}|\d{1,2}|,\d{1,2}|)') def stripColor(s): """Returns the string s, with color removed.""" return _stripColorRe.sub('', s) def stripReverse(s): """Returns the string s, with reverse-video removed.""" return s.replace('\x16', '') def stripUnderline(s): """Returns the string s, with underlining removed.""" return s.replace('\x1f', '') def stripFormatting(s): """Returns the string s, with all formatting removed.""" # stripColor has to go first because of some strings, check the tests. s = stripColor(s) s = stripBold(s) s = stripReverse(s) s = stripUnderline(s) s = stripItalic(s) return s.replace('\x0f', '') _containsFormattingRe = re.compile(r'[\x02\x03\x16\x1f]') def formatWhois(irc, replies, caller='', channel='', command='whois'): """Returns a string describing the target of a WHOIS command. Arguments are: * irc: the irclib.Irc object on which the replies was received * replies: a dict mapping the reply codes ('311', '312', etc.) to their corresponding ircmsg.IrcMsg * caller: an optional nick specifying who requested the whois information * channel: an optional channel specifying where the reply will be sent If provided, caller and channel will be used to avoid leaking information that the caller/channel shouldn't be privy to. """ hostmask = '@'.join(replies['311'].args[2:4]) nick = replies['318'].args[1] user = replies['311'].args[-1] START_CODE = '311' if command == 'whois' else '314' hostmask = '@'.join(replies[START_CODE].args[2:4]) user = replies[START_CODE].args[-1] if _containsFormattingRe.search(user) and user[-1] != '\x0f': # For good measure, disable any formatting user = '%s\x0f' % user if '319' in replies: channels = [] for msg in replies['319']: channels.extend(msg.args[-1].split()) ops = [] voices = [] normal = [] halfops = [] for chan in channels: origchan = chan chan = chan.lstrip('@%+~!') # UnrealIRCd uses & for user modes and disallows it as a # channel-prefix, flying in the face of the RFC. Have to # handle this specially when processing WHOIS response. testchan = chan.lstrip('&') if testchan != chan and irc.isChannel(testchan): chan = testchan diff = len(chan) - len(origchan) modes = origchan[:diff] chanState = irc.state.channels.get(chan) # The user is in a channel the bot is in, so the ircd may have # responded with otherwise private data. if chanState: # Skip channels the caller isn't in. This prevents # us from leaking information when the channel is +s or the # target is +i. if caller not in chanState.users: continue # Skip +s/+p channels the target is in only if the reply isn't # being sent to that channel. if set(('p', 's')) & set(chanState.modes.keys()) and \ not strEqual(channel or '', chan): continue if not modes: normal.append(chan) elif utils.iter.any(lambda c: c in modes,('@', '&', '~', '!')): ops.append(chan) elif utils.iter.any(lambda c: c in modes, ('%',)): halfops.append(chan) elif utils.iter.any(lambda c: c in modes, ('+',)): voices.append(chan) L = [] if ops: L.append(format(_('is an op on %L'), ops)) if halfops: L.append(format(_('is a halfop on %L'), halfops)) if voices: L.append(format(_('is voiced on %L'), voices)) if normal: if L: L.append(format(_('is also on %L'), normal)) else: L.append(format(_('is on %L'), normal)) else: if command == 'whois': L = [_('isn\'t on any publicly visible channels')] else: L = [] channels = format('%L', L) if '317' in replies: idle = utils.timeElapsed(replies['317'].args[2]) signon = utils.str.timestamp(float(replies['317'].args[3])) else: idle = _('') signon = _('') if '312' in replies: server = replies['312'].args[2] if len(replies['312']) > 3: signoff = replies['312'].args[3] else: server = _('') if '301' in replies: away = _(' %s is away: %s.') % (nick, replies['301'].args[2]) else: away = '' if '320' in replies: if replies['320'].args[2]: identify = _(' identified') else: identify = '' else: identify = '' if command == 'whois': s = _('%s (%s) has been%s on server %s since %s (idle for %s). %s ' '%s.%s') % (user, hostmask, identify, server, signon, idle, nick, channels, away) else: s = _('%s (%s) has been%s on server %s and disconnected on %s.') % \ (user, hostmask, identify, server, signoff) return s class FormatContext(object): def __init__(self): self.reset() def reset(self): self.fg = None self.bg = None self.bold = False self.reverse = False self.underline = False def start(self, s): """Given a string, starts all the formatters in this context.""" if self.bold: s = '\x02' + s if self.reverse: s = '\x16' + s if self.underline: s = '\x1f' + s if self.fg is not None or self.bg is not None: s = mircColor(s, fg=self.fg, bg=self.bg)[:-1] # Remove \x03. return s def end(self, s): """Given a string, ends all the formatters in this context.""" if self.bold or self.reverse or \ self.fg or self.bg or self.underline: # Should we individually end formatters? s += '\x0f' return s def size(self): """Returns the number of bytes needed to reproduce this context in an IRC string.""" prefix_size = self.bold + self.reverse + self.underline + \ bool(self.fg) + bool(self.bg) if self.fg and self.bg: prefix_size += 6 # '\x03xx,yy%s' elif self.fg or self.bg: prefix_size += 3 # '\x03xx%s' if prefix_size: return prefix_size + 1 # '\x0f' else: return 0 class FormatParser(object): def __init__(self, s): self.fd = minisix.io.StringIO(s) self.last = None self.max_context_size = 0 def getChar(self): if self.last is not None: c = self.last self.last = None return c else: return self.fd.read(1) def ungetChar(self, c): self.last = c def parse(self): context = FormatContext() c = self.getChar() while c: if c == '\x02': context.bold = not context.bold self.max_context_size = max( self.max_context_size, context.size()) elif c == '\x16': context.reverse = not context.reverse self.max_context_size = max( self.max_context_size, context.size()) elif c == '\x1f': context.underline = not context.underline self.max_context_size = max( self.max_context_size, context.size()) elif c == '\x0f': context.reset() elif c == '\x03': self.getColor(context) self.max_context_size = max( self.max_context_size, context.size()) c = self.getChar() return context def getInt(self): i = 0 setI = False c = self.getChar() while c.isdigit(): j = i * 10 j += int(c) if j >= 16: self.ungetChar(c) break else: setI = True i = j c = self.getChar() self.ungetChar(c) if setI: return i else: return None def getColor(self, context): context.fg = self.getInt() c = self.getChar() if c == ',': context.bg = self.getInt() else: self.ungetChar(c) def wrap(s, length, break_on_hyphens = False): # Get the maximum number of bytes needed to format a chunk of the string # at any point. # This is an overapproximation of what each chunk will need, but it's # either that or make the code of byteTextWrap aware of contexts, and its # code is complicated enough as it is already. parser = FormatParser(s) parser.parse() format_overhead = parser.max_context_size processed = [] chunks = utils.str.byteTextWrap(s, length - format_overhead) context = None for chunk in chunks: if context is not None: chunk = context.start(chunk) context = FormatParser(chunk).parse() processed.append(context.end(chunk)) return processed def isValidArgument(s): """Returns whether s is strictly a valid argument for an IRC message.""" return '\r' not in s and '\n' not in s and '\x00' not in s def safeArgument(s): """If s is unsafe for IRC, returns a safe version.""" if minisix.PY2 and isinstance(s, unicode): s = s.encode('utf-8') elif (minisix.PY2 and not isinstance(s, minisix.string_types)) or \ (minisix.PY3 and not isinstance(s, str)): debug('Got a non-string in safeArgument: %r', s) s = str(s) if isValidArgument(s): return s else: return repr(s) def replyTo(msg): """Returns the appropriate target to send responses to msg.""" if msg.channel: # if message was sent to +#channel, we want to reply to +#channel; # or unvoiced channel users will see the bot reply without the # origin query return msg.args[0] else: return msg.nick def dccIP(ip): """Converts an IP string to the DCC integer form.""" assert utils.net.isIPV4(ip), \ 'argument must be a string ip in xxx.yyy.zzz.www format.' i = 0 x = 256**3 for quad in ip.split('.'): i += int(quad)*x x //= 256 return i def unDccIP(i): """Takes an integer DCC IP and return a normal string IP.""" assert isinstance(i, minisix.integer_types), '%r is not an number.' % i L = [] while len(L) < 4: L.append(i % 256) i //= 256 L.reverse() return '.'.join(map(str, L)) class IrcString(str): """This class does case-insensitive comparison and hashing of nicks.""" def __new__(cls, s=''): x = super(IrcString, cls).__new__(cls, s) x.lowered = str(toLower(x)) return x def __eq__(self, s): try: return toLower(s) == self.lowered except: return False def __ne__(self, s): return not (self == s) def __hash__(self): return hash(self.lowered) class IrcDict(utils.InsensitivePreservingDict): """Subclass of dict to make key comparison IRC-case insensitive.""" def key(self, s): if s is not None: s = toLower(s) return s class CallableValueIrcDict(IrcDict): def __getitem__(self, k): v = super(IrcDict, self).__getitem__(k) if callable(v): v = v() return v class IrcSet(utils.NormalizingSet): """A sets.Set using IrcStrings instead of regular strings.""" def normalize(self, s): return IrcString(s) def __reduce__(self): return (self.__class__, (list(self),)) class FloodQueue(object): timeout = 0 def __init__(self, timeout=None, queues=None): if timeout is not None: self.timeout = timeout if queues is None: queues = IrcDict() self.queues = queues def __repr__(self): return 'FloodQueue(timeout=%r, queues=%s)' % (self.timeout, repr(self.queues)) def key(self, msg): # This really ought to be configurable without subclassing, but for # now, it works. # used to be msg.user + '@' + msg.host but that was too easily abused. return msg.host def getTimeout(self): if callable(self.timeout): return self.timeout() else: return self.timeout def _getQueue(self, msg, insert=True): key = self.key(msg) try: return self.queues[key] except KeyError: if insert: # python-- # instancemethod.__repr__ calls the instance.__repr__, which # means that our __repr__ calls self.queues.__repr__, which # calls structures.TimeoutQueue.__repr__, which calls # getTimeout.__repr__, which calls our __repr__, which calls... getTimeout = lambda : self.getTimeout() q = utils.structures.TimeoutQueue(getTimeout) self.queues[key] = q return q else: return None def enqueue(self, msg, what=None): if what is None: what = msg q = self._getQueue(msg) q.enqueue(what) def len(self, msg): q = self._getQueue(msg, insert=False) if q is not None: return len(q) else: return 0 def has(self, msg, what=None): q = self._getQueue(msg, insert=False) if q is not None: if what is None: what = msg for elt in q: if elt == what: return True return False mircColors = IrcDict({ 'white': '0', 'black': '1', 'blue': '2', 'green': '3', 'red': '4', 'brown': '5', 'purple': '6', 'orange': '7', 'yellow': '8', 'light green': '9', 'teal': '10', 'light blue': '11', 'dark blue': '12', 'pink': '13', 'dark grey': '14', 'light grey': '15', 'dark gray': '14', 'light gray': '15', }) # We'll map integers to their string form so mircColor is simpler. for (k, v) in list(mircColors.items()): if k is not None: # Ignore empty string for None. sv = str(v) mircColors[sv] = sv mircColors[sv.zfill(2)] = sv def standardSubstitute(irc, msg, text, env=None): """Do the standard set of substitutions on text, and return it""" def randInt(): return str(random.randint(-1000, 1000)) def randDate(): t = pow(2,30)*random.random()+time.time()/4.0 return time.ctime(t) ctime = time.strftime("%a %b %d %H:%M:%S %Y") localtime = time.localtime() gmtime = time.strftime("%a %b %d %H:%M:%S %Y", time.gmtime()) vars = CallableValueIrcDict({ 'now': ctime, 'ctime': ctime, 'utc': gmtime, 'gmt': gmtime, 'randdate': randDate, 'randomdate': randDate, 'rand': randInt, 'randint': randInt, 'randomint': randInt, 'today': time.strftime('%d %b %Y', localtime), 'year': localtime[0], 'month': localtime[1], 'monthname': time.strftime('%b', localtime), 'date': localtime[2], 'day': time.strftime('%A', localtime), 'h': localtime[3], 'hr': localtime[3], 'hour': localtime[3], 'm': localtime[4], 'min': localtime[4], 'minute': localtime[4], 's': localtime[5], 'sec': localtime[5], 'second': localtime[5], 'tz': time.strftime('%Z', localtime), 'version': version, }) if irc: vars.update({ 'botnick': irc.nick, 'network': irc.network, }) if msg: vars.update({ 'who': msg.nick, 'nick': msg.nick, 'user': msg.user, 'host': msg.host, }) if msg.reply_env: vars.update(msg.reply_env) if irc and msg: channel = msg.channel or 'somewhere' def randNick(): if channel != 'somewhere': L = list(irc.state.channels[channel].users) if len(L) > 1: n = msg.nick while n == msg.nick: n = utils.iter.choice(L) return n else: return msg.nick else: return 'someone' vars.update({ 'randnick': randNick, 'randomnick': randNick, 'channel': channel, }) else: vars.update({ 'channel': 'somewhere', 'randnick': 'someone', 'randomnick': 'someone', }) if env is not None: vars.update(env) t = string.Template(text) t.idpattern = '[a-zA-Z][a-zA-Z0-9]*' return t.safe_substitute(vars) AUTHENTICATE_CHUNK_SIZE = 400 def authenticate_generator(authstring, base64ify=True): if base64ify: authstring = base64.b64encode(authstring) if minisix.PY3: authstring = authstring.decode() # +1 so we get an empty string at the end if len(authstring) is a multiple # of AUTHENTICATE_CHUNK_SIZE (including 0) for n in range(0, len(authstring)+1, AUTHENTICATE_CHUNK_SIZE): chunk = authstring[n:n+AUTHENTICATE_CHUNK_SIZE] or '+' yield chunk class AuthenticateDecoder(object): def __init__(self): self.chunks = [] self.ready = False def feed(self, msg): assert msg.command == 'AUTHENTICATE' chunk = msg.args[0] if chunk == '+' or len(chunk) != AUTHENTICATE_CHUNK_SIZE: self.ready = True if chunk != '+': if minisix.PY3: chunk = chunk.encode() self.chunks.append(chunk) def get(self): assert self.ready return base64.b64decode(b''.join(self.chunks)) numerics = { # <= 2.10 # Reply '001': 'RPL_WELCOME', '002': 'RPL_YOURHOST', '003': 'RPL_CREATED', '004': 'RPL_MYINFO', '005': 'RPL_BOUNCE', '302': 'RPL_USERHOST', '303': 'RPL_ISON', '301': 'RPL_AWAY', '305': 'RPL_UNAWAY', '306': 'RPL_NOWAWAY', '311': 'RPL_WHOISUSER', '312': 'RPL_WHOISSERVER', '313': 'RPL_WHOISOPERATOR', '317': 'RPL_WHOISIDLE', '318': 'RPL_ENDOFWHOIS', '319': 'RPL_WHOISCHANNELS', '314': 'RPL_WHOWASUSER', '369': 'RPL_ENDOFWHOWAS', '321': 'RPL_LISTSTART', '322': 'RPL_LIST', '323': 'RPL_LISTEND', '325': 'RPL_UNIQOPIS', '324': 'RPL_CHANNELMODEIS', '331': 'RPL_NOTOPIC', '332': 'RPL_TOPIC', '341': 'RPL_INVITING', '342': 'RPL_SUMMONING', '346': 'RPL_INVITELIST', '347': 'RPL_ENDOFINVITELIST', '348': 'RPL_EXCEPTLIST', '349': 'RPL_ENDOFEXCEPTLIST', '351': 'RPL_VERSION', '352': 'RPL_WHOREPLY', '352': 'RPL_WHOREPLY', '353': 'RPL_NAMREPLY', '366': 'RPL_ENDOFNAMES', '364': 'RPL_LINKS', '365': 'RPL_ENDOFLINKS', '367': 'RPL_BANLIST', '368': 'RPL_ENDOFBANLIST', '371': 'RPL_INFO', '374': 'RPL_ENDOFINFO', '372': 'RPL_MOTD', '376': 'RPL_ENDOFMOTD', '381': 'RPL_YOUREOPER', '382': 'RPL_REHASHING', '383': 'RPL_YOURESERVICE', '391': 'RPL_TIME', '392': 'RPL_USERSSTART', '393': 'RPL_USERS', '394': 'RPL_ENDOFUSERS', '395': 'RPL_NOUSERS', '200': 'RPL_TRACELINK', '201': 'RPL_TRACECONNECTING', '202': 'RPL_TRACEHANDSHAKE', '203': 'RPL_TRACEUNKNOWN', '204': 'RPL_TRACEOPERATOR', '205': 'RPL_TRACEUSER', '206': 'RPL_TRACESERVER', '207': 'RPL_TRACESERVICE', '208': 'RPL_TRACENEWTYPE', '209': 'RPL_TRACECLASS', '210': 'RPL_TRACERECONNECT', '261': 'RPL_TRACELOG', '262': 'RPL_TRACEEND', '211': 'RPL_STATSLINKINFO', '212': 'RPL_STATSCOMMANDS', '219': 'RPL_ENDOFSTATS', '242': 'RPL_STATSUPTIME', '243': 'RPL_STATSOLINE', '221': 'RPL_UMODEIS', '234': 'RPL_SERVLIST', '235': 'RPL_SERVLISTEND', '251': 'RPL_LUSERCLIENT', '252': 'RPL_LUSEROP', '253': 'RPL_LUSERUNKNOWN', '254': 'RPL_LUSERCHANNELS', '255': 'RPL_LUSERME', '256': 'RPL_ADMINME', '257': 'RPL_ADMINLOC1', '258': 'RPL_ADMINLOC2', '259': 'RPL_ADMINEMAIL', '263': 'RPL_TRYAGAIN', # Error '401': 'ERR_NOSUCHNICK', '402': 'ERR_NOSUCHSERVER', '403': 'ERR_NOSUCHCHANNEL', '404': 'ERR_CANNOTSENDTOCHAN', '405': 'ERR_TOOMANYCHANNELS', '406': 'ERR_WASNOSUCHNICK', '407': 'ERR_TOOMANYTARGETS', '408': 'ERR_NOSUCHSERVICE', '409': 'ERR_NOORIGIN', '411': 'ERR_NORECIPIENT', '412': 'ERR_NOTEXTTOSEND', '413': 'ERR_NOTOPLEVEL', '414': 'ERR_WILDTOPLEVEL', '415': 'ERR_BADMASK', '421': 'ERR_UNKNOWNCOMMAND', '422': 'ERR_NOMOTD', '423': 'ERR_NOADMININFO', '424': 'ERR_FILEERROR', '431': 'ERR_NONICKNAMEGIVEN', '432': 'ERR_ERRONEUSNICKNAME', '433': 'ERR_NICKNAMEINUSE', '436': 'ERR_NICKCOLLISION', '437': 'ERR_UNAVAILRESOURCE', '441': 'ERR_USERNOTINCHANNEL', '442': 'ERR_NOTONCHANNEL', '443': 'ERR_USERONCHANNEL', '444': 'ERR_NOLOGIN', '445': 'ERR_SUMMONDISABLED', '446': 'ERR_USERSDISABLED', '451': 'ERR_NOTREGISTERED', '461': 'ERR_NEEDMOREPARAMS', '462': 'ERR_ALREADYREGISTRED', '463': 'ERR_NOPERMFORHOST', '464': 'ERR_PASSWDMISMATCH', '465': 'ERR_YOUREBANNEDCREEP', '466': 'ERR_YOUWILLBEBANNED', '467': 'ERR_KEYSET', '471': 'ERR_CHANNELISFULL', '472': 'ERR_UNKNOWNMODE', '473': 'ERR_INVITEONLYCHAN', '474': 'ERR_BANNEDFROMCHAN', '475': 'ERR_BADCHANNELKEY', '476': 'ERR_BADCHANMASK', '477': 'ERR_NOCHANMODES', '478': 'ERR_BANLISTFULL', '481': 'ERR_NOPRIVILEGES', '482': 'ERR_CHANOPRIVSNEEDED', '483': 'ERR_CANTKILLSERVER', '484': 'ERR_RESTRICTED', '485': 'ERR_UNIQOPPRIVSNEEDED', '491': 'ERR_NOOPERHOST', '501': 'ERR_UMODEUNKNOWNFLAG', '502': 'ERR_USERSDONTMATCH', # Reserved '231': 'RPL_SERVICEINFO', '232': 'RPL_ENDOFSERVICES', '233': 'RPL_SERVICE', '300': 'RPL_NONE', '316': 'RPL_WHOISCHANOP', '361': 'RPL_KILLDONE', '362': 'RPL_CLOSING', '363': 'RPL_CLOSEEND', '373': 'RPL_INFOSTART', '384': 'RPL_MYPORTIS', '213': 'RPL_STATSCLINE', '214': 'RPL_STATSNLINE', '215': 'RPL_STATSILINE', '216': 'RPL_STATSKLINE', '217': 'RPL_STATSQLINE', '218': 'RPL_STATSYLINE', '240': 'RPL_STATSVLINE', '241': 'RPL_STATSLLINE', '244': 'RPL_STATSHLINE', '244': 'RPL_STATSSLINE', '246': 'RPL_STATSPING', '247': 'RPL_STATSBLINE', '250': 'RPL_STATSDLINE', '492': 'ERR_NOSERVICEHOST', # IRC v3.1 # SASL '900': 'RPL_LOGGEDIN', '901': 'RPL_LOGGEDOUT', '902': 'ERR_NICKLOCKED', '903': 'RPL_SASLSUCCESS', '904': 'ERR_SASLFAIL', '905': 'ERR_SASLTOOLONG', '906': 'ERR_SASLABORTED', '907': 'ERR_SASLALREADY', '908': 'RPL_SASLMECHS', # IRC v3.2 # Metadata '760': 'RPL_WHOISKEYVALUE', '761': 'RPL_KEYVALUE', '762': 'RPL_METADATAEND', '764': 'ERR_METADATALIMIT', '765': 'ERR_TARGETINVALID', '766': 'ERR_NOMATCHINGKEY', '767': 'ERR_KEYINVALID', '768': 'ERR_KEYNOTSET', '769': 'ERR_KEYNOPERMISSION', # Monitor '730': 'RPL_MONONLINE', '731': 'RPL_MONOFFLINE', '732': 'RPL_MONLIST', '733': 'RPL_ENDOFMONLIST', '734': 'ERR_MONLISTFULL', } if __name__ == '__main__': import doctest doctest.testmod(sys.modules['__main__']) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/log.py0000644000175000017500000004015513634634532015246 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import os import sys import time import types import atexit import logging import operator import textwrap import traceback from . import ansi, conf, ircutils, registry, utils from .utils import minisix deadlyExceptions = [KeyboardInterrupt, SystemExit] ### # This is for testing, of course. Mostly it just disables the firewall code # so exceptions can propagate. ### testing = False class Formatter(logging.Formatter): _fmtConf = staticmethod(lambda : conf.supybot.log.format()) def formatTime(self, record, datefmt=None): return timestamp(record.created) def formatException(self, exc_info): (E, e, tb) = exc_info for exn in deadlyExceptions: if issubclass(e.__class__, exn): raise return logging.Formatter.formatException(self, (E, e, tb)) def format(self, record): self._fmt = self._fmtConf() if hasattr(self, '_style'): # Python 3 self._style._fmt = self._fmtConf() return logging.Formatter.format(self, record) class PluginFormatter(Formatter): _fmtConf = staticmethod(lambda : conf.supybot.log.plugins.format()) class Logger(logging.Logger): def exception(self, *args): (E, e, tb) = sys.exc_info() tbinfo = traceback.extract_tb(tb) path = '[%s]' % '|'.join(map(operator.itemgetter(2), tbinfo)) eStrId = '%s:%s' % (E, path) eId = hex(hash(eStrId) & 0xFFFFF) logging.Logger.exception(self, *args) self.error('Exception id: %s', eId) self.debug('%s', utils.python.collect_extra_debug_data()) # The traceback should be sufficient if we want it. # self.error('Exception string: %s', eStrId) def _log(self, level, msg, args, exc_info=None, extra=None): msg = format(msg, *args) logging.Logger._log(self, level, msg, (), exc_info=exc_info, extra=extra) class StdoutStreamHandler(logging.StreamHandler): def format(self, record): s = logging.StreamHandler.format(self, record) if record.levelname != 'ERROR' and conf.supybot.log.stdout.wrap(): # We check for ERROR there because otherwise, tracebacks (which are # already wrapped by Python itself) wrap oddly. if not isinstance(record.levelname, minisix.string_types): print(record) print(record.levelname) print(utils.stackTrace()) prefixLen = len(record.levelname) + 1 # ' ' s = textwrap.fill(s, width=78, subsequent_indent=' '*prefixLen) s.rstrip('\r\n') return s def emit(self, record): if conf.supybot.log.stdout() and not conf.daemonized: try: logging.StreamHandler.emit(self, record) except ValueError: # Raised if sys.stdout is closed. self.disable() error('Error logging to stdout. Removing stdout handler.') exception('Uncaught exception in StdoutStreamHandler:') def disable(self): self.setLevel(sys.maxsize) # Just in case. _logger.removeHandler(self) logging._acquireLock() try: del logging._handlers[self] finally: logging._releaseLock() class BetterFileHandler(logging.FileHandler): def emit(self, record): msg = self.format(record) try: self.stream.write(msg) except (UnicodeError, TypeError): try: self.stream.write(msg.encode("utf8")) except (UnicodeError, TypeError): try: self.stream.write(msg.encode("utf8").decode('ascii', 'replace')) except (UnicodeError, TypeError): self.stream.write(repr(msg)) self.stream.write(os.linesep) try: self.flush() except OSError as e: if e.args[0] == 28: print('No space left on device, cannot flush log.') else: raise class ColorizedFormatter(Formatter): # This was necessary because these variables aren't defined until later. # The staticmethod is necessary because they get treated like methods. _fmtConf = staticmethod(lambda : conf.supybot.log.stdout.format()) def formatException(self, exc_info): (E, e, tb) = exc_info if conf.supybot.log.stdout.colorized(): return ''.join([ansi.RED, Formatter.formatException(self, (E, e, tb)), ansi.RESET]) else: return Formatter.formatException(self, (E, e, tb)) def format(self, record, *args, **kwargs): if conf.supybot.log.stdout.colorized(): color = '' if record.levelno == logging.CRITICAL: color = ansi.WHITE + ansi.BOLD elif record.levelno == logging.ERROR: color = ansi.RED elif record.levelno == logging.WARNING: color = ansi.YELLOW if color: return ''.join([color, Formatter.format(self, record, *args, **kwargs), ansi.RESET]) else: return Formatter.format(self, record, *args, **kwargs) else: return Formatter.format(self, record, *args, **kwargs) _logDir = conf.supybot.directories.log() if not os.path.exists(_logDir): os.mkdir(_logDir, 0o755) pluginLogDir = os.path.join(_logDir, 'plugins') if not os.path.exists(pluginLogDir): os.mkdir(pluginLogDir, 0o755) try: messagesLogFilename = os.path.join(_logDir, 'messages.log') _handler = BetterFileHandler(messagesLogFilename, encoding='utf8') except EnvironmentError as e: raise SystemExit('Error opening messages logfile (%s). ' \ 'Generally, this is because you are running Supybot in a directory ' \ 'you don\'t have permissions to add files in, or you\'re running ' \ 'Supybot as a different user than you normally do. The original ' \ 'error was: %s' % (messagesLogFilename, utils.gen.exnToString(e))) # These are public. if sys.version_info >= (3, 8): formatter = Formatter( 'NEVER SEEN; IF YOU SEE THIS, FILE A BUG!', validate=False) pluginFormatter = PluginFormatter( 'NEVER SEEN; IF YOU SEE THIS, FILE A BUG!', validate=False) else: formatter = Formatter( 'NEVER SEEN; IF YOU SEE THIS, FILE A BUG!') pluginFormatter = PluginFormatter( 'NEVER SEEN; IF YOU SEE THIS, FILE A BUG!') # These are not. logging.setLoggerClass(Logger) _logger = logging.getLogger('supybot') _stdoutHandler = StdoutStreamHandler(sys.stdout) class ValidLogLevel(registry.String): """Invalid log level.""" handler = None minimumLevel = -1 def set(self, s): s = s.upper() try: try: level = logging._levelNames[s] except AttributeError: level = logging._nameToLevel[s] except KeyError: try: level = int(s) except ValueError: self.error() if level < self.minimumLevel: self.error() if self.handler is not None: self.handler.setLevel(level) self.setValue(level) def __str__(self): # The str() is necessary here; apparently getLevelName returns an # integer on occasion. logging-- level = str(logging.getLevelName(self.value)) if level.startswith('Level'): level = level.split()[-1] return level class LogLevel(ValidLogLevel): """Invalid log level. Value must be either DEBUG, INFO, WARNING, ERROR, or CRITICAL.""" handler = _handler class StdoutLogLevel(ValidLogLevel): """Invalid log level. Value must be either DEBUG, INFO, WARNING, ERROR, or CRITICAL.""" handler = _stdoutHandler conf.registerGroup(conf.supybot, 'log') conf.registerGlobalValue(conf.supybot.log, 'format', registry.String('%(levelname)s %(asctime)s %(name)s %(message)s', """Determines what the bot's logging format will be. The relevant documentation on the available formattings is Python's documentation on its logging module.""")) conf.registerGlobalValue(conf.supybot.log, 'level', LogLevel(logging.INFO, """Determines what the minimum priority level logged to file will be. Do note that this value does not affect the level logged to stdout; for that, you should set the value of supybot.log.stdout.level. Valid values are DEBUG, INFO, WARNING, ERROR, and CRITICAL, in order of increasing priority.""")) conf.registerGlobalValue(conf.supybot.log, 'timestampFormat', registry.String('%Y-%m-%dT%H:%M:%S', """Determines the format string for timestamps in logfiles. Refer to the Python documentation for the time module to see what formats are accepted. If you set this variable to the empty string, times will be logged in a simple seconds-since-epoch format.""")) class BooleanRequiredFalseOnWindows(registry.Boolean): """Value cannot be true on Windows""" def set(self, s): registry.Boolean.set(self, s) if self.value and os.name == 'nt': self.error() conf.registerGlobalValue(conf.supybot.log, 'stdout', registry.Boolean(True, """Determines whether the bot will log to stdout.""")) conf.registerGlobalValue(conf.supybot.log.stdout, 'colorized', BooleanRequiredFalseOnWindows(False, """Determines whether the bot's logs to stdout (if enabled) will be colorized with ANSI color.""")) conf.registerGlobalValue(conf.supybot.log.stdout, 'wrap', registry.Boolean(False, """Determines whether the bot will wrap its logs when they're output to stdout.""")) conf.registerGlobalValue(conf.supybot.log.stdout, 'format', registry.String('%(levelname)s %(asctime)s %(message)s', """Determines what the bot's logging format will be. The relevant documentation on the available formattings is Python's documentation on its logging module.""")) conf.registerGlobalValue(conf.supybot.log.stdout, 'level', StdoutLogLevel(logging.INFO, """Determines what the minimum priority level logged will be. Valid values are DEBUG, INFO, WARNING, ERROR, and CRITICAL, in order of increasing priority.""")) conf.registerGroup(conf.supybot.log, 'plugins') conf.registerGlobalValue(conf.supybot.log.plugins, 'individualLogfiles', registry.Boolean(False, """Determines whether the bot will separate plugin logs into their own individual logfiles.""")) conf.registerGlobalValue(conf.supybot.log.plugins, 'format', registry.String('%(levelname)s %(asctime)s %(message)s', """Determines what the bot's logging format will be. The relevant documentation on the available formattings is Python's documentation on its logging module.""")) # These just make things easier. debug = _logger.debug info = _logger.info warning = _logger.warning error = _logger.error critical = _logger.critical exception = _logger.exception # These were just begging to be replaced. registry.error = error registry.exception = exception setLevel = _logger.setLevel atexit.register(logging.shutdown) # ircutils will work without this, but it's useful. ircutils.debug = debug def getPluginLogger(name): if not conf.supybot.log.plugins.individualLogfiles(): return _logger log = logging.getLogger('supybot.plugins.%s' % name) if not log.handlers: filename = os.path.join(pluginLogDir, '%s.log' % name) handler = BetterFileHandler(filename) handler.setLevel(-1) handler.setFormatter(pluginFormatter) log.addHandler(handler) if name in sys.modules: log.info('Starting log for %s.', name) return log def timestamp(when=None): if when is None: when = time.time() format = conf.supybot.log.timestampFormat() t = time.localtime(when) if format: return time.strftime(format, t) else: return str(int(time.mktime(t))) def firewall(f, errorHandler=None): def logException(self, s=None): if s is None: s = 'Uncaught exception' if hasattr(self, 'log'): logging_function = self.log.exception else: logging_function = exception logging_function('%s in %s.%s:', s, self.__class__.__name__, f.__name__) def m(self, *args, **kwargs): try: return f(self, *args, **kwargs) except Exception: if testing: raise logException(self) if errorHandler is not None: try: return errorHandler(self, *args, **kwargs) except Exception: logException(self, 'Uncaught exception in errorHandler') m = utils.python.changeFunctionName(m, f.__name__, f.__doc__) return m class MetaFirewall(type): def __new__(cls, name, bases, classdict): firewalled = {} for base in bases: if hasattr(base, '__firewalled__'): cls.updateFirewalled(firewalled, base.__firewalled__) cls.updateFirewalled(firewalled, classdict.get('__firewalled__', [])) for (attr, errorHandler) in firewalled.items(): if attr in classdict: classdict[attr] = firewall(classdict[attr], errorHandler) return super(MetaFirewall, cls).__new__(cls, name, bases, classdict) def getErrorHandler(cls, dictOrTuple, name): if isinstance(dictOrTuple, dict): return dictOrTuple[name] else: return None getErrorHandler = classmethod(getErrorHandler) def updateFirewalled(cls, firewalled, __firewalled__): for attr in __firewalled__: firewalled[attr] = cls.getErrorHandler(__firewalled__, attr) updateFirewalled = classmethod(updateFirewalled) Firewalled = MetaFirewall('Firewalled', (), {}) class PluginLogFilter(logging.Filter): def filter(self, record): if conf.supybot.log.plugins.individualLogfiles(): if record.name.startswith('supybot.plugins'): return False return True _handler.setFormatter(formatter) _handler.addFilter(PluginLogFilter()) _handler.setLevel(conf.supybot.log.level()) _logger.addHandler(_handler) _logger.setLevel(-1) if sys.version_info >= (3, 8): _stdoutFormatter = ColorizedFormatter( 'IF YOU SEE THIS, FILE A BUG!', validate=False) else: _stdoutFormatter = ColorizedFormatter( 'IF YOU SEE THIS, FILE A BUG!') _stdoutHandler.setFormatter(_stdoutFormatter) _stdoutHandler.setLevel(conf.supybot.log.stdout.level()) if not conf.daemonized: _logger.addHandler(_stdoutHandler) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/plugin.py0000644000175000017500000001630113634634532015757 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import os import re import sys import os.path import linecache import importlib.util if not hasattr(importlib.util, 'module_from_spec'): # Python < 3.5 import imp from . import callbacks, conf, log, registry installDir = os.path.dirname(sys.modules[__name__].__file__) _pluginsDir = os.path.join(installDir, 'plugins') class Deprecated(ImportError): pass def loadPluginModule(name, ignoreDeprecation=False): """Loads (and returns) the module for the plugin with the given name.""" files = [] pluginDirs = conf.supybot.directories.plugins()[:] pluginDirs.append(_pluginsDir) for dir in pluginDirs: try: files.extend(os.listdir(dir)) except EnvironmentError: # OSError, IOError superclass. log.warning('Invalid plugin directory: %s; removing.', dir) conf.supybot.directories.plugins().remove(dir) if name not in files: search = lambda x: re.search(r'(?i)^%s$' % (name,), x) matched_names = list(filter(search, files)) if len(matched_names) >= 1: name = matched_names[0] else: raise ImportError(name) try: if hasattr(importlib.util, 'module_from_spec'): # Python >= 3.5 spec = importlib.machinery.PathFinder.find_spec(name, pluginDirs) if spec is None or spec.loader is None: # spec is None if 'name' can't be found; and # spec.loader might be None in some rare occasions as well # (eg. for namespace packages) assert ImportError(name) module = importlib.util.module_from_spec(spec) sys.modules[module.__name__] = module spec.loader.exec_module(module) else: # Python < 3.5 moduleInfo = imp.find_module(name, pluginDirs) module = imp.load_module(name, *moduleInfo) except: sys.modules.pop(name, None) keys = list(sys.modules.keys()) for key in keys: if key.startswith(name + '.'): sys.modules.pop(key) raise if 'deprecated' in module.__dict__ and module.deprecated: if ignoreDeprecation: log.warning('Deprecated plugin loaded: %s', name) else: raise Deprecated(format('Attempted to load deprecated plugin %s', name)) if module.__name__ in sys.modules: sys.modules[module.__name__] = module linecache.checkcache() return module def renameCommand(cb, name, newName): assert not hasattr(cb, newName), 'Cannot rename over existing attributes.' assert newName == callbacks.canonicalName(newName), \ 'newName must already be normalized.' if name != newName: method = getattr(cb.__class__, name) setattr(cb.__class__, newName, method) delattr(cb.__class__, name) def registerRename(plugin, command=None, newName=None): g = conf.registerGlobalValue(conf.supybot.commands.renames, plugin, registry.SpaceSeparatedSetOfStrings([], """Determines what commands in this plugin are to be renamed.""")) if command is not None: g().add(command) v = conf.registerGlobalValue(g, command, registry.String('', '')) if newName is not None: v.setValue(newName) # In case it was already registered. return v else: return g def loadPluginClass(irc, module, register=None): """Loads the plugin Class from the given module into the given Irc.""" try: cb = module.Class(irc) except TypeError as e: s = str(e) if '2 given' in s and '__init__' in s: raise callbacks.Error('In our switch from CVS to Darcs (after 0.80.1), we ' \ 'changed the __init__ for callbacks.Privmsg* to also ' \ 'accept an irc argument. This plugin (%s) is overriding ' \ '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__. 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 as e: if 'Class' in str(e): raise callbacks.Error('This plugin module doesn\'t have a "Class" ' \ 'attribute to specify which plugin should be ' \ 'instantiated. If you didn\'t write this ' \ 'plugin, but received it with Supybot, file ' \ 'a bug with us about this error.') else: raise cb.classModule = module plugin = cb.name() public = True if hasattr(cb, 'public'): public = cb.public conf.registerPlugin(plugin, register, public) assert not irc.getCallback(plugin), \ 'There is already a %r plugin registered.' % plugin try: v = registerRename(plugin) renames = conf.supybot.commands.renames.get(plugin)() if renames: for command in renames: v = registerRename(plugin, command) newName = v() assert newName renameCommand(cb, command, newName) else: conf.supybot.commands.renames.unregister(plugin) except registry.NonExistentRegistryEntry as e: pass # The plugin isn't there. irc.addCallback(cb) return cb # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/questions.py0000644000175000017500000001226213634634532016515 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """Handles interactive questions; useful for wizards and whatnot.""" from __future__ import print_function import sys import textwrap from getpass import getpass as getPass from . import ansi, utils from .utils import minisix from supybot.i18n import PluginInternationalization _ = PluginInternationalization() useBold = False def output(s, unformatted=True, fd=sys.stdout): if unformatted: s = textwrap.fill(utils.str.normalizeWhitespace(s), width=65) print(s, file=fd) print('', file=fd) def expect(prompt, possibilities, recursed=False, default=None, acceptEmpty=False, fd=sys.stdout): """Prompt the user with prompt, allow them to choose from possibilities. If possibilities is empty, allow anything. """ prompt = utils.str.normalizeWhitespace(prompt) originalPrompt = prompt if recursed: output(_('Sorry, that response was not an option.')) if useBold: choices = '[%s%%s%s]' % (ansi.RESET, ansi.BOLD) else: choices = '[%s]' if possibilities: prompt = '%s %s' % (originalPrompt, choices % '/'.join(possibilities)) if len(prompt) > 70: prompt = '%s %s' % (originalPrompt, choices % '/ '.join(possibilities)) if default is not None: if useBold: prompt = '%s %s(default: %s)' % (prompt, ansi.RESET, default) else: prompt = '%s (default: %s)' % (prompt, default) prompt = textwrap.fill(prompt) prompt = prompt.replace('/ ', '/') prompt = prompt.strip() + ' ' if useBold: prompt += ansi.RESET print(ansi.BOLD, end=' ', file=fd) if minisix.PY3: s = input(prompt) else: s = raw_input(prompt) s = s.strip() print(file=fd) if possibilities: if s in possibilities: return s elif not s and default is not None: return default elif not s and acceptEmpty: return s else: return expect(originalPrompt, possibilities, recursed=True, default=default) else: if not s and default is not None: return default return s.strip() def anything(prompt): """Allow anything from the user.""" return expect(prompt, []) def something(prompt, default=None): """Allow anything *except* nothing from the user.""" s = expect(prompt, [], default=default) while not s: output(_('Sorry, you must enter a value.')) s = expect(prompt, [], default=default) return s def yn(prompt, default=None): """Allow only 'y' or 'n' from the user.""" if default is not None: if default: default = 'y' else: default = 'n' s = expect(prompt, ['y', 'n'], default=default) if s == 'y': return True else: return False def getpass(prompt=None, secondPrompt=None): """Prompt the user for a password.""" if prompt is None: prompt = _('Enter password: ') if secondPrompt is None: secondPrompt = _('Re-enter password: ') password = '' secondPassword = ' ' # Note that this should be different than password. assert prompt if not prompt[-1].isspace(): prompt += ' ' while True: if useBold: prompt = ansi.BOLD + prompt + ansi.RESET secondPrompt = ansi.BOLD + secondPrompt + ansi.RESET password = getPass(prompt) secondPassword = getPass(secondPrompt) if password != secondPassword: output(_('Passwords don\'t match.')) else: break print('') return password # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/registry.py0000644000175000017500000007313113634634532016335 0ustar valval00000000000000### # Copyright (c) 2004-2005, Jeremiah Fincher # Copyright (c) 2009-2010, James McCoy # 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 re import os import time import json import codecs import string import textwrap from . import utils, i18n from .utils import minisix _ = i18n.PluginInternationalization() def error(s): """Replace me with something better from another module!""" print('***', s) def exception(s): """Ditto!""" print('***', s, 'A bad exception.') class RegistryException(Exception): pass class InvalidRegistryFile(RegistryException): pass class InvalidRegistryName(RegistryException): pass class InvalidRegistryValue(RegistryException): pass class NonExistentRegistryEntry(RegistryException, AttributeError): # If we use hasattr() on a configuration group/value, Python 3 calls # __getattr__ and looks for an AttributeError, so __getattr__ has to # raise an AttributeError if a registry entry does not exist. pass ENCODING = 'string_escape' if minisix.PY2 else 'unicode_escape' decoder = codecs.getdecoder(ENCODING) encoder = codecs.getencoder(ENCODING) if hasattr(time, 'monotonic'): monotonic_time = time.monotonic else: # fallback for python < 3.3 monotonic_time = time.time _cache = utils.InsensitivePreservingDict() _lastModified = 0 def open_registry(filename, clear=False): """Initializes the module by loading the registry file into memory.""" global _lastModified if clear: _cache.clear() _fd = open(filename) fd = utils.file.nonCommentNonEmptyLines(_fd) acc = '' slashEnd = re.compile(r'\\*$') for line in fd: line = line.rstrip('\r\n') # XXX There should be some way to determine whether or not we're # starting a new variable or not. As it is, if there's a backslash # at the end of every line in a variable, it won't be read, and # worse, the error will pass silently. # # If the line ends in an odd number of backslashes, then there is a # line-continutation. m = slashEnd.search(line) if m and len(m.group(0)) % 2: acc += line[:-1] continue else: acc += line try: (key, value) = re.split(r'(?', _cache[name] self.set(_cache[name]) if self._supplyDefault: for (k, v) in _cache.items(): if k.startswith(self._name): rest = k[len(self._name)+1:] # +1 is for . parts = split(rest) if len(parts) == 1 and parts[0] == name: try: self._makeChild(name, v) except InvalidRegistryValue: # It's probably supposed to be registered later. pass def register(self, name, node=None): if not isValidRegistryName(name): raise InvalidRegistryName(name) if node is None: node = Group(private=self._private) else: node._private = node._private or self._private # We tried in any number of horrible ways to make it so that # re-registering something would work. It doesn't, plain and simple. # For the longest time, we had an "Is this right?" comment here, but # from experience, we now know that it most definitely *is* right. if name not in self._children: self._children[name] = node self._added.append(name) names = split(self._name) names.append(name) fullname = join(names) node.setName(fullname) else: # We do this in order to reload the help, if it changed. if node._help != '' and node._help != self._children[name]._help: self._children[name]._help = node._help # We do this so the return value from here is at least useful; # otherwise, we're just returning a useless, unattached node # that's simply a waste of space. node = self._children[name] return node def unregister(self, name): try: node = self._children[name] del self._children[name] # We do this because we need to remove case-insensitively. name = name.lower() for elt in reversed(self._added): if elt.lower() == name: self._added.remove(elt) if node._name in _cache: del _cache[node._name] return node except KeyError: self.__nonExistentEntry(name) def rename(self, old, new): node = self.unregister(old) self.register(new, node) def getValues(self, getChildren=False, fullNames=True): L = [] if self._orderAlphabetically: self._added.sort() for name in self._added: node = self._children[name] if hasattr(node, 'value') or hasattr(node, 'help'): if node._wasSet: L.append((node._name, node)) if getChildren: L.extend(node.getValues(getChildren, fullNames)) if not fullNames: L = [(split(s)[-1], node) for (s, node) in L] return L class _NoValueGiven: # Special value for Value.error() pass class Value(Group): """Invalid registry value. If you're getting this message, report it, because we forgot to put a proper help string here.""" __slots__ = ('__parent', '_default', '_showDefault', '_help', '_callbacks', 'value', '_networkValue', '_channelValue', '_opSettable') def __init__(self, default, help, setDefault=True, showDefault=True, **kwargs): self.__parent = super(Value, self) self.__parent.__init__(help, **kwargs) self._default = default self._showDefault = showDefault self._help = utils.str.normalizeWhitespace(help.strip()) self._callbacks = [] self._networkValue = False self._channelValue = False if setDefault: self.setValue(default) def _makeChild(self, attr, s): v = self.__class__(self._default, self._help) v.set(s) v._wasSet = False if self._networkValue and self._channelValue: # If this is both a network-specific and channel-specific value, # then the child is (only) channel-specific. v._networkValue = False v._channelValue = True v._supplyDefault = True else: # Otherwise, the child is neither network-specific or # channel-specific. v._supplyDefault = False v._help = '' # Clear this so it doesn't print a bazillion times. self.register(attr, v) return v def error(self, value=_NoValueGiven): if hasattr(self, 'errormsg') and value is not _NoValueGiven: try: s = self.errormsg % value except TypeError: s = self.errormsg elif self.__doc__: s = self.__doc__ else: s = """%s has no docstring. If you're getting this message, report it, because we forgot to put a proper help string here."""%\ self._name e = InvalidRegistryValue(utils.str.normalizeWhitespace(s)) e.value = self raise e def getSpecific(self, network=None, channel=None, check=True): """Gets the network-specific and/or channel-specific value of this Value. If `check=True` (the default), this will raise an error if `network` (resp. `channel`) is provided but this Value is not network-specific (resp. channel-specific). If `check=False`, then `network` and/or `channel` may be silently ignored. """ if network and not self._networkValue: if check: raise NonExistentRegistryEntry('%s is not network-specific' % self._name) else: network = None if channel and not self._channelValue: if check: raise NonExistentRegistryEntry('%s is not channel-specific' % self._name) else: channel = None if network and channel: # The complicated case. We want a net+chan specific value, # which may come in three different ways: # # 1. it was set explicitely net+chan # 2. it's inherited from a net specific value (which may itself be # inherited from the base value) # 3. it's inherited from the chan specific value (which is not a # actually a parent in the registry tree, but we need this to # load configuration from old bots). # # The choice between 2 and 3 is done by checking which of the # net-specific and chan-specific values was set explicitely by # a user/admin. In case both were, the net-specific value is used # (there is no particular reason for this, I just think it makes # more sense). network_value = self.get(':' + network) network_channel_value = network_value.get(channel) channel_value = self.get(channel) if network_value._wasSet or network_channel_value._wasSet: # cases 1 and 2 return network_channel_value else: # case 3 return channel_value elif network: return self.get(':' + network) elif channel: return self.get(channel) else: return self def setName(self, *args): if self._name == 'unset': self._lastModified = 0 self.__parent.setName(*args) self._lastModified = monotonic_time() def set(self, s): """Override this with a function to convert a string to whatever type you want, and call self.setValue to set the value.""" self.setValue(s) self._wasSet = True def setValue(self, v): """Check conditions on the actual value type here. I.e., if you're a IntegerLessThanOneHundred (all your values must be integers less than 100) convert to an integer in set() and check that the integer is less than 100 in this method. You *must* call this parent method in your own setValue.""" self._setValue(v, inherited=False) def _setValue(self, v, inherited): """Like setValue, but accepted an extra 'inherited' argument. inherited=True means the value is inherited from the parent, so if the parent gets a new value, this group will get the new value as well.""" self._lastModified = monotonic_time() self.value = v if self._supplyDefault: for (name, child) in list(self._children.items()): if not child._wasSet: child._setValue(v, inherited=True) # We call the callback once everything is clean for callback, args, kwargs in self._callbacks: callback(*args, **kwargs) self._wasSet = not inherited def context(self, value): """Return a context manager object, which sets this variable to a temporary value, and set the previous value back when exiting the context.""" class Context: def __enter__(self2): self2._old_value = self.value self.setValue(value) def __exit__(self2, exc_type, exc_value, traceback): self.setValue(self2._old_value) return Context() def addCallback(self, callback, *args, **kwargs): """Add a callback to the list. A callback is a function that will be called when the value is changed. You can give this function as many extra arguments as you wish, they will be passed to the callback.""" self._callbacks.append((callback, args, kwargs)) def removeCallback(self, callback): """Remove all occurences of this callbacks from the callback list.""" self._callbacks = [x for x in self._callbacks if x[0] is not callback] def __str__(self): return repr(self()) def serialize(self): return encoder(str(self))[0].decode() # We tried many, *many* different syntactic methods here, and this one was # simply the best -- not very intrusive, easily overridden by subclasses, # etc. def __call__(self): if _lastModified > self._lastModified: if self._name in _cache: self.set(_cache[self._name]) return self.value class Boolean(Value): """Value must be either True or False (or On or Off).""" __slots__ = () errormsg = _('Value must be either True or False (or On or Off), not %r.') def set(self, s): try: v = utils.str.toBool(s) except ValueError: if s.strip().lower() == 'toggle': v = not self.value else: self.error(s) self.setValue(v) def setValue(self, v): super(Boolean, self).setValue(bool(v)) class Integer(Value): """Value must be an integer.""" __slots__ = () errormsg = _('Value must be an integer, not %r.') def set(self, s): try: self.setValue(int(s)) except ValueError: self.error(s) class NonNegativeInteger(Integer): """Value must be a non-negative integer.""" __slots__ = () errormsg = _('Value must be a non-negative integer, not %r.') def setValue(self, v): if v < 0: self.error(v) super(NonNegativeInteger, self).setValue(v) class PositiveInteger(NonNegativeInteger): """Value must be positive (non-zero) integer.""" __slots__ = () errormsg = _('Value must be positive (non-zero) integer, not %r.') def setValue(self, v): if not v: self.error(v) super(PositiveInteger, self).setValue(v) class Float(Value): """Value must be a floating-point number.""" __slots__ = () errormsg = _('Value must be a floating-point number, not %r.') def set(self, s): try: self.setValue(float(s)) except ValueError: self.error(s) def setValue(self, v): try: super(Float, self).setValue(float(v)) except ValueError: self.error(v) class PositiveFloat(Float): """Value must be a floating-point number greater than zero.""" __slots__ = () errormsg = _('Value must be a floating-point number greater than zero, ' 'not %r.') def setValue(self, v): if v <= 0: self.error(v) else: super(PositiveFloat, self).setValue(v) class Probability(Float): """Value must be a floating point number in the range [0, 1].""" __slots__ = ('__parent',) errormsg = _('Value must be a floating point number in the range [0, 1], ' 'not %r.') def __init__(self, *args, **kwargs): self.__parent = super(Probability, self) self.__parent.__init__(*args, **kwargs) def setValue(self, v): if 0 <= v <= 1: self.__parent.setValue(v) else: self.error(v) class String(Value): """Value is not a valid Python string.""" __slots__ = () errormsg = _('Value should be a valid Python string, not %r.') def set(self, s): v = s if not v: v = '""' elif v[0] != v[-1] or v[0] not in '\'"': v = repr(v) try: v = utils.safeEval(v) if not isinstance(v, minisix.string_types): raise ValueError self.setValue(v) except ValueError: # This catches utils.safeEval(s) errors too. self.error(s) _printable = string.printable[:-4] def _needsQuoting(self, s): return any([x not in self._printable for x in s]) and s.strip() != s def __str__(self): s = self.value if self._needsQuoting(s): s = repr(s) return s class OnlySomeStrings(String): __slots__ = ('__parent', '__dict__') # unfortunately, __dict__ is needed # to set __doc__. validStrings = () def __init__(self, *args, **kwargs): assert self.validStrings, 'There must be some valid strings. ' \ 'This is a bug.' self.__parent = super(OnlySomeStrings, self) self.__parent.__init__(*args, **kwargs) self.__doc__ = format(_('Valid values include %L.'), list(map(repr, self.validStrings))) self.errormsg = format(_('Valid values include %L, not %%r.'), list(map(repr, self.validStrings))) def help(self): strings = [s for s in self.validStrings if s] return format('%s Valid strings: %L.', self._help, strings) def normalize(self, s): lowered = s.lower() L = list(map(str.lower, self.validStrings)) try: i = L.index(lowered) except ValueError: return s # This is handled in setValue. return self.validStrings[i] def setValue(self, s): v = self.normalize(s) if s in self.validStrings: self.__parent.setValue(v) else: self.error(v) class NormalizedString(String): __slots__ = ('__parent') def __init__(self, default, *args, **kwargs): default = self.normalize(default) self.__parent = super(NormalizedString, self) self.__parent.__init__(default, *args, **kwargs) self._showDefault = False def normalize(self, s): return utils.str.normalizeWhitespace(s.strip()) def set(self, s): s = self.normalize(s) self.__parent.set(s) def setValue(self, s): s = self.normalize(s) self.__parent.setValue(s) def serialize(self): s = self.__parent.serialize() prefixLen = len(self._name) + 2 lines = textwrap.wrap(s, width=76-prefixLen) last = len(lines)-1 for (i, line) in enumerate(lines): if i != 0: line = ' '*prefixLen + line if i != last: line += '\\' lines[i] = line ret = os.linesep.join(lines) return ret class StringSurroundedBySpaces(String): __slots__ = () def setValue(self, v): if v and v.lstrip() == v: v= ' ' + v if v.rstrip() == v: v += ' ' super(StringSurroundedBySpaces, self).setValue(v) class StringWithSpaceOnRight(String): __slots__ = () def setValue(self, v): if v and v.rstrip() == v: v += ' ' super(StringWithSpaceOnRight, self).setValue(v) class Regexp(Value): """Value must be a valid regular expression.""" __slots__ = ('sr', 'value', '__parent') errormsg = _('Value must be a valid regular expression, not %r.') def __init__(self, *args, **kwargs): kwargs['setDefault'] = False self.sr = '' self.value = None self.__parent = super(Regexp, self) 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 def set(self, s): try: if s: self.setValue(utils.str.perlReToPythonRe(s), sr=s) else: self.setValue(None) except ValueError as e: self.error(e) def setValue(self, v, sr=None): if v is None: self.sr = '' self.__parent.setValue(None) elif sr is not None: self.sr = sr self.__parent.setValue(v) else: raise InvalidRegistryValue('Can\'t setValue a regexp, there would be an inconsistency '\ 'between the regexp and the recorded string value.') def __str__(self): self() # Gotta update if we've been reloaded. return self.sr class SeparatedListOf(Value): __slots__ = () List = list Value = Value sorted = False def splitter(self, s): """Override this with a function that takes a string and returns a list of strings.""" raise NotImplementedError def joiner(self, L): """Override this to join the internal list for output.""" raise NotImplementedError def set(self, s): L = self.splitter(s) for (i, s) in enumerate(L): v = self.Value(s, '') L[i] = v() self.setValue(L) def setValue(self, v): super(SeparatedListOf, self).setValue(self.List(v)) def __str__(self): values = self() if self.sorted: values = sorted(values) if values: return self.joiner(values) else: # We must return *something* here, otherwise down along the road we # can run into issues showing users the value if they've disabled # nick prefixes in any of the numerous ways possible. Since the # config parser doesn't care about this space, we'll use it :) return ' ' class SpaceSeparatedListOf(SeparatedListOf): __slots__ = () def splitter(self, s): return s.split() joiner = ' '.join class SpaceSeparatedListOfStrings(SpaceSeparatedListOf): __slots__ = () Value = String class SpaceSeparatedSetOfStrings(SpaceSeparatedListOfStrings): __slots__ = () List = set class CommaSeparatedListOfStrings(SeparatedListOf): __slots__ = () Value = String def splitter(self, s): return re.split(r'\s*,\s*', s) joiner = ', '.join class CommaSeparatedSetOfStrings(SeparatedListOf): __slots__ = () List = set Value = String def splitter(self, s): return re.split(r'\s*,\s*', s) joiner = ', '.join class TemplatedString(String): __slots__ = () requiredTemplates = [] def __init__(self, *args, **kwargs): assert self.requiredTemplates, \ 'There must be some templates. This is a bug.' self.__parent = super(String, self) self.__parent.__init__(*args, **kwargs) def setValue(self, v): def hasTemplate(s): return re.search(r'\$%s\b|\${%s}' % (s, s), v) is not None if utils.iter.all(hasTemplate, self.requiredTemplates): self.__parent.setValue(v) else: self.error(v) class Json(String): __slots__ = () # Json-serializable data def set(self, v): self.setValue(json.loads(v)) def setValue(self, v): super(Json, self).setValue(json.dumps(v)) def __call__(self): return json.loads(super(Json, self).__call__()) class _Context: def __init__(self, var): self._var = var def __enter__(self): self._dict = self._var() return self._dict def __exit__(self, *args): self._var.setValue(self._dict) def editable(self): """Return an editable dict usable within a 'with' statement and committed to the configuration variable at the end.""" return self._Context(self) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/schedule.py0000644000175000017500000001333213634634532016256 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Schedule plugin with a subclass of drivers.IrcDriver in order to be run as a Supybot driver. """ from __future__ import with_statement import time import heapq import functools from threading import Lock from . import drivers, log, world class mytuple(tuple): def __cmp__(self, other): return cmp(self[0], other[0]) def __le__(self, other): return self[0] <= other[0] def __lt__(self, other): return self[0] < other[0] def __gt__(self, other): return self[0] > other[0] def __ge__(self, other): return self[0] >= other[0] class Schedule(drivers.IrcDriver): """An IrcDriver to handling scheduling of events. Events, in this case, are functions accepting no arguments. """ def __init__(self): drivers.IrcDriver.__init__(self) self.schedule = [] self.events = {} self.counter = 0 self.lock = Lock() def reset(self): with self.lock: self.events.clear() self.schedule[:] = [] # We don't reset the counter here because if someone has held an id of # one of the nuked events, we don't want them removing new events with # their old id. def name(self): return 'Schedule' def addEvent(self, f, t, name=None, args=[], kwargs={}): """Schedules an event f to run at time t. name must be hashable and not an int. """ if name is None: name = self.counter self.counter += 1 assert name not in self.events, \ 'An event with the same name has already been scheduled.' with self.lock: self.events[name] = f heapq.heappush(self.schedule, mytuple((t, name, args, kwargs))) return name def removeEvent(self, name): """Removes the event with the given name from the schedule.""" f = self.events.pop(name) # We must heapify here because the heap property may not be preserved # by the above list comprehension. We could, conceivably, just mark # the elements of the heap as removed and ignore them when we heappop, # but that would only save a constant factor (we're already linear for # the listcomp) so I'm not worried about it right now. with self.lock: self.schedule = [x for x in self.schedule if x[1] != name] heapq.heapify(self.schedule) return f def rescheduleEvent(self, name, t): f = self.removeEvent(name) self.addEvent(f, t, name=name) def addPeriodicEvent(self, f, t, name=None, now=True, args=[], kwargs={}, count=None): """Adds a periodic event that is called every t seconds.""" def wrapper(count): try: f(*args, **kwargs) finally: # Even if it raises an exception, let's schedule it. if count[0] is not None: count[0] -= 1 if count[0] is None or count[0] > 0: return self.addEvent(wrapper, time.time() + t, name) wrapper = functools.partial(wrapper, [count]) if now: return wrapper() else: return self.addEvent(wrapper, time.time() + t, name) removePeriodicEvent = removeEvent def run(self): if len(drivers._drivers) == 1 and not world.testing: log.error('Schedule is the only remaining driver, ' 'why do we continue to live?') time.sleep(1) # We're the only driver; let's pause to think. while self.schedule and self.schedule[0][0] < time.time(): with self.lock: (t, name, args, kwargs) = heapq.heappop(self.schedule) f = self.events.pop(name) try: f(*args, **kwargs) except Exception: log.exception('Uncaught exception in scheduled function:') schedule = Schedule() addEvent = schedule.addEvent removeEvent = schedule.removeEvent rescheduleEvent = schedule.rescheduleEvent addPeriodicEvent = schedule.addPeriodicEvent removePeriodicEvent = removeEvent run = schedule.run # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/shlex.py0000644000175000017500000002000613634634532015601 0ustar valval00000000000000"""A lexical analyzer class for simple shell-like syntaxes.""" # Module and documentation by Eric S. Raymond, 21 Dec 1998 # Input stacking and error message cleanup added by ESR, March 2000 # push_source() and pop_source() made explicit by ESR, January 2001. import os.path import sys from .utils import minisix __all__ = ["shlex"] class shlex: "A lexical analyzer class for simple shell-like syntaxes." def __init__(self, instream=None, infile=None): if instream is not None: self.instream = instream self.infile = infile else: self.instream = sys.stdin self.infile = None self.commenters = '#' self.whitespace = ' \t\r\n' self.separators = self.whitespace self.quotes = '\'"' self.state = ' ' self.pushback = [] self.lineno = 1 self.debug = 0 self.token = '' self.backslash = False self.filestack = [] self.source = None if self.debug: print('shlex: reading from %s, line %d' \ % (self.instream, self.lineno)) def push_token(self, tok): "Push a token onto the stack popped by the get_token method" if self.debug >= 1: print("shlex: pushing token " + repr(tok)) self.pushback = [tok] + self.pushback def push_source(self, newstream, newfile=None): "Push an input source onto the lexer's input source stack." self.filestack.insert(0, (self.infile, self.instream, self.lineno)) self.infile = newfile self.instream = newstream self.lineno = 1 if self.debug: if newfile is not None: print('shlex: pushing to file %s' % (self.infile,)) else: print('shlex: pushing to stream %s' % (self.instream,)) def pop_source(self): "Pop the input source stack." self.instream.close() (self.infile, self.instream, self.lineno) = self.filestack[0] self.filestack = self.filestack[1:] if self.debug: print('shlex: popping to %s, line %d' \ % (self.instream, self.lineno)) self.state = ' ' def get_token(self): "Get a token from the input stream (or from stack if it's nonempty)" if self.pushback: tok = self.pushback[0] self.pushback = self.pushback[1:] if self.debug >= 1: print("shlex: popping token " + repr(tok)) return tok # No pushback. Get a token. raw = self.read_token() # Handle inclusions while raw == self.source: spec = self.sourcehook(self.read_token()) if spec: (newfile, newstream) = spec self.push_source(newstream, newfile) raw = self.get_token() # Maybe we got EOF instead? while raw == "": if len(self.filestack) == 0: return "" else: self.pop_source() raw = self.get_token() # Neither inclusion nor EOF if self.debug >= 1: if raw: print("shlex: token=" + repr(raw)) else: print("shlex: token=EOF") return raw def read_token(self): "Read a token from the input stream (no pushback or inclusions)" while True: nextchar = self.instream.read(1) if nextchar == '\n': self.lineno = self.lineno + 1 if self.debug >= 3: print("shlex: in state", repr(self.state), \ "I see character:", repr(nextchar)) if self.state is None: self.token = '' # past end of file break elif self.state == ' ': if not nextchar: self.state = None # end of file break elif nextchar in self.whitespace: if self.debug >= 2: print("shlex: I see whitespace in whitespace state") if self.token: break # emit current token else: continue elif nextchar in self.commenters: self.instream.readline() self.lineno = self.lineno + 1 elif nextchar not in self.separators: self.token = nextchar self.state = 'a' elif nextchar in self.quotes: self.token = nextchar self.state = nextchar else: self.token = nextchar if self.token: break # emit current token else: continue elif self.state in self.quotes: self.token = self.token + nextchar if nextchar == '\\': if self.backslash: self.backslash = False else: self.backslash = True else: if not self.backslash and nextchar == self.state: self.state = ' ' break elif self.backslash: self.backslash = False elif not nextchar: # end of file if self.debug >= 2: print("shlex: I see EOF in quotes state") # XXX what error should be raised here? raise ValueError("No closing quotation") elif self.state == 'a': if not nextchar: self.state = None # end of file break elif nextchar in self.whitespace: if self.debug >= 2: print("shlex: I see whitespace in word state") self.state = ' ' if self.token: break # emit current token else: continue elif nextchar in self.commenters: self.instream.readline() self.lineno = self.lineno + 1 elif nextchar not in self.separators or nextchar in self.quotes: self.token = self.token + nextchar else: self.pushback = [nextchar] + self.pushback if self.debug >= 2: print("shlex: I see punctuation in word state") self.state = ' ' if self.token: break # emit current token else: continue result = self.token self.token = '' if self.debug > 1: if result: print("shlex: raw token=" + repr(result)) else: print("shlex: raw token=EOF") return result def sourcehook(self, newfile): "Hook called on a filename to be sourced." if newfile[0] == '"': newfile = newfile[1:-1] # This implements cpp-like semantics for relative-path inclusion. if isinstance(self.infile, minisix.string_types) and not os.path.isabs(newfile): newfile = os.path.join(os.path.dirname(self.infile), newfile) return (newfile, open(newfile, "r")) def error_leader(self, infile=None, lineno=None): "Emit a C-compiler-like, Emacs-friendly error-message leader." if infile is None: infile = self.infile if lineno is None: lineno = self.lineno return "\"%s\", line %d: " % (infile, lineno) if __name__ == '__main__': if len(sys.argv) == 1: lexer = shlex() else: file = sys.argv[1] with open(file) as fd: lexer = shlex(fd, file) while True: tt = lexer.get_token() if tt: print("Token: " + repr(tt)) else: break limnoria-2020.03.17/src/test.py0000644000175000017500000006332213634634532015445 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2011, James McCoy # 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 gc import os import re import sys import time import shutil import urllib import unittest import functools import threading from . import (callbacks, conf, drivers, httpserver, i18n, ircdb, irclib, ircmsgs, ircutils, log, plugin, registry, utils, world) from .utils import minisix if minisix.PY2: from httplib import HTTPConnection from urllib import splithost, splituser from urllib import URLopener else: from http.client import HTTPConnection from urllib.parse import splithost, splituser from urllib.request import URLopener class verbosity: NONE = 0 EXCEPTIONS = 1 MESSAGES = 2 i18n.import_conf() network = True setuid = True # This is the global list of suites that are to be run. suites = [] timeout = 10 originalCallbacksGetHelp = callbacks.getHelp lastGetHelp = 'x' * 1000 def cachingGetHelp(method, name=None, doc=None): global lastGetHelp lastGetHelp = originalCallbacksGetHelp(method, name, doc) return lastGetHelp callbacks.getHelp = cachingGetHelp real_time = time.time mock_time_offset = 0 def mockTime(): """Wrapper for time.time() that adds an offset, eg. for skipping after a timeout expired.""" return real_time() + mock_time_offset def timeFastForward(extra_offset): global mock_time_offset mock_time_offset += extra_offset def setupMockTime(): time.time = mockTime def teardownMockTime(): time.time = real_time def retry(tries=3): assert tries > 0 def decorator(f): @functools.wraps(f) def newf(self): try: f(self) except AssertionError as e: first_exception = e for _ in range(1, tries): try: f(self) except AssertionError as e: pass else: break else: # All failed raise first_exception return newf return decorator def getTestIrc(name='test'): irc = irclib.Irc(name) # Gotta clear the connect messages (USER, NICK, etc.) while irc.takeMsg(): pass return irc class TimeoutError(AssertionError): def __str__(self): return '%r timed out' % self.args[0] class TestPlugin(callbacks.Plugin): def eval(self, irc, msg, args): """ This is the help for eval. Since Owner doesn't have an eval command anymore, we needed to add this so as not to invalidate any of the tests that depended on that eval command. """ try: irc.reply(repr(eval(' '.join(args)))) except callbacks.ArgumentError: raise except Exception as e: irc.reply(utils.exnToString(e)) # Since we know we don't now need the Irc object, we just give None. This # might break if callbacks.Privmsg ever *requires* the Irc object. TestInstance = TestPlugin(None) conf.registerPlugin('TestPlugin', True, public=False) class SupyTestCase(unittest.TestCase): """This class exists simply for extra logging. It's come in useful in the past.""" def setUp(self): log.critical('Beginning test case %s', self.id()) threads = [t.getName() for t in threading.enumerate()] log.critical('Threads: %L', threads) setupMockTime() unittest.TestCase.setUp(self) def tearDown(self): for irc in world.ircs[:]: irc._reallyDie() teardownMockTime() if sys.version_info < (2, 7, 0): def assertIn(self, member, container, msg=None): """Just like self.assertTrue(a in b), but with a nicer default message.""" if member not in container: standardMsg = '%s not found in %s' % (repr(member), repr(container)) self.fail(self._formatMessage(msg, standardMsg)) def assertNotIn(self, member, container, msg=None): """Just like self.assertTrue(a not in b), but with a nicer default message.""" if member in container: standardMsg = '%s unexpectedly found in %s' % (repr(member), repr(container)) self.fail(self._formatMessage(msg, standardMsg)) def assertIs(self, expr1, expr2, msg=None): """Just like self.assertTrue(a is b), but with a nicer default message.""" if expr1 is not expr2: standardMsg = '%s is not %s' % (repr(expr1), repr(expr2)) self.fail(self._formatMessage(msg, standardMsg)) def assertIsNot(self, expr1, expr2, msg=None): """Just like self.assertTrue(a is not b), but with a nicer default message.""" if expr1 is expr2: standardMsg = 'unexpectedly identical: %s' % (repr(expr1),) self.fail(self._formatMessage(msg, standardMsg)) class PluginTestCase(SupyTestCase): """Subclass this to write a test case for a plugin. See plugins/Plugin/test.py for an example. """ plugins = None cleanConfDir = True cleanDataDir = True config = {} def __init__(self, methodName='runTest'): self.timeout = timeout originalRunTest = getattr(self, methodName) def runTest(self): run = True if hasattr(self, 'irc') and self.irc: for cb in self.irc.callbacks: cbModule = sys.modules[cb.__class__.__module__] if hasattr(cbModule, 'deprecated') and cbModule.deprecated: print('') print('Ignored, %s is deprecated.' % cb.name()) run = False if run: originalRunTest() runTest = utils.python.changeFunctionName(runTest, methodName) setattr(self.__class__, methodName, runTest) SupyTestCase.__init__(self, methodName=methodName) self.originals = {} def setUp(self, nick='test', forceSetup=False): if not forceSetup and \ self.__class__ in (PluginTestCase, ChannelPluginTestCase): # Necessary because there's a test in here that shouldn\'t run. return SupyTestCase.setUp(self) # Just in case, let's do this. Too many people forget to call their # super methods. for irc in world.ircs[:]: irc._reallyDie() # Set conf variables appropriately. conf.supybot.reply.whenAddressedBy.chars.setValue('@') conf.supybot.reply.error.detailed.setValue(True) conf.supybot.reply.whenNotCommand.setValue(True) # Choose a random port for tests using the HTTP server conf.supybot.servers.http.port.setValue(0) self.myVerbose = world.myVerbose def rmFiles(dir): for filename in os.listdir(dir): file = os.path.join(dir, filename) if os.path.isfile(file): os.remove(file) else: shutil.rmtree(file) if self.cleanConfDir: rmFiles(conf.supybot.directories.conf()) if self.cleanDataDir: rmFiles(conf.supybot.directories.data()) ircdb.users.reload() ircdb.ignores.reload() ircdb.channels.reload() if self.plugins is None: raise ValueError('PluginTestCase must have a "plugins" attribute.') self.nick = nick self.prefix = ircutils.joinHostmask(nick, 'user', 'host.domain.tld') self.irc = getTestIrc() MiscModule = plugin.loadPluginModule('Misc') OwnerModule = plugin.loadPluginModule('Owner') ConfigModule = plugin.loadPluginModule('Config') plugin.loadPluginClass(self.irc, MiscModule) plugin.loadPluginClass(self.irc, OwnerModule) plugin.loadPluginClass(self.irc, ConfigModule) if isinstance(self.plugins, str): self.plugins = [self.plugins] else: for name in self.plugins: if name not in ('Owner', 'Misc', 'Config'): module = plugin.loadPluginModule(name, ignoreDeprecation=True) plugin.loadPluginClass(self.irc, module) self.irc.addCallback(TestInstance) for (name, value) in self.config.items(): group = conf.supybot parts = registry.split(name) if parts[0] == 'supybot': parts.pop(0) for part in parts: group = group.get(part) self.originals[group] = group() group.setValue(value) def tearDown(self): if self.__class__ in (PluginTestCase, ChannelPluginTestCase): # Necessary because there's a test in here that shouldn\'t run. return for (group, original) in self.originals.items(): group.setValue(original) ircdb.users.close() ircdb.ignores.close() ircdb.channels.close() SupyTestCase.tearDown(self) self.irc = None gc.collect() def _feedMsg(self, query, timeout=None, to=None, frm=None, usePrefixChar=True, expectException=False): if to is None: to = self.irc.nick if frm is None: frm = self.prefix if timeout is None: timeout = self.timeout if self.myVerbose >= verbosity.MESSAGES: print('') # Extra newline, so it's pretty. prefixChars = conf.supybot.reply.whenAddressedBy.chars() if not usePrefixChar and query[0] in prefixChars: query = query[1:] if minisix.PY2: query = query.encode('utf8') # unicode->str msg = ircmsgs.privmsg(to, query, prefix=frm) if self.myVerbose >= verbosity.MESSAGES: print('Feeding: %r' % msg) if not expectException and self.myVerbose >= verbosity.EXCEPTIONS: conf.supybot.log.stdout.setValue(True) self.irc.feedMsg(msg) fed = real_time() response = self.irc.takeMsg() while response is None and real_time() - fed < timeout: time.sleep(0.01) # So it doesn't suck up 100% cpu. drivers.run() response = self.irc.takeMsg() if self.myVerbose >= verbosity.MESSAGES: print('Response: %r' % response) if not expectException and self.myVerbose >= verbosity.EXCEPTIONS: conf.supybot.log.stdout.setValue(False) return response def getMsg(self, query, **kwargs): return self._feedMsg(query, **kwargs) def feedMsg(self, query, to=None, frm=None): """Just feeds it a message, that's all.""" if to is None: to = self.irc.nick if frm is None: frm = self.prefix self.irc.feedMsg(ircmsgs.privmsg(to, query, prefix=frm)) # These assertError/assertNoError are somewhat fragile. The proper way to # do them would be to use a proxy for the irc object and intercept .error. # But that would be hard, so I don't bother. When this breaks, it'll get # fixed, but not until then. def assertError(self, query, **kwargs): m = self._feedMsg(query, expectException=True, **kwargs) if m is None: raise TimeoutError(query) if lastGetHelp not in m.args[1]: self.assertTrue(m.args[1].startswith('Error:'), '%r did not error: %s' % (query, m.args[1])) return m def assertSnarfError(self, query, **kwargs): return self.assertError(query, usePrefixChar=False, **kwargs) def assertNotError(self, query, **kwargs): m = self._feedMsg(query, **kwargs) if m is None: raise TimeoutError(query) self.assertFalse(m.args[1].startswith('Error:'), '%r errored: %s' % (query, m.args[1])) self.assertFalse(lastGetHelp in m.args[1], '%r returned the help string.' % query) return m def assertSnarfNotError(self, query, **kwargs): return self.assertNotError(query, usePrefixChar=False, **kwargs) def assertHelp(self, query, **kwargs): m = self._feedMsg(query, **kwargs) if m is None: raise TimeoutError(query) msg = m.args[1] if 'more message' in msg: msg = msg[0:-27] # Strip (XXX more messages) self.assertTrue(msg in lastGetHelp, '%s is not the help (%s)' % (m.args[1], lastGetHelp)) return m def assertNoResponse(self, query, timeout=0, **kwargs): m = self._feedMsg(query, timeout=timeout, **kwargs) self.assertFalse(m, 'Unexpected response: %r' % m) return m def assertSnarfNoResponse(self, query, timeout=0, **kwargs): return self.assertNoResponse(query, timeout=timeout, usePrefixChar=False, **kwargs) def assertResponse(self, query, expectedResponse, **kwargs): m = self._feedMsg(query, **kwargs) if m is None: raise TimeoutError(query) self.assertEqual(m.args[1], expectedResponse, '%r != %r' % (expectedResponse, m.args[1])) return m def assertSnarfResponse(self, query, expectedResponse, **kwargs): return self.assertResponse(query, expectedResponse, usePrefixChar=False, **kwargs) def assertRegexp(self, query, regexp, flags=re.I, **kwargs): m = self._feedMsg(query, **kwargs) if m is None: raise TimeoutError(query) self.assertTrue(re.search(regexp, m.args[1], flags), '%r does not match %r' % (m.args[1], regexp)) return m def assertSnarfRegexp(self, query, regexp, flags=re.I, **kwargs): return self.assertRegexp(query, regexp, flags=re.I, usePrefixChar=False, **kwargs) def assertNotRegexp(self, query, regexp, flags=re.I, **kwargs): m = self._feedMsg(query, **kwargs) if m is None: raise TimeoutError(query) self.assertTrue(re.search(regexp, m.args[1], flags) is None, '%r matched %r' % (m.args[1], regexp)) return m def assertSnarfNotRegexp(self, query, regexp, flags=re.I, **kwargs): return self.assertNotRegexp(query, regexp, flags=re.I, usePrefixChar=False, **kwargs) def assertAction(self, query, expectedResponse=None, **kwargs): m = self._feedMsg(query, **kwargs) if m is None: raise TimeoutError(query) self.assertTrue(ircmsgs.isAction(m), '%r is not an action.' % m) if expectedResponse is not None: s = ircmsgs.unAction(m) self.assertEqual(s, expectedResponse, '%r != %r' % (s, expectedResponse)) return m def assertSnarfAction(self, query, expectedResponse=None, **kwargs): return self.assertAction(query, expectedResponse=None, usePrefixChar=False, **kwargs) def assertActionRegexp(self, query, regexp, flags=re.I, **kwargs): m = self._feedMsg(query, **kwargs) if m is None: raise TimeoutError(query) self.assertTrue(ircmsgs.isAction(m)) s = ircmsgs.unAction(m) self.assertTrue(re.search(regexp, s, flags), '%r does not match %r' % (s, regexp)) def assertSnarfActionRegexp(self, query, regexp, flags=re.I, **kwargs): return self.assertActionRegexp(query, regexp, flags=re.I, usePrefixChar=False, **kwargs) _noTestDoc = ('Admin', 'Channel', 'Config', 'Misc', 'Owner', 'User', 'TestPlugin') def TestDocumentation(self): if self.__class__ in (PluginTestCase, ChannelPluginTestCase): return for cb in self.irc.callbacks: name = cb.name() if ((name in self._noTestDoc) and \ not name.lower() in self.__class__.__name__.lower()): continue self.assertTrue(sys.modules[cb.__class__.__name__].__doc__, '%s has no module documentation.' % name) if hasattr(cb, 'isCommandMethod'): for attr in dir(cb): if cb.isCommandMethod(attr) and \ attr == callbacks.canonicalName(attr): self.assertTrue(getattr(cb, attr, None).__doc__, '%s.%s has no help.' % (name, attr)) class ChannelPluginTestCase(PluginTestCase): channel = '#test' def setUp(self, nick='test', forceSetup=False): if not forceSetup and \ self.__class__ in (PluginTestCase, ChannelPluginTestCase): return PluginTestCase.setUp(self) self.irc.feedMsg(ircmsgs.join(self.channel, prefix=self.prefix)) m = self.irc.takeMsg() self.assertFalse(m is None, 'No message back from joining channel.') self.assertEqual(m.command, 'MODE') m = self.irc.takeMsg() self.assertFalse(m is None, 'No message back from joining channel.') self.assertEqual(m.command, 'MODE') m = self.irc.takeMsg() self.assertFalse(m is None, 'No message back from joining channel.') self.assertEqual(m.command, 'WHO') def _feedMsg(self, query, timeout=None, to=None, frm=None, private=False, usePrefixChar=True, expectException=False): if to is None: if private: to = self.irc.nick else: to = self.channel if frm is None: frm = self.prefix if timeout is None: timeout = self.timeout if self.myVerbose >= verbosity.MESSAGES: print('') # Newline, just like PluginTestCase. prefixChars = conf.supybot.reply.whenAddressedBy.chars() if query[0] not in prefixChars and usePrefixChar: query = prefixChars[0] + query if minisix.PY2 and isinstance(query, unicode): query = query.encode('utf8') # unicode->str if not expectException and self.myVerbose >= verbosity.EXCEPTIONS: conf.supybot.log.stdout.setValue(True) msg = ircmsgs.privmsg(to, query, prefix=frm) if self.myVerbose >= verbosity.MESSAGES: print('Feeding: %r' % msg) self.irc.feedMsg(msg) fed = real_time() response = self.irc.takeMsg() while response is None and real_time() - fed < timeout: time.sleep(0.1) drivers.run() response = self.irc.takeMsg() if response is not None: if response.command == 'PRIVMSG': args = list(response.args) # Strip off nick: at beginning of response. if args[1].startswith(self.nick) or \ args[1].startswith(ircutils.nickFromHostmask(self.prefix)): try: args[1] = args[1].split(' ', 1)[1] except IndexError: # Odd. We'll skip this. pass ret = ircmsgs.privmsg(*args) else: ret = response else: ret = None if self.myVerbose >= verbosity.MESSAGES: print('Returning: %r' % ret) if not expectException and self.myVerbose >= verbosity.EXCEPTIONS: conf.supybot.log.stdout.setValue(False) return ret def feedMsg(self, query, to=None, frm=None, private=False): """Just feeds it a message, that's all.""" if to is None: if private: to = self.irc.nick else: to = self.channel if frm is None: frm = self.prefix self.irc.feedMsg(ircmsgs.privmsg(to, query, prefix=frm)) class TestRequestHandler(httpserver.SupyHTTPRequestHandler): def __init__(self, rfile, wfile, *args, **kwargs): self._headers_mode = True self.rfile = rfile self.wfile = wfile self.handle_one_request() def send_response(self, code): assert self._headers_mode self._response = code def send_headers(self, name, value): assert self._headers_mode self._headers[name] = value def end_headers(self): assert self._headers_mode self._headers_mode = False def do_X(self, *args, **kwargs): assert httpserver.http_servers, \ 'The HTTP server is not started.' self.server = httpserver.http_servers[0] httpserver.SupyHTTPRequestHandler.do_X(self, *args, **kwargs) httpserver.http_servers = [httpserver.TestSupyHTTPServer()] # Partially stolen from the standard Python library :) def open_http(url, data=None): """Use HTTP protocol.""" user_passwd = None proxy_passwd= None if isinstance(url, str): host, selector = splithost(url) if host: user_passwd, host = splituser(host) host = urllib.unquote(host) realhost = host else: host, selector = url # check whether the proxy contains authorization information proxy_passwd, host = splituser(host) # now we proceed with the url we want to obtain urltype, rest = urllib.splittype(selector) url = rest user_passwd = None if urltype.lower() != 'http': realhost = None else: realhost, rest = splithost(rest) if realhost: user_passwd, realhost = splituser(realhost) if user_passwd: selector = "%s://%s%s" % (urltype, realhost, rest) if urllib.proxy_bypass(realhost): host = realhost #print "proxy via http:", host, selector if not host: raise IOError('http error', 'no host given') if proxy_passwd: import base64 proxy_auth = base64.b64encode(proxy_passwd).strip() else: proxy_auth = None if user_passwd: import base64 auth = base64.b64encode(user_passwd).strip() else: auth = None c = FakeHTTPConnection(host) if data is not None: c.putrequest('POST', selector) c.putheader('Content-Type', 'application/x-www-form-urlencoded') c.putheader('Content-Length', '%d' % len(data)) else: c.putrequest('GET', selector) if proxy_auth: c.putheader('Proxy-Authorization', 'Basic %s' % proxy_auth) if auth: c.putheader('Authorization', 'Basic %s' % auth) if realhost: c.putheader('Host', realhost) for args in URLopener().addheaders: c.putheader(*args) c.endheaders() return c class FakeHTTPConnection(HTTPConnection): _data = '' _headers = {} def __init__(self, rfile, wfile): HTTPConnection.__init__(self, 'localhost') self.rfile = rfile self.wfile = wfile def send(self, data): self.wfile.write(data) #def putheader(self, name, value): # self._headers[name] = value #def connect(self, *args, **kwargs): # self.sock = self.wfile #def getresponse(self, *args, **kwargs): # pass class HTTPPluginTestCase(PluginTestCase): def setUp(self): PluginTestCase.setUp(self, forceSetup=True) def request(self, url, method='GET', read=True, data={}): assert url.startswith('/') wfile = minisix.io.BytesIO() rfile = minisix.io.BytesIO() connection = FakeHTTPConnection(wfile, rfile) connection.putrequest(method, url) connection.endheaders() rfile.seek(0) handler = TestRequestHandler(rfile, wfile) wfile.seek(0) if read: return (handler._response, wfile.read()) else: return handler._response def assertHTTPResponse(self, uri, expectedResponse, **kwargs): response = self.request(uri, read=False, **kwargs) self.assertEqual(response, expectedResponse) def assertNotHTTPResponse(self, uri, expectedResponse, **kwargs): response = self.request(uri, read=False, **kwargs) self.assertNotEqual(response, expectedResponse) class ChannelHTTPPluginTestCase(ChannelPluginTestCase, HTTPPluginTestCase): def setUp(self): ChannelPluginTestCase.setUp(self, forceSetup=True) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/unpreserve.py0000644000175000017500000000734013634634532016662 0ustar valval00000000000000### # Copyright (c) 2004-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### class Reader(object): """Opens a file and reads it in blocks, using the `Creator` class to instantiate an object for each of the blocks. The format is of the form: ``` entry_type entry_id1 command1 arg1 arg1b command2 arg2 entry_type entry_id2 command3 arg3 arg13 ``` When reading this file, the `Creator` will be instantiated with the provided args and kwargs to a `creator` object, whose methods will then be called in this pattern: ``` creator.entry_type("entry_id1", 1) creator.command1("arg1 arg1b", 2) creator.command2("arg2", 3) creator.finish() creator.entry_type'entry_id2", 5) creator.command3("arg3 arg3b", 6) creator.finish() ``` """ def __init__(self, Creator, *args, **kwargs): self.Creator = Creator self.args = args self.kwargs = kwargs self.creator = None self.modifiedCreator = False self.indent = None def normalizeCommand(self, s): return s.lower() def readFile(self, filename): self.read(open(filename)) def read(self, fd): lineno = 0 for line in fd: lineno += 1 if not line.strip(): continue line = line.rstrip('\r\n') line = line.expandtabs() s = line.lstrip(' ') indent = len(line) - len(s) if indent != self.indent: # New indentation level. if self.creator is not None: self.creator.finish() self.creator = self.Creator(*self.args, **self.kwargs) self.modifiedCreator = False self.indent = indent (command, rest) = s.split(None, 1) command = self.normalizeCommand(command) self.modifiedCreator = True if hasattr(self.creator, command): command = getattr(self.creator, command) command(rest, lineno) else: self.creator.badCommand(command, rest, lineno) if self.modifiedCreator: self.creator.finish() # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/utils/0000755000175000017500000000000013634634547015254 5ustar valval00000000000000limnoria-2020.03.17/src/utils/__init__.py0000644000175000017500000000504613634634532017364 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2008, James McCoy # 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 . import minisix ### # csv.{join,split} -- useful functions that should exist. ### import csv def join(L): fd = minisix.io.StringIO() writer = csv.writer(fd) writer.writerow(L) return fd.getvalue().rstrip('\r\n') def split(s): fd = minisix.io.StringIO(s) reader = csv.reader(fd) return next(reader) csv.join = join csv.split = split builtins = (__builtins__ if isinstance(__builtins__, dict) else __builtins__.__dict__) # We use this often enough that we're going to stick it in builtins. def force(x): if callable(x): return x() else: return x builtins['force'] = force internationalization = builtins.get('supybotInternationalization', None) # These imports need to happen below the block above, so things get put into # __builtins__ appropriately. from .gen import * from . import crypt, error, file, iter, net, python, seq, str, transaction, web # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/utils/crypt.py0000644000175000017500000000324113634634532016761 0ustar valval00000000000000### # Copyright (c) 2008, James McCoy # 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 hashlib import md5 from hashlib import sha1 as sha # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/utils/error.py0000644000175000017500000000363413634634532016757 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import os from . import gen class Error(Exception): def __init__(self, msg, e=None): self.msg = msg self.e = e def __str__(self): if self.e is not None: return os.linesep.join([self.msg, gen.exnToString(self.e)]) else: return self.msg # vim:set shiftwidth=4 softtabstop=8 expandtab textwidth=78: limnoria-2020.03.17/src/utils/file.py0000644000175000017500000002215113634634532016540 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2008, James McCoy # 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 os import time import codecs import random import shutil import os.path from . import crypt def sanitizeName(filename): """Removes / from filenames and escapes them if they are '.' or '..'.""" filename = filename.replace('/', '') if filename == '.': return '_' elif filename == '..': return '__' else: return filename def contents(filename): with open(filename) as fd: return fd.read() def open_mkdir(filename, mode='wb', *args, **kwargs): """filename -> file object. Returns a file object for filename, creating as many directories as may be necessary. I.e., if the filename is ./foo/bar/baz, and . exists, and ./foo exists, but ./foo/bar does not exist, bar will be created before opening baz in it. """ if mode not in ('w', 'wb'): raise ValueError('utils.file.open expects to write.') (dirname, basename) = os.path.split(filename) os.makedirs(dirname) return open(filename, mode, *args, **kwargs) def copy(src, dst): """src, dst -> None Copies src to dst, using this module's 'open' function to open dst. """ srcfd = open(src) dstfd = open_mkdir(dst, 'wb') shutil.copyfileobj(srcfd, dstfd) srcfd.close() dstfd.close() def writeLine(fd, line): fd.write(line) if not line.endswith('\n'): fd.write('\n') def readLines(filename): fd = open(filename) try: return [line.rstrip('\r\n') for line in fd.readlines()] finally: fd.close() def touch(filename): fd = open(filename, 'w') fd.close() def mktemp(suffix=''): """Gives a decent random string, suitable for a filename.""" r = random.Random() m = crypt.md5(suffix.encode('utf8')) r.seed(time.time()) s = str(r.getstate()) period = random.random() now = start = time.time() while start + period < now: time.sleep() # Induce a context switch, if possible. now = time.time() m.update(str(random.random())) m.update(s) m.update(str(now)) s = m.hexdigest() return crypt.sha((s + str(time.time())).encode('utf8')).hexdigest()+suffix def nonCommentLines(fd): for line in fd: if not line.startswith('#'): yield line def nonEmptyLines(fd): return filter(str.strip, fd) def nonCommentNonEmptyLines(fd): return nonEmptyLines(nonCommentLines(fd)) def chunks(fd, size): return iter(lambda : fd.read(size), '') ## chunk = fd.read(size) ## while chunk: ## yield chunk ## chunk = fd.read(size) class AtomicFile(object): """Used for files that need to be atomically written -- i.e., if there's a failure, the original file remains, unmodified. mode must be 'w' or 'wb'""" class default(object): # Holder for values. # Callables? tmpDir = None backupDir = None makeBackupIfSmaller = True allowEmptyOverwrite = True def __init__(self, filename, mode='w', allowEmptyOverwrite=None, makeBackupIfSmaller=None, tmpDir=None, backupDir=None, encoding=None): if tmpDir is None: tmpDir = force(self.default.tmpDir) if backupDir is None: backupDir = force(self.default.backupDir) if makeBackupIfSmaller is None: makeBackupIfSmaller = force(self.default.makeBackupIfSmaller) if allowEmptyOverwrite is None: allowEmptyOverwrite = force(self.default.allowEmptyOverwrite) if encoding is None and 'b' not in mode: encoding = 'utf8' if mode not in ('w', 'wb'): raise ValueError(format('Invalid mode: %q', mode)) self.rolledback = False self.allowEmptyOverwrite = allowEmptyOverwrite self.makeBackupIfSmaller = makeBackupIfSmaller self.filename = filename self.backupDir = backupDir if tmpDir is None: # If not given a tmpDir, we'll just put a random token on the end # of our filename and put it in the same directory. self.tempFilename = '%s.%s' % (self.filename, mktemp()) else: # If given a tmpDir, we'll get the basename (just the filename, no # directory), put our random token on the end, and put it in tmpDir tempFilename = '%s.%s' % (os.path.basename(self.filename), mktemp()) self.tempFilename = os.path.join(tmpDir, tempFilename) # This doesn't work because of the uncollectable garbage effect. # self.__parent = super(AtomicFile, self) self._fd = codecs.open(self.tempFilename, mode, encoding=encoding) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): if exc_type: self.rollback() else: self.close() @property def closed(self): return self._fd.closed def write(self, data): return self._fd.write(data) def writelines(self, lines): return self._fd.writelines(lines) def rollback(self): if not self.closed: self._fd.close() if os.path.exists(self.tempFilename): os.remove(self.tempFilename) self.rolledback = True def seek(self, offset): return self._fd.seek(offset) def tell(self): return self._fd.tell() def flush(self): return self._fd.flush() def close(self): if not self.rolledback: self._fd.close() # We don't mind writing an empty file if the file we're overwriting # doesn't exist. newSize = os.path.getsize(self.tempFilename) originalExists = os.path.exists(self.filename) if newSize or self.allowEmptyOverwrite or not originalExists: if originalExists: oldSize = os.path.getsize(self.filename) if self.makeBackupIfSmaller and newSize < oldSize and \ self.backupDir != '/dev/null': now = int(time.time()) backupFilename = '%s.backup.%s' % (self.filename, now) if self.backupDir is not None: backupFilename = os.path.basename(backupFilename) backupFilename = os.path.join(self.backupDir, backupFilename) shutil.copy(self.filename, backupFilename) # We use shutil.move here instead of os.rename because # the latter doesn't work on Windows when self.filename # (the target) already exists. shutil.move handles those # intricacies for us. # This raises IOError if we can't write to the file. Since # in *nix, it only takes write perms to the *directory* to # rename a file (and shutil.move will use os.rename if # possible), we first check if we have the write permission # and only then do we write. fd = open(self.filename, 'a') fd.close() shutil.move(self.tempFilename, self.filename) else: raise ValueError('AtomicFile.close called after rollback.') def __del__(self): # We rollback because if we're deleted without being explicitly closed, # that's bad. We really should log this here, but as of yet we've got # no logging facility in utils. I've got some ideas for this, though. self.rollback() # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/utils/gen.py0000644000175000017500000002654013634634532016400 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2008, James McCoy # 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 __future__ import print_function import os import sys import ast import textwrap import warnings import functools import traceback import collections.abc from . import crypt from .str import format from .file import mktemp from . import minisix from . import internationalization as _ def warn_non_constant_time(f): @functools.wraps(f) def newf(*args, **kwargs): # This method takes linear time whereas the subclass could probably # do it in constant time. warnings.warn('subclass of IterableMap does provide an efficient ' 'implementation of %s' % f.__name__, DeprecationWarning) return f(*args, **kwargs) return newf def abbrev(strings, d=None): """Returns a dictionary mapping unambiguous abbreviations to full forms.""" def eachSubstring(s): for i in range(1, len(s)+1): yield s[:i] if len(strings) != len(set(strings)): raise ValueError( 'strings given to utils.abbrev have duplicates: %r' % strings) if d is None: d = {} for s in strings: for abbreviation in eachSubstring(s): if abbreviation not in d: d[abbreviation] = s else: if abbreviation not in strings: d[abbreviation] = None removals = [] for key in d: if d[key] is None: removals.append(key) for key in removals: del d[key] return d def timeElapsed(elapsed, short=False, leadingZeroes=False, years=True, weeks=True, days=True, hours=True, minutes=True, seconds=True): """Given seconds, returns a string with an English description of the amount of time passed. leadingZeroes determines whether 0 days, 0 hours, etc. will be printed; the others determine what larger time periods should be used. """ ret = [] before = False def Format(s, i): if i or leadingZeroes or ret: if short: ret.append('%s%s' % (i, s[0])) else: ret.append(format('%n', (i, s))) elapsed = int(elapsed) # Handle negative times if elapsed < 0: before = True elapsed = -elapsed assert years or weeks or days or \ hours or minutes or seconds, 'One flag must be True' if years: (yrs, elapsed) = (elapsed // 31536000, elapsed % 31536000) Format(_('year'), yrs) if weeks: (wks, elapsed) = (elapsed // 604800, elapsed % 604800) Format(_('week'), wks) if days: (ds, elapsed) = (elapsed // 86400, elapsed % 86400) Format(_('day'), ds) if hours: (hrs, elapsed) = (elapsed // 3600, elapsed % 3600) Format(_('hour'), hrs) if minutes or seconds: (mins, secs) = (elapsed // 60, elapsed % 60) if leadingZeroes or mins: Format(_('minute'), mins) if seconds: leadingZeroes = True Format(_('second'), secs) if not ret: raise ValueError('Time difference not great enough to be noted.') result = '' if short: result = ' '.join(ret) else: result = format('%L', ret) if before: result = _('%s ago') % result return result def findBinaryInPath(s): """Return full path of a binary if it's in PATH, otherwise return None.""" cmdLine = None for dir in os.getenv('PATH').split(':'): filename = os.path.join(dir, s) if os.path.exists(filename): cmdLine = filename break return cmdLine def sortBy(f, L): """Uses the decorate-sort-undecorate pattern to sort L by function f.""" for (i, elt) in enumerate(L): L[i] = (f(elt), i, elt) L.sort() for (i, elt) in enumerate(L): L[i] = L[i][2] def saltHash(password, salt=None, hash='sha'): if salt is None: salt = mktemp()[:8] if hash == 'sha': hasher = crypt.sha elif hash == 'md5': hasher = crypt.md5 return '|'.join([salt, hasher((salt + password).encode('utf8')).hexdigest()]) _astStr2 = ast.Str if minisix.PY2 else ast.Bytes def safeEval(s, namespace=None): """Evaluates s, safely. Useful for turning strings into tuples/lists/etc. without unsafely using eval().""" try: node = ast.parse(s, mode='eval').body except SyntaxError as e: raise ValueError('Invalid string: %s.' % e) def checkNode(node): if node.__class__ is ast.Expr: node = node.value if node.__class__ in (ast.Num, ast.Str, _astStr2): return True elif node.__class__ in (ast.List, ast.Tuple): return all([checkNode(x) for x in node.elts]) elif node.__class__ is ast.Dict: return all([checkNode(x) for x in node.values]) and \ all([checkNode(x) for x in node.values]) elif node.__class__ is ast.Name: if namespace is None and node.id in ('True', 'False', 'None'): # For Python < 3.4, which does not have NameConstant. return True elif namespace is not None and node.id in namespace: return True else: return False elif sys.version_info[0:2] >= (3, 4) and \ node.__class__ is ast.NameConstant: return True elif sys.version_info[0:2] >= (3, 8) and \ node.__class__ is ast.Constant: return True else: return False if checkNode(node): if namespace is None: return eval(s, namespace, namespace) else: # Probably equivalent to eval() because checkNode(node) is True, # but it's an extra security. return ast.literal_eval(node) else: raise ValueError(format('Unsafe string: %q', s)) def exnToString(e): """Turns a simple exception instance into a string (better than str(e))""" strE = str(e) if strE: return '%s: %s' % (e.__class__.__name__, strE) else: return e.__class__.__name__ class IterableMap(object): """Define .items() in a class and subclass this to get the other iters. """ def items(self): if minisix.PY3 and hasattr(self, 'iteritems'): # For old plugins return self.iteritems() # avoid 2to3 else: raise NotImplementedError() __iter__ = items def keys(self): for (key, __) in self.items(): yield key def values(self): for (__, value) in self.items(): yield value @warn_non_constant_time def __len__(self): ret = 0 for __ in self.items(): ret += 1 return ret @warn_non_constant_time def __bool__(self): for __ in self.items(): return True return False __nonzero__ = __bool__ class InsensitivePreservingDict(collections.abc.MutableMapping): def key(self, s): """Override this if you wish.""" if s is not None: s = s.lower() return s def __init__(self, dict=None, key=None): if key is not None: self.key = key self.data = {} if dict is not None: self.update(dict) def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.data) def fromkeys(cls, keys, s=None, dict=None, key=None): d = cls(dict=dict, key=key) for key in keys: d[key] = s return d fromkeys = classmethod(fromkeys) def __getitem__(self, k): return self.data[self.key(k)][1] def __setitem__(self, k, v): self.data[self.key(k)] = (k, v) def __delitem__(self, k): del self.data[self.key(k)] def __iter__(self): return iter(self.data) def __len__(self): return len(self.data) def items(self): return self.data.values() def items(self): return self.data.values() def keys(self): L = [] for (k, __) in self.items(): L.append(k) return L def __reduce__(self): return (self.__class__, (dict(self.data.values()),)) class NormalizingSet(set): def __init__(self, iterable=()): iterable = list(map(self.normalize, iterable)) super(NormalizingSet, self).__init__(iterable) def normalize(self, x): return x def add(self, x): return super(NormalizingSet, self).add(self.normalize(x)) def remove(self, x): return super(NormalizingSet, self).remove(self.normalize(x)) def discard(self, x): return super(NormalizingSet, self).discard(self.normalize(x)) def __contains__(self, x): return super(NormalizingSet, self).__contains__(self.normalize(x)) has_key = __contains__ def stackTrace(frame=None, compact=True): if frame is None: frame = sys._getframe() if compact: L = [] while frame: lineno = frame.f_lineno funcname = frame.f_code.co_name filename = os.path.basename(frame.f_code.co_filename) L.append('[%s|%s|%s]' % (filename, funcname, lineno)) frame = frame.f_back return textwrap.fill(' '.join(L)) else: return traceback.format_stack(frame) def callTracer(fd=None, basename=True): if fd is None: fd = sys.stdout def tracer(frame, event, __): if event == 'call': code = frame.f_code lineno = frame.f_lineno funcname = code.co_name filename = code.co_filename if basename: filename = os.path.basename(filename) print('%s: %s(%s)' % (filename, funcname, lineno), file=fd) return tracer # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/utils/iter.py0000644000175000017500000001137013634634532016565 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from __future__ import division import random from itertools import * from . import minisix # For old plugins ifilter = filter def filterfalse(p, L): if p is None: p = lambda x:x return filter(lambda x:not p(x), L) ifilterfalse = filterfalse imap = map def len(iterable): """Returns the length of an iterator.""" i = 0 for _ in iterable: i += 1 return i def trueCycle(iterable): while True: yielded = False for x in iterable: yield x yielded = True if not yielded: raise StopIteration def partition(p, iterable): """Partitions an iterable based on a predicate p. Returns a (yes,no) tuple""" no = [] yes = [] for elt in iterable: if p(elt): yes.append(elt) else: no.append(elt) return (yes, no) def any(p, iterable): """Returns true if any element in iterable satisfies predicate p.""" for elt in filter(p, iterable): return True else: return False def all(p, iterable): """Returns true if all elements in iterable satisfy predicate p.""" for elt in filterfalse(p, iterable): return False else: return True def choice(iterable): if isinstance(iterable, (list, tuple)): return random.choice(iterable) else: n = 1 found = False for x in iterable: if random.random() < 1/n: ret = x found = True n += 1 if not found: raise IndexError return ret def flatten(iterable, strings=False): """Flattens a list of lists into a single list. See the test for examples. """ for elt in iterable: if not strings and isinstance(elt, minisix.string_types): yield elt else: try: for x in flatten(elt): yield x except TypeError: yield elt def split(isSeparator, iterable, maxsplit=-1, yieldEmpty=False): """split(isSeparator, iterable, maxsplit=-1, yieldEmpty=False) Splits an iterator based on a predicate isSeparator.""" if isinstance(isSeparator, minisix.string_types): f = lambda s: s == isSeparator else: f = isSeparator acc = [] for element in iterable: if maxsplit == 0 or not f(element): acc.append(element) else: maxsplit -= 1 if acc or yieldEmpty: yield acc acc = [] if acc or yieldEmpty: yield acc def ilen(iterable): i = 0 for _ in iterable: i += 1 return i def startswith(long_, short): longI = iter(long_) shortI = iter(short) try: while True: if next(shortI) != next(longI): return False except StopIteration: return True def limited(iterable, limit): i = limit iterable = iter(iterable) try: while i: yield next(iterable) i -= 1 except StopIteration: raise ValueError('Expected %s elements in iterable (%r), got %s.' % \ (limit, iterable, limit-i)) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/utils/math_evaluator.py0000644000175000017500000001405613634634532020641 0ustar valval00000000000000### # Copyright (c) 2019, Valentin Lorentz # 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. ### """A safe evaluator for math expressions using Python syntax. Unlike eval(), it can be run on untrusted input. """ import ast import math import cmath import operator class InvalidNode(Exception): pass def filter_module(module, safe_names): return dict([ (name, getattr(module, name)) for name in safe_names if hasattr(module, name) ]) UNARY_OPS = { ast.UAdd: lambda x: x, ast.USub: lambda x: -x, } BIN_OPS = { ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv, ast.Mod: operator.mod, ast.Pow: operator.pow, ast.BitXor: operator.xor, ast.BitOr: operator.or_, ast.BitAnd: operator.and_, } MATH_CONSTANTS = 'e inf nan pi tau'.split() SAFE_MATH_FUNCTIONS = ( 'acos acosh asin asinh atan atan2 atanh copysign cos cosh degrees erf ' 'erfc exp expm1 fabs fmod frexp fsum gamma hypot ldexp lgamma ' 'log1p log2 modf pow radians remainder sin sinh tan tanh' ).split() SAFE_CMATH_FUNCTIONS = ( 'acos acosh asin asinh atan atanh cos cosh exp inf infj ' 'nanj phase polar rect sin sinh tan tanh tau' ).split() SAFE_ENV = filter_module(math, MATH_CONSTANTS + SAFE_MATH_FUNCTIONS) SAFE_ENV.update(filter_module(cmath, SAFE_CMATH_FUNCTIONS)) def _sqrt(x): if isinstance(x, complex) or x < 0: return cmath.sqrt(x) else: return math.sqrt(x) def _log(x): if isinstance(x, complex) or x < 0: return cmath.log(x) else: return math.log(x) def _log10(x): if isinstance(x, complex) or x < 0: return cmath.log10(x) else: return math.log10(x) def _cbrt(x): return math.pow(x, 1.0/3) def _factorial(x): if x<=10000: return float(math.factorial(x)) else: raise Exception('factorial argument too large') SAFE_ENV.update({ 'i': 1j, 'abs': abs, 'max': max, 'min': min, 'round': lambda x, y=0: round(x, int(y)), 'factorial': _factorial, 'sqrt': _sqrt, 'cbrt': _cbrt, 'log': _log, 'log10': _log10, 'ceil': lambda x: float(math.ceil(x)), 'floor': lambda x: float(math.floor(x)), }) UNSAFE_ENV = SAFE_ENV.copy() # Add functions that return integers UNSAFE_ENV.update(filter_module(math, 'ceil floor factorial gcd'.split())) # It would be nice if ast.literal_eval used a visitor so we could subclass # to extend it, but it doesn't, so let's reimplement it entirely. class SafeEvalVisitor(ast.NodeVisitor): def __init__(self, allow_ints, variables=None): self._allow_ints = allow_ints self._env = UNSAFE_ENV if allow_ints else SAFE_ENV if variables: self._env = self._env.copy() self._env.update(variables) def _convert_num(self, x): """Converts numbers to complex if ints are not allowed.""" if self._allow_ints: return x else: x = complex(x) if x.imag == 0: x = x.real # Need to use string-formatting here instead of str() because # use of str() on large numbers loses information: # str(float(33333333333333)) => '3.33333333333e+13' # float('3.33333333333e+13') => 33333333333300.0 return float('%.16f' % x) else: return x def visit_Expression(self, node): return self.visit(node.body) def visit_Num(self, node): return self._convert_num(node.n) def visit_Name(self, node): id_ = node.id.lower() if id_ in self._env: return self._env[id_] else: raise NameError(node.id) def visit_Call(self, node): func = self.visit(node.func) args = map(self.visit, node.args) # TODO: keywords? return func(*args) def visit_UnaryOp(self, node): op = UNARY_OPS.get(node.op.__class__) if op: return op(self.visit(node.operand)) else: raise InvalidNode('illegal operator %s' % node.op.__class__.__name__) def visit_BinOp(self, node): op = BIN_OPS.get(node.op.__class__) if op: return op(self.visit(node.left), self.visit(node.right)) else: raise InvalidNode('illegal operator %s' % node.op.__class__.__name__) def generic_visit(self, node): raise InvalidNode('illegal construct %s' % node.__class__.__name__) def safe_eval(text, allow_ints, variables=None): node = ast.parse(text, mode='eval') return SafeEvalVisitor(allow_ints, variables=variables).visit(node) limnoria-2020.03.17/src/utils/minisix.py0000644000175000017500000000750513634634532017307 0ustar valval00000000000000### # Copyright (c) 2014, Valentin Lorentz # 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. ### """Restricted equivalent to six.""" from __future__ import division import sys import warnings if sys.version_info[0] >= 3: PY2 = False PY3 = True intern = sys.intern integer_types = (int,) string_types = (str,) long = int import io import pickle import queue u = lambda x:x L = lambda x:x def make_datetime_utc(dt): import datetime return dt.replace(tzinfo=datetime.timezone.utc) def timedelta__totalseconds(td): return td.total_seconds() if sys.version_info >= (3, 3): def datetime__timestamp(dt): return dt.timestamp() else: def datetime__timestamp(dt): import datetime td = dt - datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc) return timedelta__totalseconds(td) else: PY2 = True PY3 = False if isinstance(__builtins__, dict): intern = __builtins__['intern'] else: intern = __builtins__.intern integer_types = (int, long) string_types = (basestring,) long = long class io: # cStringIO is buggy with Python 2.6 ( # see http://paste.progval.net/show/227/ ) # and it does not handle unicode objects in Python 2.x from StringIO import StringIO from cStringIO import StringIO as BytesIO import cPickle as pickle import Queue as queue u = lambda x:x.decode('utf8') L = lambda x:long(x) def make_datetime_utc(dt): warnings.warn('Timezones are not available on this version of ' 'Python and may lead to incorrect results. You should ' 'consider upgrading to Python 3.') return dt.replace(tzinfo=None) if sys.version_info >= (2, 7): def timedelta__totalseconds(td): return td.total_seconds() else: def timedelta__totalseconds(td): return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6 def datetime__timestamp(dt): import datetime warnings.warn('Timezones are not available on this version of ' 'Python and may lead to incorrect results. You should ' 'consider upgrading to Python 3.') return timedelta__totalseconds(dt - datetime.datetime(1970, 1, 1)) limnoria-2020.03.17/src/utils/net.py0000644000175000017500000001621113634634532016407 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2011, 2013, James McCoy # 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. ### """ Simple utility modules. """ import re import ssl import socket import hashlib from .web import _ipAddr, _domain emailRe = re.compile(r"^(\w&.+-]+!)*[\w&.+-]+@(%s|%s)$" % (_domain, _ipAddr), re.I) def getAddressFromHostname(host, port=None, attempt=0): addrinfo = socket.getaddrinfo(host, port) addresses = [] for (family, socktype, proto, canonname, sockaddr) in addrinfo: if sockaddr[0] not in addresses: addresses.append(sockaddr[0]) return addresses[attempt % len(addresses)] def getSocket(host, port=None, socks_proxy=None, vhost=None, vhostv6=None): """Returns a socket of the correct AF_INET type (v4 or v6) in order to communicate with host. """ if not socks_proxy: addrinfo = socket.getaddrinfo(host, port) host = addrinfo[0][4][0] if socks_proxy: import socks s = socks.socksocket() hostname, port = socks_proxy.rsplit(':', 1) s.setproxy(socks.PROXY_TYPE_SOCKS5, hostname, int(port), rdns=True) return s if isIPV4(host): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if vhost: s.bind((vhost, 0)) return s elif isIPV6(host): s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) if vhostv6: s.bind((vhostv6, 0)) return s else: raise socket.error('Something wonky happened.') def isSocketAddress(s): if ':' in s: host, port = s.rsplit(':', 1) try: int(port) sock = getSocket(host, port) return True except (ValueError, socket.error): pass return False def isIP(s): """Returns whether or not a given string is an IP address. >>> isIP('255.255.255.255') 1 >>> isIP('::1') 0 """ return isIPV4(s) or isIPV6(s) def isIPV4(s): """Returns whether or not a given string is an IPV4 address. >>> isIPV4('255.255.255.255') 1 >>> isIPV4('abc.abc.abc.abc') 0 """ if set(s) - set('0123456789.'): # inet_aton ignores trailing data after the first valid IP address return False try: return bool(socket.inet_aton(str(s))) except socket.error: return False def bruteIsIPV6(s): if s.count('::') <= 1: L = s.split(':') if len(L) <= 8: for x in L: if x: try: int(x, 16) except ValueError: return False return True return False def isIPV6(s): """Returns whether or not a given string is an IPV6 address.""" try: if hasattr(socket, 'inet_pton'): return bool(socket.inet_pton(socket.AF_INET6, s)) else: return bruteIsIPV6(s) except socket.error: try: socket.inet_pton(socket.AF_INET6, '::') except socket.error: # We gotta fake it. return bruteIsIPV6(s) return False normalize_fingerprint = lambda fp: fp.replace(':', '').lower() FINGERPRINT_ALGORITHMS = ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512') def check_certificate_fingerprint(conn, trusted_fingerprints): trusted_fingerprints = set(normalize_fingerprint(fp) for fp in trusted_fingerprints) cert = conn.getpeercert(binary_form=True) for algorithm in FINGERPRINT_ALGORITHMS: h = hashlib.new(algorithm) h.update(cert) if h.hexdigest() in trusted_fingerprints: return raise ssl.CertificateError('No matching fingerprint.') if hasattr(ssl, 'create_default_context'): def ssl_wrap_socket(conn, hostname, logger, certfile=None, trusted_fingerprints=None, verify=True, ca_file=None, **kwargs): context = ssl.create_default_context(**kwargs) if trusted_fingerprints or not verify: # Do not use Certification Authorities context.check_hostname = False context.verify_mode = ssl.CERT_NONE if ca_file: context.load_verify_locations(cafile=ca_file) if certfile: context.load_cert_chain(certfile) conn = context.wrap_socket(conn, server_hostname=hostname) if verify and trusted_fingerprints: check_certificate_fingerprint(conn, trusted_fingerprints) return conn else: def ssl_wrap_socket(conn, hostname, logger, verify=True, certfile=None, ca_file=None, trusted_fingerprints=None): # TLSv1.0 is the only TLS version Python < 2.7.9 supports # (besides SSLv2 and v3, which are known to be insecure) try: conn = ssl.wrap_socket(conn, server_hostname=hostname, certfile=certfile, ca_certs=ca_file, ssl_version=ssl.PROTOCOL_TLSv1) except TypeError: # server_hostname is not supported conn = ssl.wrap_socket(conn, certfile=certfile, ca_certs=ca_file, ssl_version=ssl.PROTOCOL_TLSv1) if trusted_fingerprints: check_certificate_fingerprint(conn, trusted_fingerprints) elif verify: logger.critical('This Python version does not support SSL/TLS ' 'certification authority verification, which makes your ' 'connection vulnerable to man-in-the-middle attacks. See: ' '') return conn # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/utils/python.py0000644000175000017500000001700213634634532017141 0ustar valval00000000000000### # Copyright (c) 2005-2009, Jeremiah Fincher # Copyright (c) 2009-2010, James McCoy # 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 sys import types import fnmatch import threading def universalImport(*names): """Attempt to import the given modules, in order, returning the first successfully imported module. ImportError will be raised, as usual, if no imports succeed. To emulate ``from ModuleA import ModuleB'', pass the string 'ModuleA.ModuleB'""" f = sys._getframe(1) for name in names: try: # __import__ didn't gain keyword arguments until 2.5 ret = __import__(name, f.f_globals) except ImportError: continue else: if '.' in name: parts = name.split('.')[1:] while parts: ret = getattr(ret, parts[0]) del parts[0] return ret raise ImportError(','.join(names)) def changeFunctionName(f, name, doc=None): if doc is None: doc = f.__doc__ if hasattr(f, '__closure__'): closure = f.__closure__ else: # Pypy closure = f.func_closure newf = types.FunctionType(f.__code__, f.__globals__, name, f.__defaults__, closure) newf.__doc__ = doc return newf class Object(object): def __ne__(self, other): return not self == other class MetaSynchronized(type): METHODS = '__synchronized__' LOCK = '_MetaSynchronized_rlock' def __new__(cls, name, bases, dict): sync = set() for base in bases: if hasattr(base, MetaSynchronized.METHODS): sync.update(getattr(base, MetaSynchronized.METHODS)) if MetaSynchronized.METHODS in dict: sync.update(dict[MetaSynchronized.METHODS]) if sync: def synchronized(f): def g(self, *args, **kwargs): lock = getattr(self, MetaSynchronized.LOCK) lock.acquire() try: f(self, *args, **kwargs) finally: lock.release() return changeFunctionName(g, f.__name__, f.__doc__) for attr in sync: if attr in dict: dict[attr] = synchronized(dict[attr]) original__init__ = dict.get('__init__') def __init__(self, *args, **kwargs): if not hasattr(self, MetaSynchronized.LOCK): setattr(self, MetaSynchronized.LOCK, threading.RLock()) if original__init__: original__init__(self, *args, **kwargs) else: # newclass is defined below. super(newclass, self).__init__(*args, **kwargs) dict['__init__'] = __init__ newclass = super(MetaSynchronized, cls).__new__(cls, name, bases, dict) return newclass Synchronized = MetaSynchronized('Synchronized', (), {}) def glob2re(g): pattern = fnmatch.translate(g) if pattern.startswith('(?s:') and pattern.endswith(')\\Z'): # Python >= 3.6 return pattern[4:-3] + '\\Z' elif pattern.endswith('\\Z(?ms)'): # Python >= 2.6 and < 3.6 # # Translate glob to regular expression, trimming the "match EOL" # portion of the regular expression. # Some Python versions use \Z(?ms) per # https://bugs.python.org/issue6665 return pattern[:-7] else: assert False, 'Python < 2.6, or unknown behavior of fnmatch.translate.' _debug_software_name = 'Limnoria' _debug_software_version = None # From http://code.activestate.com/recipes/52215-get-more-information-from-tracebacks/ def collect_extra_debug_data(): """ Print the usual traceback information, followed by a listing of all the local variables in each frame. """ data = '' try: tb = sys.exc_info()[2] stack = [] while tb: stack.append(tb.tb_frame) tb = tb.tb_next finally: del tb if _debug_software_version: data += '%s version: %s\n\n' % \ (_debug_software_name, _debug_software_version) else: data += '(Cannot get %s version.)\n\n' % _debug_software_name data += 'Locals by frame, innermost last:\n' for frame in stack: data += '\n\n' data += ('Frame %s in %s at line %s\n' % (frame.f_code.co_name, frame.f_code.co_filename, frame.f_lineno)) frame_locals = frame.f_locals for inspected in ('self', 'cls'): if inspected in frame_locals: try: attribute_names = dir(frame_locals[inspected]) except Exception: # For Python 2 and Pypy try: attribute_names = list( frame_locals[inspected].__dict__) except Exception: attribute_names = [] for attr_name in attribute_names: try: v = getattr(frame_locals[inspected], attr_name) except Exception: v = '' frame_locals['%s.%s' % (inspected, attr_name)] = v for key, value in frame_locals.items(): if key == '__builtins__': # This is flooding continue data += ('\t%20s = ' % key) #We have to be careful not to cause a new error in our error #printer! Calling str() on an unknown object could cause an #error we don't want. try: data += repr(value) + '\n' except Exception: data += '\n' data += '\n' data += '+-----------------------+\n' data += '| End of locals display |\n' data += '+-----------------------+\n' data += '\n' return data # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=78: limnoria-2020.03.17/src/utils/seq.py0000644000175000017500000001003513634634532016407 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### def window(L, size): """list * size -> window iterable Returns a sliding 'window' through the list L of size size.""" assert not isinstance(L, int), 'Argument order swapped: window(L, size)' if size < 1: raise ValueError('size <= 0 disallowed.') for i in range(len(L) - (size-1)): yield L[i:i+size] def mapinto(f, L): for (i, x) in enumerate(L): L[i] = f(x) def renumerate(L): for i in range(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 = list(range(1, len(seq2) + 1)) + [0] for x in range(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 range(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: limnoria-2020.03.17/src/utils/str.py0000644000175000017500000005164013634634532016436 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2008-2009, James McCoy # Copyright (c) 2010, Valentin Lorentz # 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. ### """ Simple utility functions related to strings. """ import re import sys import time import string import textwrap from . import minisix from .iter import any from .structures import TwoWayDictionary from . import internationalization as _ internationalizeFunction = _.internationalizeFunction try: from charade.universaldetector import UniversalDetector charadeLoaded = True except ImportError: charadeLoaded = False if minisix.PY3: def decode_raw_line(line): #first, try to decode using utf-8 try: line = line.decode('utf8', 'strict') except UnicodeError: # if this fails and charade is loaded, try to guess the correct encoding if charadeLoaded: u = UniversalDetector() u.feed(line) u.close() if u.result['encoding']: # try to use the guessed encoding try: line = line.decode(u.result['encoding'], 'strict') # on error, give up and replace the offending characters except UnicodeError: line = line.decode(errors='replace') else: # if no encoding could be guessed, fall back to utf-8 and # replace offending characters line = line.decode('utf8', 'replace') # if charade is not loaded, try to decode using utf-8 and replace any # offending characters else: line = line.decode('utf8', 'replace') return line else: def decode_raw_line(line): return line def rsplit(s, sep=None, maxsplit=-1): """Equivalent to str.split, except splitting from the right.""" return s.rsplit(sep, maxsplit) def normalizeWhitespace(s, removeNewline=True): r"""Normalizes the whitespace in a string; \s+ becomes one space.""" if not s: return str(s) # not the same reference starts_with_space = (s[0] in ' \n\t\r') ends_with_space = (s[-1] in ' \n\t\r') if removeNewline: newline_re = re.compile('[\r\n]+') s = ' '.join(filter(bool, newline_re.split(s))) s = ' '.join(filter(bool, s.split('\t'))) s = ' '.join(filter(bool, s.split(' '))) if starts_with_space: s = ' ' + s if ends_with_space: s += ' ' return s def distance(s, t): """Returns the levenshtein edit distance between two strings.""" n = len(s) m = len(t) if n == 0: return m elif m == 0: return n d = [] for i in range(n+1): d.append([]) for j in range(m+1): d[i].append(0) d[0][j] = j d[i][0] = i for i in range(1, n+1): cs = s[i-1] for j in range(1, m+1): ct = t[j-1] cost = int(cs != ct) d[i][j] = min(d[i-1][j]+1, d[i][j-1]+1, d[i-1][j-1]+cost) return d[n][m] class MultipleReplacer: """Return a callable that replaces all dict keys by the associated value. More efficient than multiple .replace().""" # We use an object instead of a lambda function because it avoids the # need for using the staticmethod() on the lambda function if assigning # it to a class in Python 3. def __init__(self, dict_): self._dict = dict_ dict_ = dict([(re.escape(key), val) for key,val in dict_.items()]) self._matcher = re.compile('|'.join(dict_.keys())) def __call__(self, s): return self._matcher.sub(lambda m: self._dict[m.group(0)], s) def multipleReplacer(dict_): return MultipleReplacer(dict_) class MultipleRemover: """Return a callable that removes all words in the list. A bit more efficient than multipleReplacer""" # See comment of MultipleReplacer def __init__(self, list_): list_ = [re.escape(x) for x in list_] self._matcher = re.compile('|'.join(list_)) def __call__(self, s): return self._matcher.sub(lambda m: '', s) _soundextrans = MultipleReplacer(dict(list(zip(string.ascii_uppercase, '01230120022455012623010202')))) def soundex(s, length=4): """Returns the soundex hash of a given string. length=0 doesn't truncate the hash. """ s = s.upper() # Make everything uppercase. s = ''.join([x for x in s if x in string.ascii_uppercase]) if not s: raise ValueError('Invalid string for soundex: %s') firstChar = s[0] # Save the first character. s = _soundextrans(s) # Convert to soundex numbers. s = s.lstrip(s[0]) # Remove all repeated first characters. L = [firstChar] for c in s: if c != L[-1]: L.append(c) L = [c for c in L if c != '0'] s = ''.join(L) if length: s = s.ljust(length, '0')[:length] return s def dqrepr(s): """Returns a repr() of s guaranteed to be in double quotes.""" # The wankers-that-be decided not to use double-quotes anymore in 2.3. # return '"' + repr("'\x00" + s)[6:] encoding = 'string_escape' if minisix.PY2 else 'unicode_escape' if minisix.PY2 and isinstance(s, unicode): s = s.encode('utf8', 'replace') return '"%s"' % s.encode(encoding).decode().replace('"', '\\"') def quoted(s): """Returns a quoted s.""" return '"%s"' % s _openers = '{[(<' _closers = '}])>' def _getSep(s, allowBraces=False): if len(s) < 2: raise ValueError('string given to _getSep is too short: %r' % s) if allowBraces: braces = _closers else: braces = _openers + _closers if s.startswith('m') or s.startswith('s'): separator = s[1] else: separator = s[0] if separator.isalnum() or separator in braces: raise ValueError('Invalid separator: separator must not be alphanumeric or in ' \ '"%s"' % braces) return separator def perlReToPythonRe(s, allowG=False): """Converts a string representation of a Perl regular expression (i.e., m/^foo$/i or /foo|bar/) to a Python regular expression. """ opener = closer = _getSep(s, True) if opener in '{[(<': closer = _closers[_openers.index(opener)] opener = re.escape(opener) closer = re.escape(closer) matcher = re.compile(r'm?%s((?:\\.|[^\\])*)%s(.*)' % (opener, closer)) try: (regexp, flags) = matcher.match(s).groups() except AttributeError: # Unpack list of wrong size. raise ValueError('Must be of the form m/.../ or /.../') regexp = regexp.replace('\\'+opener, opener) if opener != closer: regexp = regexp.replace('\\'+closer, closer) flag = 0 g = False try: for c in flags.upper(): if c == 'G' and allowG: g = True continue flag |= getattr(re, c) except AttributeError: raise ValueError('Invalid flag: %s' % c) try: r = re.compile(regexp, flag) except re.error as e: raise ValueError(str(e)) if allowG: return (r, g) else: return r def perlReToFindall(s): """Converts a string representation of a Perl regular expression (i.e., m/^foo$/i or /foo|bar/) to a Python regular expression, with support for G flag """ (r, g) = perlReToPythonRe(s, allowG=True) if g: return lambda s: r.findall(s) else: return lambda s: r.search(s) and r.search(s).group(0) or '' def perlReToReplacer(s): """Converts a string representation of a Perl regular expression (i.e., s/foo/bar/g or s/foo/bar/i) to a Python function doing the equivalent replacement. """ sep = _getSep(s) escaped = re.escape(sep) matcher = re.compile(r's%s((?:\\.|[^\\])*)%s((?:\\.|[^\\])*)%s(.*)' % (escaped, escaped, escaped)) try: (regexp, replace, flags) = matcher.match(s).groups() except AttributeError: # Unpack list of wrong size. raise ValueError('Must be of the form s/.../.../') regexp = regexp.replace('\x08', r'\b') replace = replace.replace('\\'+sep, sep) for i in range(10): replace = replace.replace(chr(i), r'\%s' % i) g = False if 'g' in flags: g = True flags = list(filter('g'.__ne__, flags)) if isinstance(flags, list): flags = ''.join(flags) r = perlReToPythonRe(sep.join(('', regexp, flags))) if g: return lambda s: r.sub(replace, s) else: return lambda s: r.sub(replace, s, 1) _perlVarSubstituteRe = re.compile(r'\$\{([^}]+)\}|\$([a-zA-Z][a-zA-Z0-9]*)') def perlVariableSubstitute(vars, text): def replacer(m): (braced, unbraced) = m.groups() var = braced or unbraced try: x = vars[var] if callable(x): return x() else: try: return str(x) except UnicodeEncodeError: # Python 2 return str(x).encode('utf8') except KeyError: if braced: return '${%s}' % braced else: return '$' + unbraced return _perlVarSubstituteRe.sub(replacer, text) def splitBytes(word, size): # I'm going to hell for this function for i in range(4): # a character takes at most 4 bytes in UTF-8 try: if sys.version_info[0] >= 3: word[size-i:].decode() else: word[size-i:].encode('utf8') except UnicodeDecodeError: continue else: return (word[0:size-i], word[size-i:]) assert False, (word, size) def byteTextWrap(text, size, break_on_hyphens=False): """Similar to textwrap.wrap(), but considers the size of strings (in bytes) instead of their length (in characters).""" try: words = textwrap.TextWrapper()._split_chunks(text) except AttributeError: # Python 2 words = textwrap.TextWrapper()._split(text) words.reverse() # use it as a stack if sys.version_info[0] >= 3: words = [w.encode() for w in words] lines = [b''] while words: word = words.pop(-1) if len(word) > size: (before, after) = splitBytes(word, size) words.append(after) word = before if len(lines[-1]) + len(word) <= size: lines[-1] += word else: lines.append(word) if sys.version_info[0] >= 3: return [l.decode() for l in lines] else: return lines def commaAndify(seq, comma=',', And=None): """Given a a sequence, returns an English clause for that sequence. I.e., given [1, 2, 3], returns '1, 2, and 3' """ if And is None: And = _('and') L = list(seq) if len(L) == 0: return '' elif len(L) == 1: return ''.join(L) # We need this because it raises TypeError. elif len(L) == 2: L.insert(1, And) return ' '.join(L) else: L[-1] = '%s %s' % (And, L[-1]) sep = '%s ' % comma return sep.join(L) _unCommaTheRe = re.compile(r'(.*),\s*(the)$', re.I) def unCommaThe(s): """Takes a string of the form 'foo, the' and turns it into 'the foo'.""" m = _unCommaTheRe.match(s) if m is not None: return '%s %s' % (m.group(2), m.group(1)) else: return s def ellipsisify(s, n): """Returns a shortened version of s. Produces up to the first n chars at the nearest word boundary. """ if len(s) <= n: return s else: return (textwrap.wrap(s, n-3)[0] + '...') plurals = TwoWayDictionary({}) def matchCase(s1, s2): """Matches the case of s1 in s2""" if s1.isupper(): return s2.upper() else: L = list(s2) for (i, char) in enumerate(s1[:len(s2)]): if char.isupper(): L[i] = L[i].upper() return ''.join(L) @internationalizeFunction('pluralize') def pluralize(s): """Returns the plural of s. Put any exceptions to the general English rule of appending 's' in the plurals dictionary. """ consonants = 'bcdfghjklmnpqrstvwxz' _pluralizeRegex = re.compile('[%s]y$' % consonants) lowered = s.lower() # Exception dictionary if lowered in plurals: return matchCase(s, plurals[lowered]) # Words ending with 'ch', 'sh' or 'ss' such as 'punch(es)', 'fish(es) # and miss(es) elif any(lowered.endswith, ['x', 'ch', 'sh', 'ss']): return matchCase(s, s+'es') # Words ending with a consonant followed by a 'y' such as # 'try (tries)' or 'spy (spies)' elif _pluralizeRegex.search(lowered): return matchCase(s, s[:-1] + 'ies') # In all other cases, we simply add an 's' to the base word else: return matchCase(s, s+'s') @internationalizeFunction('depluralize') def depluralize(s): """Returns the singular of s.""" consonants = 'bcdfghjklmnpqrstvwxz' _depluralizeRegex = re.compile('[%s]ies' % consonants) lowered = s.lower() if lowered in plurals: return matchCase(s, plurals[lowered]) elif any(lowered.endswith, ['ches', 'shes', 'sses']): return s[:-2] elif re.search(_depluralizeRegex, lowered): return s[:-3] + 'y' else: if lowered.endswith('s'): return s[:-1] # Chop off 's'. else: return s # Don't know what to do. def nItems(n, item, between=None): """Works like this: >>> nItems(4, '') '4' >>> nItems(1, 'clock') '1 clock' >>> nItems(10, 'clock') '10 clocks' >>> nItems(4, '', between='grandfather') '4 grandfather' >>> nItems(10, 'clock', between='grandfather') '10 grandfather clocks' """ assert isinstance(n, minisix.integer_types), \ 'The order of the arguments to nItems changed again, sorry.' if item == '': if between is None: return format('%s', n) else: return format('%s %s', n, item) if between is None: if n != 1: return format('%s %p', n, item) else: return format('%s %s', n, item) else: if n != 1: return format('%s %s %p', n, between, item) else: return format('%s %s %s', n, between, item) @internationalizeFunction('ordinal') def ordinal(i): """Returns i + the ordinal indicator for the number. Example: ordinal(3) => '3rd' """ i = int(i) if i % 100 in (11,12,13): return '%sth' % i ord = 'th' test = i % 10 if test == 1: ord = 'st' elif test == 2: ord = 'nd' elif test == 3: ord = 'rd' return '%s%s' % (i, ord) @internationalizeFunction('be') def be(i): """Returns the form of the verb 'to be' based on the number i.""" if i == 1: return 'is' else: return 'are' @internationalizeFunction('has') def has(i): """Returns the form of the verb 'to have' based on the number i.""" if i == 1: return 'has' else: return 'have' def toBool(s): s = s.strip().lower() if s in ('true', 'on', 'enable', 'enabled', '1'): return True elif s in ('false', 'off', 'disable', 'disabled', '0'): return False else: raise ValueError('Invalid string for toBool: %s' % quoted(s)) # When used with Supybot, this is overriden when supybot.conf is loaded def timestamp(t): if t is None: t = time.time() return time.ctime(t) def url(url): return url _formatRe = re.compile(r'%((?:\d+)?\.\d+f|[bfhiLnpqrsStTuv%])') def format(s, *args, **kwargs): """w00t. %: literal %. i: integer s: string f: float r: repr b: form of the verb 'to be' (takes an int) h: form of the verb 'to have' (takes an int) L: commaAndify (takes a list of strings or a tuple of ([strings], and)) p: pluralize (takes a string) q: quoted (takes a string) n: nItems (takes a 2-tuple of (n, item) or a 3-tuple of (n, between, item)) S: returns a human-readable size (takes an int) t: time, formatted (takes an int) T: time delta, formatted (takes an int) u: url, wrapped in braces (this should be configurable at some point) v: void : takes one or many arguments, but doesn't display it (useful for translation) """ # Note to developers: If you want to add an argument type, do not forget # to add the character to the _formatRe regexp or it will be ignored # (and hard to debug if you don't know the trick). # Of course, you should also document it in the docstring above. if minisix.PY2: def pred(s): if isinstance(s, unicode): return s.encode('utf8') else: return s args = map(pred, args) args = list(args) args.reverse() # For more efficient popping. def sub(match): char = match.group(1) if char == 's': token = args.pop() if isinstance(token, str): return token elif minisix.PY2 and isinstance(token, unicode): return token.encode('utf8', 'replace') else: return str(token) elif char == 'i': # XXX Improve me! return str(args.pop()) elif char.endswith('f'): return ('%'+char) % args.pop() elif char == 'b': return be(args.pop()) elif char == 'h': return has(args.pop()) elif char == 'L': t = args.pop() if isinstance(t, tuple) and len(t) == 2: if not isinstance(t[0], list): raise ValueError('Invalid list for %%L in format: %s' % t) if not isinstance(t[1], minisix.string_types): raise ValueError('Invalid string for %%L in format: %s' % t) return commaAndify(t[0], And=t[1]) elif hasattr(t, '__iter__'): return commaAndify(t) else: raise ValueError('Invalid value for %%L in format: %s' % t) elif char == 'p': return pluralize(args.pop()) elif char == 'q': return quoted(args.pop()) elif char == 'r': return repr(args.pop()) elif char == 'n': t = args.pop() if not isinstance(t, (tuple, list)): raise ValueError('Invalid value for %%n in format: %s' % t) if len(t) == 2: return nItems(*t) elif len(t) == 3: return nItems(t[0], t[2], between=t[1]) else: raise ValueError('Invalid value for %%n in format: %s' % t) elif char == 'S': t = args.pop() if not isinstance(t, minisix.integer_types): raise ValueError('Invalid value for %%S in format: %s' % t) for suffix in ['B','KB','MB','GB','TB']: if t < 1024: return "%i%s" % (t, suffix) t /= 1024 elif char == 't': return timestamp(args.pop()) elif char == 'T': from .gen import timeElapsed return timeElapsed(args.pop()) elif char == 'u': return url(args.pop()) elif char == 'v': args.pop() return '' elif char == '%': return '%' else: raise ValueError('Invalid char in sub (in format).') try: return _formatRe.sub(sub, s) except IndexError: raise ValueError('Extra format chars in format spec: %r' % s) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/utils/structures.py0000644000175000017500000003515613634634533020056 0ustar valval00000000000000### # Copyright (c) 2002-2009, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Data structures for Python. """ import time import collections.abc class RingBuffer(object): """Class to represent a fixed-size ring buffer.""" __slots__ = ('L', 'i', 'full', 'maxSize') def __init__(self, maxSize, seq=()): if maxSize <= 0: raise ValueError('maxSize must be > 0.') self.maxSize = maxSize self.reset() for elt in seq: self.append(elt) def reset(self): self.full = False self.L = [] self.i = 0 def resize(self, size): L = list(self) i = self.i self.reset() self.maxSize = size for elt in L[i+1:]: self.append(elt) for elt in L[0:i]: self.append(elt) def __len__(self): return len(self.L) def __eq__(self, other): if self.__class__ == other.__class__ and \ self.maxSize == other.maxSize and len(self) == len(other): iterator = iter(other) for elt in self: otherelt = next(iterator) if not elt == otherelt: return False return True return False def __bool__(self): return len(self) > 0 __nonzero__ = __bool__ def __contains__(self, elt): return elt in self.L def append(self, elt): if self.full: self.L[self.i] = elt self.i += 1 self.i %= len(self.L) elif len(self) == self.maxSize: self.full = True self.append(elt) else: self.L.append(elt) def extend(self, seq): for elt in seq: self.append(elt) def __getitem__(self, idx): if self.full: oidx = idx if isinstance(oidx, slice): L = [] for i in range(*slice.indices(oidx, len(self))): L.append(self[i]) return L else: (m, idx) = divmod(oidx, len(self.L)) if m and m != -1: raise IndexError(oidx) idx = (idx + self.i) % len(self.L) return self.L[idx] else: if isinstance(idx, slice): L = [] for i in range(*slice.indices(idx, len(self))): L.append(self[i]) return L else: return self.L[idx] def __setitem__(self, idx, elt): if self.full: oidx = idx if isinstance(oidx, slice): range_ = range(*slice.indices(oidx, len(self))) if len(range_) != len(elt): raise ValueError('seq must be the same length as slice.') else: for (i, x) in zip(range_, elt): self[i] = x else: (m, idx) = divmod(oidx, len(self.L)) if m and m != -1: raise IndexError(oidx) idx = (idx + self.i) % len(self.L) self.L[idx] = elt else: if isinstance(idx, slice): range_ = range(*slice.indices(idx, len(self))) if len(range_) != len(elt): raise ValueError('seq must be the same length as slice.') else: for (i, x) in zip(range_, elt): self[i] = x else: self.L[idx] = elt def __repr__(self): return 'RingBuffer(%r, %r)' % (self.maxSize, list(self)) def __getstate__(self): return (self.maxSize, self.full, self.i, self.L) def __setstate__(self, state): (maxSize, full, i, L) = state self.maxSize = maxSize self.full = full self.i = i self.L = L class queue(object): """Queue class for handling large queues. Queues smaller than 1,000 or so elements are probably better served by the smallqueue class. """ __slots__ = ('front', 'back') def __init__(self, seq=()): self.back = [] self.front = [] for elt in seq: self.enqueue(elt) def reset(self): self.back[:] = [] self.front[:] = [] def enqueue(self, elt): self.back.append(elt) def dequeue(self): try: return self.front.pop() except IndexError: self.back.reverse() self.front = self.back self.back = [] return self.front.pop() def peek(self): if self.front: return self.front[-1] else: return self.back[0] def __len__(self): return len(self.front) + len(self.back) def __bool__(self): return bool(self.back or self.front) __nonzero__ = __bool__ def __contains__(self, elt): return elt in self.front or elt in self.back def __iter__(self): for elt in reversed(self.front): yield elt for elt in self.back: yield elt def __eq__(self, other): if len(self) == len(other): otheriter = iter(other) for elt in self: otherelt = next(otheriter) if not (elt == otherelt): return False return True else: return False def __repr__(self): return 'queue([%s])' % ', '.join(map(repr, self)) def __getitem__(self, oidx): if len(self) == 0: raise IndexError('queue index out of range') if isinstance(oidx, slice): L = [] for i in range(*slice.indices(oidx, len(self))): L.append(self[i]) return L else: (m, idx) = divmod(oidx, len(self)) if m and m != -1: raise IndexError(oidx) if len(self.front) > idx: return self.front[-(idx+1)] else: return self.back[(idx-len(self.front))] def __setitem__(self, oidx, value): if len(self) == 0: raise IndexError('queue index out of range') if isinstance(oidx, slice): range_ = range(*slice.indices(oidx, len(self))) if len(range_) != len(value): raise ValueError('seq must be the same length as slice.') else: for i in range_: (m, idx) = divmod(oidx, len(self)) if m and m != -1: raise IndexError(oidx) for (i, x) in zip(range_, value): self[i] = x else: (m, idx) = divmod(oidx, len(self)) if m and m != -1: raise IndexError(oidx) if len(self.front) > idx: self.front[-(idx+1)] = value else: self.back[idx-len(self.front)] = value def __delitem__(self, oidx): if isinstance(oidx, slice): range_ = range(*slice.indices(oidx, len(self))) for i in range_: del self[i] else: (m, idx) = divmod(oidx, len(self)) if m and m != -1: raise IndexError(oidx) if len(self.front) > idx: del self.front[-(idx+1)] else: del self.back[idx-len(self.front)] def __getstate__(self): return (list(self),) def __setstate__(self, state): (L,) = state L.reverse() self.front = L self.back = [] class smallqueue(list): __slots__ = () def enqueue(self, elt): self.append(elt) def dequeue(self): return self.pop(0) def peek(self): return self[0] def __repr__(self): return 'smallqueue([%s])' % ', '.join(map(repr, self)) def reset(self): self[:] = [] class TimeoutQueue(object): __slots__ = ('queue', 'timeout') def __init__(self, timeout, queue=None): if queue is None: queue = smallqueue() self.queue = queue self.timeout = timeout def reset(self): self.queue.reset() def __repr__(self): self._clearOldElements() return '%s(timeout=%r, queue=%r)' % (self.__class__.__name__, self.timeout, self.queue) def _getTimeout(self): if callable(self.timeout): return self.timeout() else: return self.timeout def _clearOldElements(self): now = time.time() while self.queue and now - self.queue.peek()[0] > self._getTimeout(): self.queue.dequeue() def setTimeout(self, i): self.timeout = i def enqueue(self, elt, at=None): if at is None: at = time.time() self.queue.enqueue((at, elt)) def dequeue(self): self._clearOldElements() return self.queue.dequeue()[1] def __iter__(self): # We could _clearOldElements here, but what happens if someone stores # the resulting generator and elements that should've timed out are # yielded? Hmm? What happens then, smarty-pants? for (t, elt) in self.queue: if time.time() - t < self._getTimeout(): yield elt def __len__(self): # No dependency on utils.iter # return ilen(self) self._clearOldElements() return len(self.queue) class MaxLengthQueue(queue): __slots__ = ('length',) def __init__(self, length, seq=()): self.length = length queue.__init__(self, seq) def __getstate__(self): return (self.length, queue.__getstate__(self)) def __setstate__(self, state): (length, q) = state self.length = length queue.__setstate__(self, q) def enqueue(self, elt): queue.enqueue(self, elt) if len(self) > self.length: self.dequeue() class TwoWayDictionary(dict): __slots__ = () def __init__(self, seq=(), **kwargs): if hasattr(seq, 'iteritems'): seq = seq.iteritems() elif hasattr(seq, 'items'): seq = seq.items() for (key, value) in seq: self[key] = value self[value] = key for (key, value) in kwargs.items(): self[key] = value self[value] = key def __setitem__(self, key, value): dict.__setitem__(self, key, value) dict.__setitem__(self, value, key) def __delitem__(self, key): value = self[key] dict.__delitem__(self, key) dict.__delitem__(self, value) class MultiSet(object): __slots__ = ('d',) def __init__(self, seq=()): self.d = {} for elt in seq: self.add(elt) def add(self, elt): try: self.d[elt] += 1 except KeyError: self.d[elt] = 1 def remove(self, elt): self.d[elt] -= 1 if not self.d[elt]: del self.d[elt] def __getitem__(self, elt): return self.d[elt] def __contains__(self, elt): return elt in self.d class CacheDict(collections.abc.MutableMapping): __slots__ = ('d', 'max') def __init__(self, max, **kwargs): self.d = dict(**kwargs) self.max = max def __getitem__(self, key): return self.d[key] def __setitem__(self, key, value): if len(self.d) >= self.max: self.d.clear() self.d[key] = value def __delitem__(self, key): del self.d[key] def keys(self): return self.d.keys() def items(self): return self.d.items() def __iter__(self): return iter(self.d) def __len__(self): return len(self.d) class TruncatableSet(collections.abc.MutableSet): """A set that keeps track of the order of inserted elements so the oldest can be removed.""" __slots__ = ('_ordered_items', '_items') def __init__(self, iterable=[]): self._ordered_items = list(iterable) self._items = set(self._ordered_items) def __repr__(self): return 'TruncatableSet({%r})' % self._items def __contains__(self, item): return item in self._items def __iter__(self): return iter(self._items) def __len__(self): return len(self._items) def add(self, item): if item not in self._items: self._items.add(item) self._ordered_items.append(item) def discard(self, item): self._items.discard(item) self._ordered_items.remove(item) def truncate(self, size): assert size >= 0 removed_size = len(self)-size # I make two different cases depending on removed_size None txnDir is the directory that will hold the transaction's working files and such. If it can't be renamed, there is probably an active transaction. """ TransactionMixin.__init__(self, *args, **kwargs) if os.path.exists(self.dir): raise InProgress(self.dir) if not os.path.exists(self.txnDir): raise FailedAcquisition(self.txnDir) try: os.rename(self.txnDir, self.dir) except EnvironmentError as e: raise FailedAcquisition(self.txnDir, e) os.mkdir(self.dirize(self.ORIGINALS)) os.mkdir(self.dirize(self.REPLACEMENTS)) self._journal = open(self._journalName, 'a') cwd = open(self.dirize('cwd'), 'w') cwd.write(os.getcwd()) cwd.close() def _journalCommand(self, command, *args): File.writeLine(self._journal, '%s %s' % (command, ' '.join(map(str, args)))) self._journal.flush() def _makeOriginal(self, filename): File.copy(filename, self._original(filename)) # XXX There needs to be a way, given a transaction, to get a # "sub-transaction", which: # # 1. Doesn't try to grab the txnDir and move it, but instead is just # given the actual directory being used and uses that. # 2. Acquires the lock of the original transaction, only releasing it # when its .commit method is called (assuming Transaction is # threadsafe). # 3. Has a no-op .commit method (i.e., doesn't commit). # # This is so that, for instance, an object with an active Transaction # can give other objects a Transaction-ish object without worrying that # the transaction will be committed, while still allowing those objects # to work properly with real transactions (i.e., they still call # as they would on a normal Transaction, it just has no effect with a # sub-transaction). # The method that returns a subtransaction should be called "child." def child(self): raise NotImplementedError # XXX create, replace, etc. return file objects. This class should keep a # list of such file descriptors and only allow a commit if all of them # are closed. Trying to commit with open file objects should raise an # exception. def create(self, filename): """ Returns a file object for a filename that should be created (with the contents as they were written to the filename) when the transaction is committed. """ raise NotImplementedError # XXX. def mkdir(self, filename): raise NotImplementedError # XXX def delete(self, filename): raise NotImplementedError # XXX def replace(self, filename): """ Returns a file object for a filename that should be replaced by the contents written to the file object when the transaction is committed. """ self._checkCwd() self._makeOriginal(filename) self._journalCommand('replace', filename) return File.open(self._replacement(filename)) def append(self, filename): self._checkCwd() length = os.stat(filename).st_size self._journalCommand('append', filename, length) replacement = self._replacement(filename) File.copy(filename, replacement) return open(replacement, 'a') def commit(self, removeWhenComplete=True): self._journal.close() self._checkCwd() File.touch(self.dirize('commit')) for (command, args) in self._journalCommands(): methodName = 'commit%s' % command.capitalize() getattr(self, methodName)(*args) File.touch(self.dirize('committed')) if removeWhenComplete: shutil.rmtree(self.dir) def commitReplace(self, filename): shutil.copy(self._replacement(filename), filename) def commitAppend(self, filename, length): shutil.copy(self._replacement(filename), filename) # XXX need to be able to rename files transactionally. (hard; especially # with renames that depend on one another. It might be easier to do # rename separate from relocate.) class Rollback(TransactionMixin): def rollback(self, removeWhenComplete=True): self._checkCwd() if not os.path.exists(self.dirize('commit')): return # No action taken; commit hadn't begun. for (command, args) in self._journalCommands(): methodName = 'rollback%s' % command.capitalize() getattr(self, methodName)(*args) if removeWhenComplete: shutil.rmtree(self.dir) def rollbackReplace(self, filename): shutil.copy(self._original(filename), filename) def rollbackAppend(self, filename, length): fd = open(filename, 'a') fd.truncate(int(length)) fd.close() # vim:set shiftwidth=4 softtabstop=8 expandtab textwidth=78: limnoria-2020.03.17/src/utils/web.py0000644000175000017500000002323113634634533016377 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009, James McCoy # 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 re import base64 import socket sockerrors = (socket.error,) try: sockerrors += (socket.sslerror,) except AttributeError: pass from .str import normalizeWhitespace from . import minisix if minisix.PY2: import urllib import urllib2 from httplib import InvalidURL from urlparse import urlsplit, urlunsplit, urlparse from htmlentitydefs import entitydefs, name2codepoint from HTMLParser import HTMLParser from cgi import escape as html_escape Request = urllib2.Request urlquote = urllib.quote urlquote_plus = urllib.quote_plus urlunquote = urllib.unquote urlopen = urllib2.urlopen def urlencode(*args, **kwargs): return urllib.urlencode(*args, **kwargs).encode() from urllib2 import HTTPError, URLError from urllib import splithost, splituser else: from http.client import InvalidURL from urllib.parse import urlsplit, urlunsplit, urlparse from html.entities import entitydefs, name2codepoint from html.parser import HTMLParser from html import escape as html_escape import urllib.request, urllib.parse, urllib.error Request = urllib.request.Request urlquote = urllib.parse.quote urlquote_plus = urllib.parse.quote_plus urlunquote = urllib.parse.unquote urlopen = urllib.request.urlopen def urlencode(*args, **kwargs): return urllib.parse.urlencode(*args, **kwargs) from urllib.error import HTTPError, URLError from urllib.parse import splithost, splituser class Error(Exception): pass _octet = r'(?:2(?:[0-4]\d|5[0-5])|1\d\d|\d{1,2})' _ipAddr = r'%s(?:\.%s){3}' % (_octet, _octet) # Base domain regex off RFC 1034 and 1738 _label = r'[0-9a-z][-0-9a-z]*[0-9a-z]?' _scheme = r'[a-z][a-z0-9+.-]*' _domain = r'%s(?:\.%s)*\.[0-9a-z][-0-9a-z]+' % (_label, _label) _urlRe = r'(%s://(?:\S+@)?(?:%s|%s)(?::\d+)?(?:/[^\])>\s]*)?)' % ( _scheme, _domain, _ipAddr) urlRe = re.compile(_urlRe, re.I) _httpUrlRe = r'(https?://(?:\S+@)?(?:%s|%s)(?::\d+)?(?:/[^\])>\s]*)?)' % \ (_domain, _ipAddr) httpUrlRe = re.compile(_httpUrlRe, re.I) REFUSED = 'Connection refused.' TIMED_OUT = 'Connection timed out.' UNKNOWN_HOST = 'Unknown host.' RESET_BY_PEER = 'Connection reset by peer.' FORBIDDEN = 'Client forbidden from accessing URL.' def strError(e): try: n = e.args[0] except Exception: return str(e) if n == 111: return REFUSED elif n in (110, 10060): return TIMED_OUT elif n == 104: return RESET_BY_PEER elif n in (8, 7, 3, 2, -2, -3): return UNKNOWN_HOST elif n == 403: return FORBIDDEN else: return str(e) # Used to build defaultHeaders baseDefaultHeaders = { 'User-agent': 'Mozilla/5.0 (compatible; utils.web python module)' } # overridable by other modules/plugins. defaultHeaders = baseDefaultHeaders.copy() # Other modules should feel free to replace this with an appropriate # application-specific function. Feel free to use a callable here. proxy = None def getUrlFd(url, headers=None, data=None, timeout=None): """getUrlFd(url, headers=None, data=None, timeout=None) Opens the given url and returns a file object. Headers and data are a dict and string, respectively, as per urllib.request.Request's arguments.""" if headers is None: headers = defaultHeaders if minisix.PY3 and isinstance(data, str): data = data.encode() try: if not isinstance(url, Request): (scheme, loc, path, query, frag) = urlsplit(url) (user, host) = splituser(loc) url = urlunsplit((scheme, host, path, query, '')) request = Request(url, headers=headers, data=data) if user: request.add_header('Authorization', 'Basic %s' % base64.b64encode(user)) else: request = url request.add_data(data) fd = urlopen(request, timeout=timeout) return fd except socket.timeout as e: raise Error(TIMED_OUT) except sockerrors as e: raise Error(strError(e)) except InvalidURL as e: raise Error('Invalid URL: %s' % e) except HTTPError as e: raise Error(strError(e)) except URLError as e: raise Error(strError(e.reason)) # Raised when urllib doesn't recognize the url type except ValueError as e: raise Error(strError(e)) def getUrlTargetAndContent(url, size=None, headers=None, data=None, timeout=None): """getUrlTargetAndContent(url, size=None, headers=None, data=None, timeout=None) Gets a page. Returns two strings that are the page gotten and the target URL (ie. after redirections). Size is an integer number of bytes to read from the URL. Headers and data are dicts as per urllib.request.Request's arguments.""" fd = getUrlFd(url, headers=headers, data=data, timeout=timeout) try: if size is None: text = fd.read() else: text = fd.read(size) except socket.timeout: raise Error(TIMED_OUT) target = fd.geturl() fd.close() return (target, text) def getUrlContent(*args, **kwargs): """getUrlContent(url, size=None, headers=None, data=None, timeout=None) Gets a page. Returns a string that is the page gotten. Size is an integer number of bytes to read from the URL. Headers and data are dicts as per urllib.request.Request's arguments.""" (target, text) = getUrlTargetAndContent(*args, **kwargs) return text def getUrl(*args, **kwargs): """Alias for getUrlContent.""" return getUrlContent(*args, **kwargs) def getDomain(url): return urlparse(url)[1] _charset_re = (']+charset=' """(?P("[^"]+"|'[^']+'))""") def getEncoding(s): try: match = re.search(_charset_re, s, re.MULTILINE) if match: return match.group('charset')[1:-1] except: match = re.search(_charset_re.encode(), s, re.MULTILINE) if match: return match.group('charset').decode()[1:-1] try: import charade.universaldetector u = charade.universaldetector.UniversalDetector() u.feed(s) u.close() return u.result['encoding'] except: return None class HtmlToText(HTMLParser, object): """Taken from some eff-bot code on c.l.p.""" entitydefs = entitydefs.copy() entitydefs['nbsp'] = ' ' entitydefs['apos'] = '\'' def __init__(self, tagReplace=' '): self.data = [] self.tagReplace = tagReplace super(HtmlToText, self).__init__() def append(self, data): self.data.append(data) def handle_starttag(self, tag, attr): self.append(self.tagReplace) def handle_endtag(self, tag): self.append(self.tagReplace) def handle_data(self, data): self.append(data) def handle_entityref(self, data): if minisix.PY3: if data in name2codepoint: self.append(chr(name2codepoint[data])) elif isinstance(data, bytes): self.append(data.decode()) else: self.append(data) else: if data in name2codepoint: self.append(unichr(name2codepoint[data])) elif isinstance(data, str): self.append(data.decode('utf8', errors='replace')) else: self.append(data) def getText(self): text = ''.join(self.data).strip() return normalizeWhitespace(text) def handle_charref(self, name): self.append(self.unescape('&#%s;' % name)) def htmlToText(s, tagReplace=' '): """Turns HTML into text. tagReplace is a string to replace HTML tags with. """ encoding = getEncoding(s) if encoding: s = s.decode(encoding) else: try: if minisix.PY2 or isinstance(s, bytes): s = s.decode('utf8') except: pass x = HtmlToText(tagReplace) x.feed(s) x.close() return x.getText() def mungeEmail(s): s = s.replace('@', ' AT ') s = s.replace('.', ' DOT ') return s # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/src/version.py0000644000175000017500000000025713634634546016156 0ustar valval00000000000000version = '2020.03.17' try: # For import from setup.py import supybot.utils.python supybot.utils.python._debug_software_version = version except ImportError: pass limnoria-2020.03.17/src/world.py0000644000175000017500000002032313634634533015610 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### """ Module for general worldly stuff, like global variables and whatnot. """ import gc import os import sys import time import atexit import select import threading import multiprocessing import re from . import conf, drivers, ircutils, log, registry from .utils import minisix startedAt = time.time() # Just in case it doesn't get set later. starting = False mainThread = threading.currentThread() def isMainThread(): return mainThread is threading.currentThread() threadsSpawned = 1 # Starts at one for the initial "thread." class SupyThread(threading.Thread, object): def __init__(self, *args, **kwargs): global threadsSpawned threadsSpawned += 1 super(SupyThread, self).__init__(*args, **kwargs) log.debug('Spawning thread %q.', self.getName()) processesSpawned = 1 # Starts at one for the initial process. class SupyProcess(multiprocessing.Process): def __init__(self, *args, **kwargs): global processesSpawned processesSpawned += 1 super(SupyProcess, self).__init__(*args, **kwargs) log.debug('Spawning process %q.', self.name) if sys.version_info[0:3] == (3, 3, 1) and hasattr(select, 'poll'): # http://bugs.python.org/issue17707 import multiprocessing.connection def _poll(fds, timeout): if timeout is not None: timeout = int(timeout * 1000) # timeout is in milliseconds fd_map = {} pollster = select.poll() for fd in fds: pollster.register(fd, select.POLLIN) if hasattr(fd, 'fileno'): fd_map[fd.fileno()] = fd else: fd_map[fd] = fd ls = [] for fd, event in pollster.poll(timeout): if event & select.POLLNVAL: raise ValueError('invalid file descriptor %i' % fd) ls.append(fd_map[fd]) return ls multiprocessing.connection._poll = _poll commandsProcessed = 0 ircs = [] # A list of all the IRCs. def getIrc(network): """Returns Irc object of the given network. is string and not case-sensitive.""" network = network.lower() for irc in ircs: if irc.network.lower() == network: return irc return None def _flushUserData(): userdataFilename = os.path.join(conf.supybot.directories.conf(), 'userdata.conf') registry.close(conf.users, userdataFilename) flushers = [_flushUserData] # A periodic function will flush all these. registryFilename = None def flush(): """Flushes all the registered flushers.""" for (i, f) in enumerate(flushers): try: f() except Exception: log.exception('Uncaught exception in flusher #%s (%s):', i, f) def debugFlush(s=''): if conf.supybot.debug.flushVeryOften(): if s: log.debug(s) flush() def upkeep(): """Does upkeep (like flushing, garbage collection, etc.)""" # Just in case, let's clear the exception info. try: sys.exc_clear() except AttributeError: # Python 3 does not have sys.exc_clear. The except statement clears # the info itself (and we've just entered an except statement) pass if os.name == 'nt': try: import msvcrt msvcrt.heapmin() except ImportError: pass except IOError: # Win98 pass if conf.daemonized: # If we're daemonized, sys.stdout has been replaced with a StringIO # object, so let's see if anything's been printed, and if so, let's # log.warning it (things shouldn't be printed, and we're more likely # to get bug reports if we make it a warning). if not hasattr(sys.stdout, 'getvalue'): # Stupid twisted sometimes replaces our stdout with theirs, because # "The Twisted Way Is The Right Way" (ha!). So we're stuck simply # skipping the checks log.warning('Expected cStringIO as stdout, got %r.', sys.stdout) else: s = sys.stdout.getvalue() if s: log.warning('Printed to stdout after daemonization: %s', s) sys.stdout.seek(0) sys.stdout.truncate() # Truncates to current offset. s = sys.stderr.getvalue() if s: log.error('Printed to stderr after daemonization: %s', s) sys.stderr.seek(0) sys.stderr.truncate() # Truncates to current offset. doFlush = conf.supybot.flush() and not starting if doFlush: flush() # This is so registry._cache gets filled. # This seems dumb, so we'll try not doing it anymore. #if registryFilename is not None: # registry.open(registryFilename) if not dying: if minisix.PY2: log.debug('Regexp cache size: %s', len(re._cache)) log.debug('Pattern cache size: %s', len(ircutils._patternCache)) log.debug('HostmaskPatternEqual cache size: %s', len(ircutils._hostmaskPatternEqualCache)) #timestamp = log.timestamp() if doFlush: log.info('Flushers flushed and garbage collected.') else: log.info('Garbage collected.') collected = gc.collect() if gc.garbage: log.warning('Noncollectable garbage (file this as a bug on SF.net): %s', gc.garbage) return collected def makeDriversDie(): """Kills drivers.""" log.info('Killing Driver objects.') for driver in drivers._drivers.values(): driver.die() def makeIrcsDie(): """Kills Ircs.""" log.info('Killing Irc objects.') for irc in ircs[:]: if not irc.zombie: irc.die() else: log.debug('Not killing %s, it\'s already a zombie.', irc) def startDying(): """Starts dying.""" log.info('Shutdown initiated.') global dying dying = True def finished(): log.info('Shutdown complete.') # These are in order; don't reorder them for cosmetic purposes. The order # in which they're registered is the reverse order in which they will run. atexit.register(finished) atexit.register(upkeep) atexit.register(makeIrcsDie) atexit.register(makeDriversDie) atexit.register(startDying) ################################################## ################################################## ################################################## ## Don't even *think* about messing with these. ## ################################################## ################################################## ################################################## dying = False testing = False starting = False profiling = False documenting = False # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/test/0000755000175000017500000000000013634634547014304 5ustar valval00000000000000limnoria-2020.03.17/test/__init__.py0000644000175000017500000000325613634634533016416 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### # We're just masquerading as a plugin :) from . import test # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/test/test.py0000644000175000017500000000425513634634533015636 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### import sys import os.path import unittest import supybot.test as test load = unittest.defaultTestLoader.loadTestsFromModule GLOBALS = globals() dirname = os.path.dirname(__file__) sys.path.append(dirname) filenames = os.listdir(dirname) # Uncomment these if you need some consistency in the order these tests run. # filenames.sort() # filenames.reverse() for filename in filenames: if filename.startswith('test_') and filename.endswith('.py'): name = filename[:-3] plugin = __import__(name) test.suites.append(load(plugin)) module = None # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/test/test_callbacks.py0000644000175000017500000007503713634634533017643 0ustar valval00000000000000# -*- coding: utf8 -*- ### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * import supybot.conf as conf import supybot.utils as utils import supybot.ircmsgs as ircmsgs import supybot.utils.minisix as minisix import supybot.callbacks as callbacks tokenize = callbacks.tokenize class TokenizerTestCase(SupyTestCase): def testEmpty(self): self.assertEqual(tokenize(''), []) def testNullCharacter(self): self.assertEqual(tokenize(utils.str.dqrepr('\0')), ['\0']) def testSingleDQInDQString(self): self.assertEqual(tokenize('"\\""'), ['"']) def testDQsWithBackslash(self): self.assertEqual(tokenize('"\\\\"'), ["\\"]) def testDoubleQuotes(self): self.assertEqual(tokenize('"\\"foo\\""'), ['"foo"']) def testSingleWord(self): self.assertEqual(tokenize('foo'), ['foo']) def testMultipleSimpleWords(self): words = 'one two three four five six seven eight'.split() for i in range(len(words)): self.assertEqual(tokenize(' '.join(words[:i])), words[:i]) def testSingleQuotesNotQuotes(self): self.assertEqual(tokenize("it's"), ["it's"]) def testQuotedWords(self): self.assertEqual(tokenize('"foo bar"'), ['foo bar']) self.assertEqual(tokenize('""'), ['']) self.assertEqual(tokenize('foo "" bar'), ['foo', '', 'bar']) self.assertEqual(tokenize('foo "bar baz" quux'), ['foo', 'bar baz', 'quux']) _testUnicode = """ def testUnicode(self): self.assertEqual(tokenize(u'好'), [u'好']) self.assertEqual(tokenize(u'"好"'), [u'好'])""" if minisix.PY3: _testUnicode = _testUnicode.replace("u'", "'") exec(_testUnicode) def testNesting(self): self.assertEqual(tokenize('[]'), [[]]) self.assertEqual(tokenize('[foo]'), [['foo']]) self.assertEqual(tokenize('[ foo ]'), [['foo']]) self.assertEqual(tokenize('foo [bar]'), ['foo', ['bar']]) self.assertEqual(tokenize('foo bar [baz quux]'), ['foo', 'bar', ['baz', 'quux']]) try: orig = conf.supybot.commands.nested() conf.supybot.commands.nested.setValue(False) self.assertEqual(tokenize('[]'), ['[]']) self.assertEqual(tokenize('[foo]'), ['[foo]']) self.assertEqual(tokenize('foo [bar]'), ['foo', '[bar]']) self.assertEqual(tokenize('foo bar [baz quux]'), ['foo', 'bar', '[baz', 'quux]']) finally: conf.supybot.commands.nested.setValue(orig) def testError(self): self.assertRaises(SyntaxError, tokenize, '[foo') #] self.assertRaises(SyntaxError, tokenize, '"foo') #" def testPipe(self): try: conf.supybot.commands.nested.pipeSyntax.setValue(True) self.assertRaises(SyntaxError, tokenize, '| foo') self.assertRaises(SyntaxError, tokenize, 'foo ||bar') self.assertRaises(SyntaxError, tokenize, 'bar |') self.assertEqual(tokenize('foo|bar'), ['bar', ['foo']]) self.assertEqual(tokenize('foo | bar'), ['bar', ['foo']]) self.assertEqual(tokenize('foo | bar | baz'), ['baz', ['bar',['foo']]]) self.assertEqual(tokenize('foo bar | baz'), ['baz', ['foo', 'bar']]) self.assertEqual(tokenize('foo | bar baz'), ['bar', 'baz', ['foo']]) self.assertEqual(tokenize('foo bar | baz quux'), ['baz', 'quux', ['foo', 'bar']]) finally: conf.supybot.commands.nested.pipeSyntax.setValue(False) self.assertEqual(tokenize('foo|bar'), ['foo|bar']) self.assertEqual(tokenize('foo | bar'), ['foo', '|', 'bar']) self.assertEqual(tokenize('foo | bar | baz'), ['foo', '|', 'bar', '|', 'baz']) self.assertEqual(tokenize('foo bar | baz'), ['foo', 'bar', '|', 'baz']) def testQuoteConfiguration(self): f = callbacks.tokenize self.assertEqual(f('[foo]'), [['foo']]) self.assertEqual(f('"[foo]"'), ['[foo]']) try: original = conf.supybot.commands.quotes() conf.supybot.commands.quotes.setValue('`') self.assertEqual(f('[foo]'), [['foo']]) self.assertEqual(f('`[foo]`'), ['[foo]']) conf.supybot.commands.quotes.setValue('\'') self.assertEqual(f('[foo]'), [['foo']]) self.assertEqual(f('\'[foo]\''), ['[foo]']) conf.supybot.commands.quotes.setValue('`\'') self.assertEqual(f('[foo]'), [['foo']]) self.assertEqual(f('`[foo]`'), ['[foo]']) self.assertEqual(f('[foo]'), [['foo']]) self.assertEqual(f('\'[foo]\''), ['[foo]']) finally: conf.supybot.commands.quotes.setValue(original) def testBold(self): s = '\x02foo\x02' self.assertEqual(tokenize(s), [s]) s = s[:-1] + '\x0f' self.assertEqual(tokenize(s), [s]) def testColor(self): s = '\x032,3foo\x03' self.assertEqual(tokenize(s), [s]) s = s[:-1] + '\x0f' self.assertEqual(tokenize(s), [s]) class FunctionsTestCase(SupyTestCase): def testCanonicalName(self): self.assertEqual('foo', callbacks.canonicalName('foo')) self.assertEqual('foobar', callbacks.canonicalName('foo-bar')) self.assertEqual('foobar', callbacks.canonicalName('foo_bar')) self.assertEqual('foobar', callbacks.canonicalName('FOO-bar')) self.assertEqual('foobar', callbacks.canonicalName('FOOBAR')) self.assertEqual('foobar', callbacks.canonicalName('foo___bar')) self.assertEqual('foobar', callbacks.canonicalName('_f_o_o-b_a_r')) # The following seems to be a hack for the Karma plugin; I'm not # entirely sure that it's completely necessary anymore. self.assertEqual('foobar--', callbacks.canonicalName('foobar--')) def testAddressed(self): irc = getTestIrc() oldprefixchars = str(conf.supybot.reply.whenAddressedBy.chars) nick = 'supybot' conf.supybot.reply.whenAddressedBy.chars.set('~!@') inChannel = ['~foo', '@foo', '!foo', '%s: foo' % nick, '%s foo' % nick, '%s: foo' % nick.capitalize(), '%s: foo' % nick.upper()] inChannel = [ircmsgs.privmsg('#foo', s) for s in inChannel] badmsg = ircmsgs.privmsg('#foo', '%s:foo' % nick) self.assertFalse(callbacks.addressed(nick, badmsg)) badmsg = ircmsgs.privmsg('#foo', '%s^: foo' % nick) self.assertFalse(callbacks.addressed(nick, badmsg)) for msg in inChannel: self.assertEqual('foo', callbacks.addressed(nick, msg), msg) msg = ircmsgs.privmsg(nick, 'foo') irc._tagMsg(msg) self.assertEqual('foo', callbacks.addressed(nick, msg)) conf.supybot.reply.whenAddressedBy.chars.set(oldprefixchars) msg = ircmsgs.privmsg('#foo', '%s::::: bar' % nick) self.assertEqual('bar', callbacks.addressed(nick, msg)) msg = ircmsgs.privmsg('#foo', '%s: foo' % nick.upper()) self.assertEqual('foo', callbacks.addressed(nick, msg)) badmsg = ircmsgs.privmsg('#foo', '%s`: foo' % nick) self.assertFalse(callbacks.addressed(nick, badmsg)) def testAddressedReplyWhenNotAddressed(self): msg1 = ircmsgs.privmsg('#foo', '@bar') msg2 = ircmsgs.privmsg('#foo', 'bar') self.assertEqual(callbacks.addressed('blah', msg1), 'bar') self.assertEqual(callbacks.addressed('blah', msg2), '') try: original = conf.supybot.reply.whenNotAddressed() conf.supybot.reply.whenNotAddressed.setValue(True) # need to recreate the msg objects since the old ones have already # been tagged msg1 = ircmsgs.privmsg('#foo', '@bar') msg2 = ircmsgs.privmsg('#foo', 'bar') self.assertEqual(callbacks.addressed('blah', msg1), 'bar') self.assertEqual(callbacks.addressed('blah', msg2), 'bar') finally: conf.supybot.reply.whenNotAddressed.setValue(original) def testAddressedWithMultipleNicks(self): irc = getTestIrc() msg = ircmsgs.privmsg('#foo', 'bar: baz') irc._tagMsg(msg) self.assertEqual(callbacks.addressed('bar', msg), 'baz') # need to recreate the msg objects since the old ones have already # been tagged msg = ircmsgs.privmsg('#foo', 'bar: baz') irc._tagMsg(msg) self.assertEqual(callbacks.addressed('biff', msg, nicks=['bar']), 'baz') def testAddressedWithNickAtEnd(self): irc = getTestIrc() msg = ircmsgs.privmsg('#foo', 'baz, bar') irc._tagMsg(msg) self.assertEqual(callbacks.addressed('bar', msg, whenAddressedByNickAtEnd=True), 'baz') def testAddressedPrefixCharsTakePrecedenceOverNickAtEnd(self): irc = getTestIrc() msg = ircmsgs.privmsg('#foo', '@echo foo') irc._tagMsg(msg) self.assertEqual(callbacks.addressed('foo', msg, whenAddressedByNickAtEnd=True, prefixChars='@'), 'echo foo') def testReply(self): irc = getTestIrc() prefix = 'foo!bar@baz' channelMsg = ircmsgs.privmsg('#foo', 'bar baz', prefix=prefix) nonChannelMsg = ircmsgs.privmsg('supybot', 'bar baz', prefix=prefix) irc._tagMsg(channelMsg) irc._tagMsg(nonChannelMsg) self.assertEqual(ircmsgs.notice(nonChannelMsg.nick, 'foo'), callbacks._makeReply(irc, channelMsg, 'foo', private=True)) self.assertEqual(ircmsgs.notice(nonChannelMsg.nick, 'foo'), callbacks._makeReply(irc, nonChannelMsg, 'foo')) self.assertEqual(ircmsgs.privmsg(channelMsg.args[0], '%s: foo' % channelMsg.nick), callbacks._makeReply(irc, channelMsg, 'foo')) self.assertEqual(ircmsgs.privmsg(channelMsg.args[0], 'foo'), callbacks._makeReply(irc, channelMsg, 'foo', prefixNick=False)) self.assertEqual(ircmsgs.notice(nonChannelMsg.nick, 'foo'), callbacks._makeReply(irc, channelMsg, 'foo', notice=True, private=True)) def testReplyStatusmsg(self): irc = getTestIrc() prefix = 'foo!bar@baz' msg = ircmsgs.privmsg('+#foo', 'bar baz', prefix=prefix) irc._tagMsg(msg) # No statusmsg set, so understood as being a private message, so # private reply self.assertEqual(ircmsgs.notice(msg.nick, 'foo'), callbacks._makeReply(irc, msg, 'foo')) irc.state.supported['statusmsg'] = '+' msg = ircmsgs.privmsg('+#foo', 'bar baz', prefix=prefix) irc._tagMsg(msg) print(msg.channel) self.assertEqual(ircmsgs.privmsg('+#foo', '%s: foo' % msg.nick), callbacks._makeReply(irc, msg, 'foo')) def testReplyTo(self): irc = getTestIrc() prefix = 'foo!bar@baz' msg = ircmsgs.privmsg('#foo', 'bar baz', prefix=prefix) irc._tagMsg(msg) self.assertEqual(callbacks._makeReply(irc, msg, 'blah', to='blah'), ircmsgs.privmsg('#foo', 'blah: blah')) self.assertEqual(callbacks._makeReply(irc, msg, 'blah', to='blah', private=True), ircmsgs.notice('blah', 'blah')) def testTokenize(self): self.assertEqual(callbacks.tokenize(''), []) self.assertEqual(callbacks.tokenize('foo'), ['foo']) self.assertEqual(callbacks.tokenize('foo'), ['foo']) self.assertEqual(callbacks.tokenize('bar [baz]'), ['bar', ['baz']]) class AmbiguityTestCase(PluginTestCase): plugins = ('Misc',) # Something so it doesn't complain. class Foo(callbacks.Plugin): def bar(self, irc, msg, args): irc.reply('foo.bar') class Bar(callbacks.Plugin): def bar(self, irc, msg, args): irc.reply('bar.bar') def testAmbiguityWithCommandSameNameAsPlugin(self): self.irc.addCallback(self.Foo(self.irc)) self.assertResponse('bar', 'foo.bar') self.irc.addCallback(self.Bar(self.irc)) self.assertResponse('bar', 'bar.bar') class ProperStringificationOfReplyArgs(PluginTestCase): plugins = ('Misc',) # Same as above. class NonString(callbacks.Plugin): def int(self, irc, msg, args): irc.reply(1) class ExpectsString(callbacks.Plugin): def lower(self, irc, msg, args): irc.reply(args[0].lower()) def test(self): self.irc.addCallback(self.NonString(self.irc)) self.irc.addCallback(self.ExpectsString(self.irc)) self.assertResponse('expectsstring lower [nonstring int]', '1') ## class PrivmsgTestCaseWithKarma(ChannelPluginTestCase): ## plugins = ('Utilities', 'Misc', 'Web', 'Karma', 'String') ## conf.allowEval = True ## timeout = 2 ## def testSecondInvalidCommandRespondsWithThreadedInvalidCommands(self): ## try: ## orig = conf.supybot.plugins.Karma.response() ## conf.supybot.plugins.Karma.response.setValue(True) ## self.assertNotRegexp('echo [foo++] [foo++]', 'not a valid') ## _ = self.irc.takeMsg() ## finally: ## conf.supybot.plugins.Karma.response.setValue(orig) class PrivmsgTestCase(ChannelPluginTestCase): plugins = ('Utilities', 'Misc', 'Web', 'String') conf.allowEval = True timeout = 2 def testEmptySquareBrackets(self): self.assertError('echo []') ## def testHelpNoNameError(self): ## # This will raise a NameError if some dynamic scoping isn't working ## self.assertNotError('load Http') ## self.assertHelp('extension') def testMaximumNestingDepth(self): original = conf.supybot.commands.nested.maximum() try: conf.supybot.commands.nested.maximum.setValue(3) self.assertResponse('echo foo', 'foo') self.assertResponse('echo [echo foo]', 'foo') self.assertResponse('echo [echo [echo foo]]', 'foo') self.assertResponse('echo [echo [echo [echo foo]]]', 'foo') self.assertError('echo [echo [echo [echo [echo foo]]]]') finally: conf.supybot.commands.nested.maximum.setValue(original) def testSimpleReply(self): self.assertResponse("eval irc.reply('foo')", 'foo') def testSimpleReplyAction(self): self.assertResponse("eval irc.reply('foo', action=True)", '\x01ACTION foo\x01') def testReplyWithNickPrefix(self): self.feedMsg('@len foo') m = self.irc.takeMsg() self.assertTrue(m is not None, 'm: %r' % m) self.assertTrue(m.args[1].startswith(self.nick)) try: original = conf.supybot.reply.withNickPrefix() conf.supybot.reply.withNickPrefix.setValue(False) self.feedMsg('@len foobar') m = self.irc.takeMsg() self.assertTrue(m is not None) self.assertFalse(m.args[1].startswith(self.nick)) finally: conf.supybot.reply.withNickPrefix.setValue(original) def testErrorPrivateKwarg(self): try: original = conf.supybot.reply.error.inPrivate() conf.supybot.reply.error.inPrivate.setValue(False) m = self.getMsg("eval irc.error('foo', private=True)") self.assertTrue(m, 'No message returned.') self.assertFalse(ircutils.isChannel(m.args[0])) finally: conf.supybot.reply.error.inPrivate.setValue(original) def testErrorNoArgumentIsSilent(self): self.assertResponse('eval irc.error()', 'None') def testErrorWithNotice(self): try: original = conf.supybot.reply.error.withNotice() conf.supybot.reply.error.withNotice.setValue(True) m = self.getMsg("eval irc.error('foo')") self.assertTrue(m, 'No message returned.') self.assertTrue(m.command == 'NOTICE') finally: conf.supybot.reply.error.withNotice.setValue(original) def testErrorReplyPrivate(self): try: original = str(conf.supybot.reply.error.inPrivate) conf.supybot.reply.error.inPrivate.set('False') # If this doesn't raise an error, we've got a problem, so the next # two assertions shouldn't run. So we first check that what we # expect to error actually does so we don't go on a wild goose # chase because our command never errored in the first place :) s = 're s/foo/bar baz' # will error; should be "re s/foo/bar/ baz" self.assertError(s) m = self.getMsg(s) self.assertTrue(ircutils.isChannel(m.args[0])) conf.supybot.reply.error.inPrivate.set('True') m = self.getMsg(s) self.assertFalse(ircutils.isChannel(m.args[0])) finally: conf.supybot.reply.error.inPrivate.set(original) # Now for stuff not based on the plugins. class First(callbacks.Plugin): def firstcmd(self, irc, msg, args): """First""" irc.reply('foo') class Second(callbacks.Plugin): def secondcmd(self, irc, msg, args): """Second""" irc.reply('bar') class FirstRepeat(callbacks.Plugin): def firstcmd(self, irc, msg, args): """FirstRepeat""" irc.reply('baz') class Third(callbacks.Plugin): def third(self, irc, msg, args): """Third""" irc.reply(' '.join(args)) def tearDown(self): if hasattr(self.First, 'first'): del self.First.first if hasattr(self.Second, 'second'): del self.Second.second if hasattr(self.FirstRepeat, 'firstrepeat'): del self.FirstRepeat.firstrepeat ChannelPluginTestCase.tearDown(self) def testDispatching(self): self.irc.addCallback(self.First(self.irc)) self.irc.addCallback(self.Second(self.irc)) self.assertResponse('firstcmd', 'foo') self.assertResponse('secondcmd', 'bar') self.assertResponse('first firstcmd', 'foo') self.assertResponse('second secondcmd', 'bar') self.assertRegexp('first first firstcmd', 'there is no command named "first" in it') def testAmbiguousError(self): self.irc.addCallback(self.First(self.irc)) self.assertNotError('firstcmd') self.irc.addCallback(self.FirstRepeat(self.irc)) self.assertError('firstcmd') self.assertError('firstcmd [firstcmd]') self.assertNotRegexp('firstcmd', '(foo.*baz|baz.*foo)') self.assertResponse('first firstcmd', 'foo') self.assertResponse('firstrepeat firstcmd', 'baz') def testAmbiguousHelpError(self): self.irc.addCallback(self.First(self.irc)) self.irc.addCallback(self.FirstRepeat(self.irc)) self.assertError('help first') def testHelpDispatching(self): self.irc.addCallback(self.First(self.irc)) self.assertHelp('help firstcmd') self.assertHelp('help first firstcmd') self.irc.addCallback(self.FirstRepeat(self.irc)) self.assertError('help firstcmd') self.assertRegexp('help first firstcmd', 'First', 0) # no re.I flag. self.assertRegexp('help firstrepeat firstcmd', 'FirstRepeat', 0) class TwoRepliesFirstAction(callbacks.Plugin): def testactionreply(self, irc, msg, args): irc.reply('foo', action=True) irc.reply('bar') # We're going to check that this isn't an action. def testNotActionSecondReply(self): self.irc.addCallback(self.TwoRepliesFirstAction(self.irc)) self.assertAction('testactionreply', 'foo') m = self.getMsg(' ') self.assertFalse(m.args[1].startswith('\x01ACTION')) def testEmptyNest(self): try: conf.supybot.reply.whenNotCommand.set('True') self.assertError('echo []') conf.supybot.reply.whenNotCommand.set('False') self.assertResponse('echo []', '[]') finally: conf.supybot.reply.whenNotCommand.set('False') def testDispatcherHelp(self): self.assertNotRegexp('help first', r'\(dispatcher') self.assertNotRegexp('help first', r'%s') def testDefaultCommand(self): self.irc.addCallback(self.First(self.irc)) self.irc.addCallback(self.Third(self.irc)) self.assertError('first blah') self.assertResponse('third foo bar baz', 'foo bar baz') def testSyntaxErrorNotEscaping(self): self.assertError('load [foo') self.assertError('load foo]') def testNoEscapingAttributeErrorFromTokenizeWithFirstElementList(self): self.assertError('[plugin list] list') class InvalidCommand(callbacks.Plugin): def invalidCommand(self, irc, msg, tokens): irc.reply('foo') def testInvalidCommandOneReplyOnly(self): try: original = str(conf.supybot.reply.whenNotCommand) conf.supybot.reply.whenNotCommand.set('True') self.assertRegexp('asdfjkl', 'not a valid command') self.irc.addCallback(self.InvalidCommand(self.irc)) self.assertResponse('asdfjkl', 'foo') self.assertNoResponse(' ', 2) finally: conf.supybot.reply.whenNotCommand.set(original) class BadInvalidCommand(callbacks.Plugin): def invalidCommand(self, irc, msg, tokens): s = 'This shouldn\'t keep Misc.invalidCommand from being called' raise Exception(s) def testBadInvalidCommandDoesNotKillAll(self): try: original = str(conf.supybot.reply.whenNotCommand) conf.supybot.reply.whenNotCommand.set('True') self.irc.addCallback(self.BadInvalidCommand(self.irc)) self.assertRegexp('asdfjkl', 'not a valid command', expectException=True) finally: conf.supybot.reply.whenNotCommand.set(original) class PluginRegexpTestCase(PluginTestCase): plugins = () class PCAR(callbacks.PluginRegexp): def test(self, irc, msg, args): "" raise callbacks.ArgumentError def testNoEscapingArgumentError(self): self.irc.addCallback(self.PCAR(self.irc)) self.assertResponse('test', 'test ') class RichReplyMethodsTestCase(PluginTestCase): plugins = ('Config',) class NoCapability(callbacks.Plugin): def error(self, irc, msg, args): irc.errorNoCapability('admin') def testErrorNoCapability(self): self.irc.addCallback(self.NoCapability(self.irc)) self.assertRegexp('error', 'You don\'t have the admin capability') self.assertNotError('config capabilities.private admin') self.assertRegexp('error', 'Error: You\'re missing some capability') self.assertNotError('config capabilities.private ""') class SourceNestedPluginTestCase(PluginTestCase): plugins = ('Utilities',) class E(callbacks.Plugin): def f(self, irc, msg, args): """takes no arguments F """ irc.reply('f') def empty(self, irc, msg, args): pass class g(callbacks.Commands): def h(self, irc, msg, args): """takes no arguments H """ irc.reply('h') class i(callbacks.Commands): def j(self, irc, msg, args): """takes no arguments J """ irc.reply('j') class same(callbacks.Commands): def same(self, irc, msg, args): """takes no arguments same """ irc.reply('same') def test(self): cb = self.E(self.irc) self.irc.addCallback(cb) self.assertEqual(cb.getCommand(['f']), ['f']) self.assertEqual(cb.getCommand(['same']), ['same']) self.assertEqual(cb.getCommand(['e', 'f']), ['e', 'f']) self.assertEqual(cb.getCommand(['e', 'g', 'h']), ['e', 'g', 'h']) self.assertEqual(cb.getCommand(['e', 'g', 'i', 'j']), ['e', 'g', 'i', 'j']) self.assertResponse('e f', 'f') self.assertResponse('e same', 'same') self.assertResponse('e g h', 'h') self.assertResponse('e g i j', 'j') self.assertHelp('help f') self.assertHelp('help empty') self.assertHelp('help same') self.assertHelp('help e g h') self.assertHelp('help e g i j') self.assertRegexp('list e', 'f, g h, g i j, and same') def testCommandSameNameAsNestedPlugin(self): cb = self.E(self.irc) self.irc.addCallback(cb) self.assertResponse('e f', 'f') # Just to make sure it was loaded. self.assertEqual(cb.getCommand(['e', 'same']), ['e', 'same']) self.assertResponse('e same', 'same') class WithPrivateNoticeTestCase(ChannelPluginTestCase): plugins = ('Utilities',) class WithPrivateNotice(callbacks.Plugin): def normal(self, irc, msg, args): irc.reply('should be with private notice') def explicit(self, irc, msg, args): irc.reply('should not be with private notice', private=False, notice=False) def implicit(self, irc, msg, args): irc.reply('should be with notice due to private', private=True) def test(self): self.irc.addCallback(self.WithPrivateNotice(self.irc)) # Check normal behavior. m = self.assertNotError('normal') self.assertFalse(m.command == 'NOTICE') self.assertTrue(ircutils.isChannel(m.args[0])) m = self.assertNotError('explicit') self.assertFalse(m.command == 'NOTICE') self.assertTrue(ircutils.isChannel(m.args[0])) # Check abnormal behavior. originalInPrivate = conf.supybot.reply.inPrivate() originalWithNotice = conf.supybot.reply.withNotice() try: conf.supybot.reply.inPrivate.setValue(True) conf.supybot.reply.withNotice.setValue(True) m = self.assertNotError('normal') self.assertTrue(m.command == 'NOTICE') self.assertFalse(ircutils.isChannel(m.args[0])) m = self.assertNotError('explicit') self.assertFalse(m.command == 'NOTICE') self.assertTrue(ircutils.isChannel(m.args[0])) finally: conf.supybot.reply.inPrivate.setValue(originalInPrivate) conf.supybot.reply.withNotice.setValue(originalWithNotice) orig = conf.supybot.reply.withNoticeWhenPrivate() try: conf.supybot.reply.withNoticeWhenPrivate.setValue(True) m = self.assertNotError('implicit') self.assertTrue(m.command == 'NOTICE') self.assertFalse(ircutils.isChannel(m.args[0])) m = self.assertNotError('normal') self.assertFalse(m.command == 'NOTICE') self.assertTrue(ircutils.isChannel(m.args[0])) finally: conf.supybot.reply.withNoticeWhenPrivate.setValue(orig) def testWithNoticeWhenPrivateNotChannel(self): original = conf.supybot.reply.withNoticeWhenPrivate() try: conf.supybot.reply.withNoticeWhenPrivate.setValue(True) m = self.assertNotError("eval irc.reply('y',to='x',private=True)") self.assertTrue(m.command == 'NOTICE') m = self.getMsg(' ') m = self.assertNotError("eval irc.reply('y',to='#x',private=True)") self.assertFalse(m.command == 'NOTICE') finally: conf.supybot.reply.withNoticeWhenPrivate.setValue(original) class ProxyTestCase(SupyTestCase): def testHashing(self): irc = getTestIrc() msg = ircmsgs.ping('0') irc._tagMsg(msg) irc = irclib.Irc('test') proxy = callbacks.SimpleProxy(irc, msg) # First one way... self.assertFalse(proxy != irc) self.assertTrue(proxy == irc) self.assertEqual(hash(proxy), hash(irc)) # Then the other! self.assertFalse(irc != proxy) self.assertTrue(irc == proxy) self.assertEqual(hash(irc), hash(proxy)) # And now dictionaries... d = {} d[irc] = 'foo' self.assertTrue(len(d) == 1) self.assertTrue(d[irc] == 'foo') self.assertTrue(d[proxy] == 'foo') d[proxy] = 'bar' self.assertTrue(len(d) == 1) self.assertTrue(d[irc] == 'bar') self.assertTrue(d[proxy] == 'bar') d[irc] = 'foo' self.assertTrue(len(d) == 1) self.assertTrue(d[irc] == 'foo') self.assertTrue(d[proxy] == 'foo') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/test/test_commands.py0000644000175000017500000002137613634634533017522 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2015, James McCoy # 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 sys import getopt from supybot.test import * from supybot.commands import * import supybot.conf as conf import supybot.irclib as irclib import supybot.ircmsgs as ircmsgs import supybot.utils.minisix as minisix import supybot.callbacks as callbacks class CommandsTestCase(SupyTestCase): def assertState(self, spec, given, expected, target='test', **kwargs): msg = ircmsgs.privmsg(target, 'foo') realIrc = getTestIrc() realIrc.nick = 'test' realIrc.state.supported['chantypes'] = '#' realIrc._tagMsg(msg) irc = callbacks.SimpleProxy(realIrc, msg) myspec = Spec(spec, **kwargs) state = myspec(irc, msg, given) self.assertEqual(state.args, expected, 'Expected %r, got %r' % (expected, state.args)) def assertError(self, spec, given): self.assertRaises(callbacks.Error, self.assertState, spec, given, given) def assertStateErrored(self, spec, given, target='test', errored=True, **kwargs): msg = ircmsgs.privmsg(target, 'foo') realIrc = getTestIrc() realIrc.nick = 'test' realIrc.state.supported['chantypes'] = '#' irc = callbacks.SimpleProxy(realIrc, msg) myspec = Spec(spec, **kwargs) state = myspec(irc, msg, given) self.assertEqual(state.errored, errored, 'Expected %r, got %r' % (errored, state.errored)) class GeneralContextTestCase(CommandsTestCase): def testEmptySpec(self): self.assertState([], [], []) def testSpecInt(self): self.assertState(['int'], ['1'], [1]) self.assertState(['int', 'int', 'int'], ['1', '2', '3'], [1, 2, 3]) self.assertError(['int'], ['9e999']) def testSpecNick(self): strict = conf.supybot.protocols.irc.strictRfc() try: conf.supybot.protocols.irc.strictRfc.setValue(True) self.assertError(['nick'], ['1abc']) conf.supybot.protocols.irc.strictRfc.setValue(False) self.assertState(['nick'], ['1abc'], ['1abc']) finally: conf.supybot.protocols.irc.strictRfc.setValue(strict) if minisix.PY2: def testSpecLong(self): self.assertState(['long'], ['1'], [long(1)]) self.assertState(['long', 'long', 'long'], ['1', '2', '3'], [long(1), long(2), long(3)]) def testRestHandling(self): self.assertState([rest(None)], ['foo', 'bar', 'baz'], ['foo bar baz']) def testRestRequiresArgs(self): self.assertError([rest('something')], []) def testOptional(self): spec = [optional('int', 999), None] self.assertState(spec, ['12', 'foo'], [12, 'foo']) self.assertState(spec, ['foo'], [999, 'foo']) def testAdditional(self): spec = [additional('int', 999)] self.assertState(spec, ['12'], [12]) self.assertState(spec, [], [999]) self.assertError(spec, ['foo']) def testReverse(self): spec = [reverse('positiveInt'), 'float', 'text'] self.assertState(spec, ['-1.0', 'foo', '1'], [1, -1.0, 'foo']) def testGetopts(self): spec = ['int', getopts({'foo': None, 'bar': 'int'}), 'int'] self.assertState(spec, ['12', '--foo', 'baz', '--bar', '13', '15'], [12, [('foo', 'baz'), ('bar', 13)], 15]) def testGetoptsShort(self): spec = ['int', getopts({'foo': None, 'bar': 'int'}), 'int'] self.assertState(spec, ['12', '--f', 'baz', '--ba', '13', '15'], [12, [('foo', 'baz'), ('bar', 13)], 15]) def testGetoptsConflict(self): spec = ['int', getopts({'foo': None, 'fbar': 'int'}), 'int'] self.assertRaises(getopt.GetoptError, self.assertStateErrored, spec, ['12', '--f', 'baz', '--ba', '13', '15']) def testAny(self): self.assertState([any('int')], ['1', '2', '3'], [[1, 2, 3]]) self.assertState([None, any('int')], ['1', '2', '3'], ['1', [2, 3]]) self.assertState([any('int')], [], [[]]) self.assertState([any('int', continueOnError=True), 'text'], ['1', '2', 'test'], [[1, 2], 'test']) def testMany(self): spec = [many('int')] self.assertState(spec, ['1', '2', '3'], [[1, 2, 3]]) self.assertError(spec, []) def testChannelRespectsNetwork(self): spec = ['channel', 'text'] self.assertState(spec, ['#foo', '+s'], ['#foo', '+s']) self.assertState(spec, ['+s'], ['#foo', '+s'], target='#foo') def testGlob(self): spec = ['glob'] self.assertState(spec, ['foo'], ['*foo*']) self.assertState(spec, ['?foo'], ['?foo']) self.assertState(spec, ['foo*'], ['foo*']) def testGetId(self): spec = ['id'] self.assertState(spec, ['#12'], [12]) def testCommaList(self): spec = [commalist('int')] self.assertState(spec, ['12'], [[12]]) self.assertState(spec, ['12,', '10'], [[12, 10]]) self.assertState(spec, ['12,11,10,', '9'], [[12, 11, 10, 9]]) spec.append('int') self.assertState(spec, ['12,11,10', '9'], [[12, 11, 10], 9]) def testLiteral(self): spec = [('literal', ['foo', 'bar', 'baz'])] self.assertState(spec, ['foo'], ['foo']) self.assertState(spec, ['fo'], ['foo']) self.assertState(spec, ['f'], ['foo']) self.assertState(spec, ['bar'], ['bar']) self.assertState(spec, ['baz'], ['baz']) self.assertError(spec, ['ba']) class ConverterTestCase(CommandsTestCase): def testUrlAllowsHttps(self): url = 'https://foo.bar/baz' self.assertState(['url'], [url], [url]) self.assertState(['httpUrl'], [url], [url]) def testEmail(self): email = 'jemfinch@supybot.com' self.assertState(['email'], [email], [email]) self.assertError(['email'], ['foo']) self.assertError(['email'], ['foo@']) self.assertError(['email'], ['@foo']) class FirstTestCase(CommandsTestCase): def testRepr(self): self.assertTrue(repr(first('int'))) def testFirstConverterFailsAndNotErroredState(self): self.assertStateErrored([first('int', 'something')], ['words'], errored=False) def testLongRegexp(self): spec = [first('regexpMatcher', 'regexpReplacer'), 'text'] self.assertStateErrored(spec, ['s/foo/bar/', 'x' * 512], errored=False) class GetoptTestCase(PluginTestCase): plugins = ('Misc',) # We put something so it does not complain class Foo(callbacks.Plugin): def bar(self, irc, msg, args, optlist): irc.reply(' '.join(sorted(['%s:%d'%x for x in optlist]))) bar = wrap(bar, [getopts({'foo': 'int', 'fbar': 'int'})], checkDoc=False) def testGetoptsExact(self): self.irc.addCallback(self.Foo(self.irc)) self.assertResponse('bar --foo 3 --fbar 4', 'fbar:4 foo:3') self.assertResponse('bar --fo 3 --fb 4', 'fbar:4 foo:3') self.assertResponse('bar --f 3 --fb 5', 'Error: Invalid arguments for bar.') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/test/test_dynamicScope.py0000644000175000017500000000455613634634533020340 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * class TestDynamic(SupyTestCase): def test(self): def f(x): i = 2 return g(x) def g(y): j = 3 return h(y) def h(z): self.assertEqual(dynamic.z, z) self.assertEqual(dynamic.j, 3) self.assertEqual(dynamic.i, 2) self.assertEqual(dynamic.y, z) self.assertEqual(dynamic.x, z) #self.assertRaises(NameError, getattr, dynamic, 'asdfqwerqewr') self.assertEqual(dynamic.self, self) return z self.assertEqual(f(10), 10) def testCommonUsage(self): foo = 'bar' def f(): foo = dynamic.foo self.assertEqual(foo, 'bar') f() # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/test/test_firewall.py0000644000175000017500000000615513634634533017524 0ustar valval00000000000000### # Copyright (c) 2008, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * from supybot import log import supybot.utils.minisix as minisix class FirewallTestCase(SupyTestCase): def setUp(self): log.testing = False def tearDown(self): log.testing = True # Python 3's syntax for metaclasses is incompatible with Python 3 so # using Python 3's syntax directly will raise a SyntaxError on Python 2. exec(""" class C(%s __firewalled__ = {'foo': None} class MyException(Exception): pass def foo(self): raise self.MyException()""" % ('metaclass=log.MetaFirewall):\n' if minisix.PY3 else 'object):\n __metaclass__ = log.MetaFirewall')) def testCFooDoesNotRaise(self): c = self.C() self.assertEqual(c.foo(), None) class D(C): def foo(self): raise self.MyException() def testDFooDoesNotRaise(self): d = self.D() self.assertEqual(d.foo(), None) class E(C): __firewalled__ = {'bar': None} def foo(self): raise self.MyException() def bar(self): raise self.MyException() def testEFooDoesNotRaise(self): e = self.E() self.assertEqual(e.foo(), None) def testEBarDoesNotRaise(self): e = self.E() self.assertEqual(e.bar(), None) class F(C): __firewalled__ = {'bar': lambda self: 2} def bar(self): raise self.MyException() def testFBarReturns2(self): f = self.F() self.assertEqual(f.bar(), 2) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/test/test_format.py0000644000175000017500000000353113634634533017202 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * class FormatTestCase(SupyTestCase): def test_t_acceptsNone(self): self.assertTrue(format('%t', None)) def testFloatingPoint(self): self.assertEqual(format('%.2f', 0.12345), '0.12') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/test/test_i18n.py0000644000175000017500000000541313634634533016472 0ustar valval00000000000000# -*- coding: utf8 -*- ### # Copyright (c) 2012, Valentin Lorentz # 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 * from supybot.commands import wrap from supybot.i18n import PluginInternationalization, internationalizeDocstring import supybot.conf as conf msg_en = 'The operation succeeded.' msg_fr = 'Opération effectuée avec succès.' _ = PluginInternationalization() @internationalizeDocstring def foo(): """The operation succeeded.""" pass @wrap def bar(): """The operation succeeded.""" pass class I18nTestCase(SupyTestCase): def testPluginInternationalization(self): self.assertEqual(_(msg_en), msg_en) with conf.supybot.language.context('fr'): self.assertEqual(_(msg_en), msg_fr) conf.supybot.language.setValue('en') self.assertEqual(_(msg_en), msg_en) multiline = '%s\n\n%s' % (msg_en, msg_en) self.assertEqual(_(multiline), multiline) @retry() def testDocstring(self): self.assertEqual(foo.__doc__, msg_en) self.assertEqual(bar.__doc__, msg_en) with conf.supybot.language.context('fr'): self.assertEqual(foo.__doc__, msg_fr) self.assertEqual(bar.__doc__, msg_fr) self.assertEqual(foo.__doc__, msg_en) self.assertEqual(bar.__doc__, msg_en) limnoria-2020.03.17/test/test_ircdb.py0000644000175000017500000005731413634634533017005 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * import os import unittest import supybot.conf as conf import supybot.world as world import supybot.ircdb as ircdb import supybot.ircutils as ircutils class IrcdbTestCase(SupyTestCase): def setUp(self): world.testing = False SupyTestCase.setUp(self) def tearDown(self): world.testing = True SupyTestCase.tearDown(self) class FunctionsTestCase(IrcdbTestCase): def testIsAntiCapability(self): self.assertFalse(ircdb.isAntiCapability('foo')) self.assertFalse(ircdb.isAntiCapability('#foo,bar')) self.assertTrue(ircdb.isAntiCapability('-foo')) self.assertTrue(ircdb.isAntiCapability('#foo,-bar')) self.assertTrue(ircdb.isAntiCapability('#foo.bar,-baz')) def testIsChannelCapability(self): self.assertFalse(ircdb.isChannelCapability('foo')) self.assertTrue(ircdb.isChannelCapability('#foo,bar')) self.assertTrue(ircdb.isChannelCapability('#foo.bar,baz')) self.assertTrue(ircdb.isChannelCapability('#foo,bar.baz')) def testMakeAntiCapability(self): self.assertEqual(ircdb.makeAntiCapability('foo'), '-foo') self.assertEqual(ircdb.makeAntiCapability('#foo,bar'), '#foo,-bar') def testMakeChannelCapability(self): self.assertEqual(ircdb.makeChannelCapability('#f', 'b'), '#f,b') self.assertEqual(ircdb.makeChannelCapability('#f', '-b'), '#f,-b') def testFromChannelCapability(self): self.assertEqual(ircdb.fromChannelCapability('#foo,bar'), ['#foo', 'bar']) self.assertEqual(ircdb.fromChannelCapability('#foo.bar,baz'), ['#foo.bar', 'baz']) self.assertEqual(ircdb.fromChannelCapability('#foo,bar.baz'), ['#foo', 'bar.baz']) def testUnAntiCapability(self): self.assertEqual(ircdb.unAntiCapability('-bar'), 'bar') self.assertEqual(ircdb.unAntiCapability('#foo,-bar'), '#foo,bar') self.assertEqual(ircdb.unAntiCapability('#foo.bar,-baz'), '#foo.bar,baz') def testInvertCapability(self): self.assertEqual(ircdb.invertCapability('bar'), '-bar') self.assertEqual(ircdb.invertCapability('-bar'), 'bar') self.assertEqual(ircdb.invertCapability('#foo,bar'), '#foo,-bar') self.assertEqual(ircdb.invertCapability('#foo,-bar'), '#foo,bar') class CapabilitySetTestCase(SupyTestCase): def testGeneral(self): d = ircdb.CapabilitySet() self.assertRaises(KeyError, d.check, 'foo') d = ircdb.CapabilitySet(('foo',)) self.assertTrue(d.check('foo')) self.assertFalse(d.check('-foo')) d.add('bar') self.assertTrue(d.check('bar')) self.assertFalse(d.check('-bar')) d.add('-baz') self.assertFalse(d.check('baz')) self.assertTrue(d.check('-baz')) d.add('-bar') self.assertFalse(d.check('bar')) self.assertTrue(d.check('-bar')) d.remove('-bar') self.assertRaises(KeyError, d.check, '-bar') self.assertRaises(KeyError, d.check, 'bar') def testReprEval(self): s = ircdb.UserCapabilitySet() self.assertEqual(s, eval(repr(s), ircdb.__dict__, ircdb.__dict__)) s.add('foo') self.assertEqual(s, eval(repr(s), ircdb.__dict__, ircdb.__dict__)) s.add('bar') self.assertEqual(s, eval(repr(s), ircdb.__dict__, ircdb.__dict__)) def testContains(self): s = ircdb.CapabilitySet() self.assertFalse('foo' in s) self.assertFalse('-foo' in s) s.add('foo') self.assertTrue('foo' in s) self.assertTrue('-foo' in s) s.remove('foo') self.assertFalse('foo' in s) self.assertFalse('-foo' in s) s.add('-foo') self.assertTrue('foo' in s) self.assertTrue('-foo' in s) def testCheck(self): s = ircdb.CapabilitySet() self.assertRaises(KeyError, s.check, 'foo') self.assertRaises(KeyError, s.check, '-foo') s.add('foo') self.assertTrue(s.check('foo')) self.assertFalse(s.check('-foo')) s.remove('foo') self.assertRaises(KeyError, s.check, 'foo') self.assertRaises(KeyError, s.check, '-foo') s.add('-foo') self.assertFalse(s.check('foo')) self.assertTrue(s.check('-foo')) s.remove('-foo') self.assertRaises(KeyError, s.check, 'foo') self.assertRaises(KeyError, s.check, '-foo') def testAdd(self): s = ircdb.CapabilitySet() s.add('foo') s.add('-foo') self.assertFalse(s.check('foo')) self.assertTrue(s.check('-foo')) s.add('foo') self.assertTrue(s.check('foo')) self.assertFalse(s.check('-foo')) class UserCapabilitySetTestCase(SupyTestCase): def testOwnerHasAll(self): d = ircdb.UserCapabilitySet(('owner',)) self.assertFalse(d.check('-foo')) self.assertTrue(d.check('foo')) def testOwnerIsAlwaysPresent(self): d = ircdb.UserCapabilitySet() self.assertTrue('owner' in d) self.assertTrue('-owner' in d) self.assertFalse(d.check('owner')) d.add('owner') self.assertTrue(d.check('owner')) def testReprEval(self): s = ircdb.UserCapabilitySet() self.assertEqual(s, eval(repr(s), ircdb.__dict__, ircdb.__dict__)) s.add('foo') self.assertEqual(s, eval(repr(s), ircdb.__dict__, ircdb.__dict__)) s.add('bar') self.assertEqual(s, eval(repr(s), ircdb.__dict__, ircdb.__dict__)) def testOwner(self): s = ircdb.UserCapabilitySet() s.add('owner') self.assertTrue('foo' in s) self.assertTrue('-foo' in s) self.assertTrue(s.check('owner')) self.assertFalse(s.check('-owner')) self.assertFalse(s.check('-foo')) self.assertTrue(s.check('foo')) ## def testWorksAfterReload(self): ## s = ircdb.UserCapabilitySet(['owner']) ## self.assertTrue(s.check('owner')) ## import sets ## reload(sets) ## self.assertTrue(s.check('owner')) class IrcUserTestCase(IrcdbTestCase): def testCapabilities(self): u = ircdb.IrcUser() u.addCapability('foo') self.assertTrue(u._checkCapability('foo')) self.assertFalse(u._checkCapability('-foo')) u.addCapability('-bar') self.assertTrue(u._checkCapability('-bar')) self.assertFalse(u._checkCapability('bar')) u.removeCapability('foo') u.removeCapability('-bar') self.assertRaises(KeyError, u._checkCapability, 'foo') self.assertRaises(KeyError, u._checkCapability, '-bar') def testAddhostmask(self): u = ircdb.IrcUser() self.assertRaises(ValueError, u.addHostmask, '*!*@*') def testRemoveHostmask(self): u = ircdb.IrcUser() u.addHostmask('foo!bar@baz') self.assertTrue(u.checkHostmask('foo!bar@baz')) u.addHostmask('foo!bar@baz') u.removeHostmask('foo!bar@baz') self.assertFalse(u.checkHostmask('foo!bar@baz')) def testOwner(self): u = ircdb.IrcUser() u.addCapability('owner') self.assertTrue(u._checkCapability('foo')) self.assertFalse(u._checkCapability('-foo')) def testInitCapabilities(self): u = ircdb.IrcUser(capabilities=['foo']) self.assertTrue(u._checkCapability('foo')) def testPassword(self): u = ircdb.IrcUser() u.setPassword('foobar') self.assertTrue(u.checkPassword('foobar')) self.assertFalse(u.checkPassword('somethingelse')) def testTimeoutAuth(self): orig = conf.supybot.databases.users.timeoutIdentification() try: conf.supybot.databases.users.timeoutIdentification.setValue(2) u = ircdb.IrcUser() u.addAuth('foo!bar@baz') self.assertTrue(u.checkHostmask('foo!bar@baz')) timeFastForward(2.1) self.assertFalse(u.checkHostmask('foo!bar@baz')) finally: conf.supybot.databases.users.timeoutIdentification.setValue(orig) def testMultipleAuth(self): orig = conf.supybot.databases.users.timeoutIdentification() try: conf.supybot.databases.users.timeoutIdentification.setValue(2) u = ircdb.IrcUser() u.addAuth('foo!bar@baz') self.assertTrue(u.checkHostmask('foo!bar@baz')) u.addAuth('foo!bar@baz') self.assertTrue(u.checkHostmask('foo!bar@baz')) self.assertTrue(len(u.auth) == 1) u.addAuth('boo!far@fizz') self.assertTrue(u.checkHostmask('boo!far@fizz')) timeFastForward(2.1) self.assertFalse(u.checkHostmask('foo!bar@baz')) self.assertFalse(u.checkHostmask('boo!far@fizz')) finally: conf.supybot.databases.users.timeoutIdentification.setValue(orig) def testHashedPassword(self): u = ircdb.IrcUser() u.setPassword('foobar', hashed=True) self.assertTrue(u.checkPassword('foobar')) self.assertFalse(u.checkPassword('somethingelse')) self.assertNotEqual(u.password, 'foobar') def testHostmasks(self): prefix = 'foo12341234!bar@baz.domain.tld' hostmasks = ['*!bar@baz.domain.tld', 'foo12341234!*@*'] u = ircdb.IrcUser() self.assertFalse(u.checkHostmask(prefix)) for hostmask in hostmasks: u.addHostmask(hostmask) self.assertTrue(u.checkHostmask(prefix)) def testAuth(self): prefix = 'foo!bar@baz' u = ircdb.IrcUser() u.addAuth(prefix) self.assertTrue(u.auth) u.clearAuth() self.assertFalse(u.auth) def testIgnore(self): u = ircdb.IrcUser(ignore=True) self.assertFalse(u._checkCapability('foo')) self.assertTrue(u._checkCapability('-foo')) def testRemoveCapability(self): u = ircdb.IrcUser(capabilities=('foo',)) self.assertRaises(KeyError, u.removeCapability, 'bar') class IrcChannelTestCase(IrcdbTestCase): def testInit(self): c = ircdb.IrcChannel() self.assertFalse(c._checkCapability('op')) self.assertFalse(c._checkCapability('voice')) self.assertFalse(c._checkCapability('halfop')) self.assertFalse(c._checkCapability('protected')) def testCapabilities(self): c = ircdb.IrcChannel(defaultAllow=False) self.assertFalse(c._checkCapability('foo')) c.addCapability('foo') self.assertTrue(c._checkCapability('foo')) c.removeCapability('foo') self.assertFalse(c._checkCapability('foo')) def testDefaultCapability(self): c = ircdb.IrcChannel() c.setDefaultCapability(False) self.assertFalse(c._checkCapability('foo')) self.assertTrue(c._checkCapability('-foo')) c.setDefaultCapability(True) self.assertTrue(c._checkCapability('foo')) self.assertFalse(c._checkCapability('-foo')) def testLobotomized(self): c = ircdb.IrcChannel(lobotomized=True) self.assertTrue(c.checkIgnored('foo!bar@baz')) def testIgnored(self): prefix = 'foo!bar@baz' banmask = ircutils.banmask(prefix) c = ircdb.IrcChannel() self.assertFalse(c.checkIgnored(prefix)) c.addIgnore(banmask) self.assertTrue(c.checkIgnored(prefix)) c.removeIgnore(banmask) self.assertFalse(c.checkIgnored(prefix)) c.addBan(banmask) self.assertTrue(c.checkIgnored(prefix)) c.removeBan(banmask) self.assertFalse(c.checkIgnored(prefix)) class UsersDictionaryTestCase(IrcdbTestCase): filename = os.path.join(conf.supybot.directories.conf(), 'UsersDictionaryTestCase.conf') def setUp(self): try: os.remove(self.filename) except: pass self.users = ircdb.UsersDictionary() IrcdbTestCase.setUp(self) def testIterAndNumUsers(self): self.assertEqual(self.users.numUsers(), 0) u = self.users.newUser() hostmask = 'foo!xyzzy@baz.domain.com' banmask = ircutils.banmask(hostmask) u.addHostmask(banmask) u.name = 'foo' self.users.setUser(u) self.assertEqual(self.users.numUsers(), 1) u = self.users.newUser() hostmask = 'biff!fladksfj@blakjdsf' banmask = ircutils.banmask(hostmask) u.addHostmask(banmask) u.name = 'biff' self.users.setUser(u) self.assertEqual(self.users.numUsers(), 2) self.users.delUser(2) self.assertEqual(self.users.numUsers(), 1) self.users.delUser(1) self.assertEqual(self.users.numUsers(), 0) def testGetSetDelUser(self): self.assertRaises(KeyError, self.users.getUser, 'foo') self.assertRaises(KeyError, self.users.getUser, 'foo!xyzzy@baz.domain.com') u = self.users.newUser() hostmask = 'foo!xyzzy@baz.domain.com' banmask = ircutils.banmask(hostmask) u.addHostmask(banmask) u.addHostmask(hostmask) u.name = 'foo' self.users.setUser(u) self.assertEqual(self.users.getUser('foo'), u) self.assertEqual(self.users.getUser('FOO'), u) self.assertEqual(self.users.getUser(hostmask), u) self.assertEqual(self.users.getUser(banmask), u) # The UsersDictionary shouldn't allow users to be added whose hostmasks # match another user's already in the database. u2 = self.users.newUser() u2.addHostmask('*!xyzzy@baz.domain.c?m') self.assertRaises(ValueError, self.users.setUser, u2) class CheckCapabilityTestCase(IrcdbTestCase): filename = os.path.join(conf.supybot.directories.conf(), 'CheckCapabilityTestCase.conf') owner = 'owner!owner@owner' nothing = 'nothing!nothing@nothing' justfoo = 'justfoo!justfoo@justfoo' antifoo = 'antifoo!antifoo@antifoo' justchanfoo = 'justchanfoo!justchanfoo@justchanfoo' antichanfoo = 'antichanfoo!antichanfoo@antichanfoo' securefoo = 'securefoo!securefoo@securefoo' channel = '#channel' cap = 'foo' anticap = ircdb.makeAntiCapability(cap) chancap = ircdb.makeChannelCapability(channel, cap) antichancap = ircdb.makeAntiCapability(chancap) chanop = ircdb.makeChannelCapability(channel, 'op') channelnothing = ircdb.IrcChannel() channelcap = ircdb.IrcChannel() channelcap.addCapability(cap) channelanticap = ircdb.IrcChannel() channelanticap.addCapability(anticap) def setUp(self): IrcdbTestCase.setUp(self) try: os.remove(self.filename) except: pass self.users = ircdb.UsersDictionary() #self.users.open(self.filename) self.channels = ircdb.ChannelsDictionary() #self.channels.open(self.filename) owner = self.users.newUser() owner.name = 'owner' owner.addCapability('owner') owner.addHostmask(self.owner) self.users.setUser(owner) nothing = self.users.newUser() nothing.name = 'nothing' nothing.addHostmask(self.nothing) self.users.setUser(nothing) justfoo = self.users.newUser() justfoo.name = 'justfoo' justfoo.addCapability(self.cap) justfoo.addHostmask(self.justfoo) self.users.setUser(justfoo) antifoo = self.users.newUser() antifoo.name = 'antifoo' antifoo.addCapability(self.anticap) antifoo.addHostmask(self.antifoo) self.users.setUser(antifoo) justchanfoo = self.users.newUser() justchanfoo.name = 'justchanfoo' justchanfoo.addCapability(self.chancap) justchanfoo.addHostmask(self.justchanfoo) self.users.setUser(justchanfoo) antichanfoo = self.users.newUser() antichanfoo.name = 'antichanfoo' antichanfoo.addCapability(self.antichancap) antichanfoo.addHostmask(self.antichanfoo) self.users.setUser(antichanfoo) securefoo = self.users.newUser() securefoo.name = 'securefoo' securefoo.addCapability(self.cap) securefoo.secure = True securefoo.addHostmask(self.securefoo) self.users.setUser(securefoo) channel = ircdb.IrcChannel() self.channels.setChannel(self.channel, channel) def checkCapability(self, hostmask, capability): return ircdb.checkCapability(hostmask, capability, self.users, self.channels) def testOwner(self): self.assertTrue(self.checkCapability(self.owner, self.cap)) self.assertFalse(self.checkCapability(self.owner, self.anticap)) self.assertTrue(self.checkCapability(self.owner, self.chancap)) self.assertFalse(self.checkCapability(self.owner, self.antichancap)) self.channels.setChannel(self.channel, self.channelanticap) self.assertTrue(self.checkCapability(self.owner, self.cap)) self.assertFalse(self.checkCapability(self.owner, self.anticap)) def testNothingAgainstChannel(self): self.channels.setChannel(self.channel, self.channelnothing) self.assertEqual(self.checkCapability(self.nothing, self.chancap), self.channelnothing.defaultAllow) self.channelnothing.defaultAllow = not self.channelnothing.defaultAllow self.channels.setChannel(self.channel, self.channelnothing) self.assertEqual(self.checkCapability(self.nothing, self.chancap), self.channelnothing.defaultAllow) self.channels.setChannel(self.channel, self.channelcap) self.assertTrue(self.checkCapability(self.nothing, self.chancap)) self.assertFalse(self.checkCapability(self.nothing, self.antichancap)) self.channels.setChannel(self.channel, self.channelanticap) self.assertFalse(self.checkCapability(self.nothing, self.chancap)) self.assertTrue(self.checkCapability(self.nothing, self.antichancap)) def testNothing(self): self.assertEqual(self.checkCapability(self.nothing, self.cap), conf.supybot.capabilities.default()) self.assertEqual(self.checkCapability(self.nothing, self.anticap), not conf.supybot.capabilities.default()) def testJustFoo(self): self.assertTrue(self.checkCapability(self.justfoo, self.cap)) self.assertFalse(self.checkCapability(self.justfoo, self.anticap)) def testAntiFoo(self): self.assertTrue(self.checkCapability(self.antifoo, self.anticap)) self.assertFalse(self.checkCapability(self.antifoo, self.cap)) def testJustChanFoo(self): self.channels.setChannel(self.channel, self.channelnothing) self.assertTrue(self.checkCapability(self.justchanfoo, self.chancap)) self.assertFalse(self.checkCapability(self.justchanfoo, self.antichancap)) self.channelnothing.defaultAllow = not self.channelnothing.defaultAllow self.assertTrue(self.checkCapability(self.justchanfoo, self.chancap)) self.assertFalse(self.checkCapability(self.justchanfoo, self.antichancap)) self.channels.setChannel(self.channel, self.channelanticap) self.assertTrue(self.checkCapability(self.justchanfoo, self.chancap)) self.assertFalse(self.checkCapability(self.justchanfoo, self.antichancap)) def testChanOpCountsAsEverything(self): self.channels.setChannel(self.channel, self.channelanticap) id = self.users.getUserId('nothing') u = self.users.getUser(id) u.addCapability(self.chanop) self.users.setUser(u) self.assertTrue(self.checkCapability(self.nothing, self.chancap)) self.channels.setChannel(self.channel, self.channelnothing) self.assertTrue(self.checkCapability(self.nothing, self.chancap)) self.channelnothing.defaultAllow = not self.channelnothing.defaultAllow self.assertTrue(self.checkCapability(self.nothing, self.chancap)) def testAntiChanFoo(self): self.channels.setChannel(self.channel, self.channelnothing) self.assertFalse(self.checkCapability(self.antichanfoo, self.chancap)) self.assertTrue(self.checkCapability(self.antichanfoo, self.antichancap)) def testSecurefoo(self): self.assertTrue(self.checkCapability(self.securefoo, self.cap)) id = self.users.getUserId(self.securefoo) u = self.users.getUser(id) u.addAuth(self.securefoo) self.users.setUser(u) try: originalConfDefaultAllow = conf.supybot.capabilities.default() conf.supybot.capabilities.default.set('False') self.assertFalse(self.checkCapability('a' + self.securefoo, self.cap)) finally: conf.supybot.capabilities.default.set(str(originalConfDefaultAllow)) class PersistanceTestCase(IrcdbTestCase): filename = os.path.join(conf.supybot.directories.conf(), 'PersistanceTestCase.conf') def setUp(self): IrcdbTestCase.setUp(self) try: os.remove(self.filename) except OSError: pass super(PersistanceTestCase, self).setUp() def testAddUser(self): db = ircdb.UsersDictionary() db.filename = self.filename u = db.newUser() u.name = 'foouser' u.addCapability('foocapa') u.addHostmask('*!fooident@foohost') db.setUser(u) db.flush() db2 = ircdb.UsersDictionary() db2.open(self.filename) self.assertEqual(list(db2.users), [1]) self.assertEqual(db2.users[1].name, 'foouser') db.reload() self.assertEqual(list(db.users), [1]) self.assertEqual(db.users[1].name, 'foouser') def testAddRemoveUser(self): db = ircdb.UsersDictionary() db.filename = self.filename u = db.newUser() u.name = 'foouser' u.addCapability('foocapa') u.addHostmask('*!fooident@foohost') db.setUser(u) db2 = ircdb.UsersDictionary() db2.open(self.filename) self.assertEqual(list(db2.users), [1]) self.assertEqual(db2.users[1].name, 'foouser') db.delUser(1) self.assertEqual(list(db.users), []) db2 = ircdb.UsersDictionary() db2.open(self.filename) self.assertEqual(list(db.users), []) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/test/test_irclib.py0000644000175000017500000010732013634634533017157 0ustar valval00000000000000## # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * import copy import pickle import warnings import supybot.conf as conf import supybot.irclib as irclib import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils # The test framework used to provide these, but not it doesn't. We'll add # messages to as we find bugs (if indeed we find bugs). msgs = [] rawmsgs = [] class IrcCommandDispatcherTestCase(SupyTestCase): class DispatchedClass(irclib.IrcCommandDispatcher): def doPrivmsg(): pass def doCap(): pass def doFail(): pass class DispatchedClassSub(irclib.IrcCommandDispatcher): def doPrivmsg(): pass def doPrivmsgFoo(): pass def doCapLs(): pass def doFailFoo(): pass def testCommandDispatch(self): dispatcher = self.DispatchedClass() self.assertEqual( dispatcher.dispatchCommand('privmsg', ['foo']), dispatcher.doPrivmsg) self.assertEqual( dispatcher.dispatchCommand('cap', ['*', 'ls']), dispatcher.doCap) self.assertEqual( dispatcher.dispatchCommand('fail', ['foo', 'bar']), dispatcher.doFail) self.assertEqual( dispatcher.dispatchCommand('foobar', ['*', 'ls']), None) def testSubCommandDispatch(self): dispatcher = self.DispatchedClassSub() self.assertEqual( dispatcher.dispatchCommand('privmsg', ['foo']), dispatcher.doPrivmsg) self.assertEqual( dispatcher.dispatchCommand('cap', ['*', 'ls']), dispatcher.doCapLs) self.assertEqual( dispatcher.dispatchCommand('fail', ['foo', 'bar']), dispatcher.doFailFoo) self.assertEqual( dispatcher.dispatchCommand('foobar', ['*', 'ls']), None) def testCommandDispatchMissingArgs(self): dispatcher = self.DispatchedClass() self.assertEqual( dispatcher.dispatchCommand('privmsg', ['foo']), dispatcher.doPrivmsg) self.assertEqual( dispatcher.dispatchCommand('cap', ['*']), dispatcher.doCap) self.assertEqual( dispatcher.dispatchCommand('fail', []), dispatcher.doFail) self.assertEqual( dispatcher.dispatchCommand('foobar', ['*']), None) def testCommandDispatchLegacy(self): """Tests the legacy parameters of dispatchCommand, without the "args" argument.""" dispatcher = self.DispatchedClass() with self.assertWarnsRegex(DeprecationWarning, "'args'"): self.assertEqual( dispatcher.dispatchCommand('privmsg'), dispatcher.doPrivmsg) with self.assertWarnsRegex(DeprecationWarning, "'args'"): self.assertEqual( dispatcher.dispatchCommand('cap'), dispatcher.doCap) with self.assertWarnsRegex(DeprecationWarning, "'args'"): self.assertEqual( dispatcher.dispatchCommand('fail'), dispatcher.doFail) with self.assertWarnsRegex(DeprecationWarning, "'args'"): self.assertEqual( dispatcher.dispatchCommand('foobar'), None) class IrcMsgQueueTestCase(SupyTestCase): mode = ircmsgs.op('#foo', 'jemfinch') msg = ircmsgs.privmsg('#foo', 'hey, you') msgs = [ircmsgs.privmsg('#foo', str(i)) for i in range(10)] kick = ircmsgs.kick('#foo', 'PeterB') pong = ircmsgs.pong('123') ping = ircmsgs.ping('123') topic = ircmsgs.topic('#foo') notice = ircmsgs.notice('jemfinch', 'supybot here') join = ircmsgs.join('#foo') who = ircmsgs.who('#foo') def testInit(self): q = irclib.IrcMsgQueue([self.msg, self.topic, self.ping]) self.assertEqual(len(q), 3) def testLen(self): q = irclib.IrcMsgQueue() q.enqueue(self.msg) self.assertEqual(len(q), 1) q.enqueue(self.mode) self.assertEqual(len(q), 2) q.enqueue(self.kick) self.assertEqual(len(q), 3) q.enqueue(self.topic) self.assertEqual(len(q), 4) q.dequeue() self.assertEqual(len(q), 3) q.dequeue() self.assertEqual(len(q), 2) q.dequeue() self.assertEqual(len(q), 1) q.dequeue() self.assertEqual(len(q), 0) def testContains(self): q = irclib.IrcMsgQueue() q.enqueue(self.msg) q.enqueue(self.msg) q.enqueue(self.msg) self.assertTrue(self.msg in q) q.dequeue() self.assertTrue(self.msg in q) q.dequeue() self.assertTrue(self.msg in q) q.dequeue() self.assertFalse(self.msg in q) def testRepr(self): q = irclib.IrcMsgQueue() self.assertEqual(repr(q), 'IrcMsgQueue([])') q.enqueue(self.msg) try: repr(q) except Exception as e: self.fail('repr(q) raised an exception: %s' % utils.exnToString(e)) def testEmpty(self): q = irclib.IrcMsgQueue() self.assertFalse(q) def testEnqueueDequeue(self): q = irclib.IrcMsgQueue() q.enqueue(self.msg) self.assertTrue(q) self.assertEqual(self.msg, q.dequeue()) self.assertFalse(q) q.enqueue(self.msg) q.enqueue(self.notice) self.assertEqual(self.msg, q.dequeue()) self.assertEqual(self.notice, q.dequeue()) for msg in self.msgs: q.enqueue(msg) for msg in self.msgs: self.assertEqual(msg, q.dequeue()) def testPrioritizing(self): q = irclib.IrcMsgQueue() q.enqueue(self.msg) q.enqueue(self.mode) self.assertEqual(self.mode, q.dequeue()) self.assertEqual(self.msg, q.dequeue()) q.enqueue(self.msg) q.enqueue(self.kick) self.assertEqual(self.kick, q.dequeue()) self.assertEqual(self.msg, q.dequeue()) q.enqueue(self.ping) q.enqueue(self.msgs[0]) q.enqueue(self.kick) q.enqueue(self.msgs[1]) q.enqueue(self.mode) self.assertEqual(self.kick, q.dequeue()) self.assertEqual(self.mode, q.dequeue()) self.assertEqual(self.ping, q.dequeue()) self.assertEqual(self.msgs[0], q.dequeue()) self.assertEqual(self.msgs[1], q.dequeue()) def testNoIdenticals(self): configVar = conf.supybot.protocols.irc.queuing.duplicates original = configVar() try: configVar.setValue(True) q = irclib.IrcMsgQueue() q.enqueue(self.msg) q.enqueue(self.msg) self.assertEqual(self.msg, q.dequeue()) self.assertFalse(q) finally: configVar.setValue(original) def testJoinBeforeWho(self): q = irclib.IrcMsgQueue() q.enqueue(self.join) q.enqueue(self.who) self.assertEqual(self.join, q.dequeue()) self.assertEqual(self.who, q.dequeue()) ## q.enqueue(self.who) ## q.enqueue(self.join) ## self.assertEqual(self.join, q.dequeue()) ## self.assertEqual(self.who, q.dequeue()) def testTopicBeforePrivmsg(self): q = irclib.IrcMsgQueue() q.enqueue(self.msg) q.enqueue(self.topic) self.assertEqual(self.topic, q.dequeue()) self.assertEqual(self.msg, q.dequeue()) def testModeBeforePrivmsg(self): q = irclib.IrcMsgQueue() q.enqueue(self.msg) q.enqueue(self.mode) self.assertEqual(self.mode, q.dequeue()) self.assertEqual(self.msg, q.dequeue()) q.enqueue(self.mode) q.enqueue(self.msg) self.assertEqual(self.mode, q.dequeue()) self.assertEqual(self.msg, q.dequeue()) class ChannelStateTestCase(SupyTestCase): def testPickleCopy(self): c = irclib.ChannelState() self.assertEqual(pickle.loads(pickle.dumps(c)), c) c.addUser('jemfinch') c1 = pickle.loads(pickle.dumps(c)) self.assertEqual(c, c1) c.removeUser('jemfinch') self.assertFalse('jemfinch' in c.users) self.assertTrue('jemfinch' in c1.users) def testCopy(self): c = irclib.ChannelState() c.addUser('jemfinch') c1 = copy.deepcopy(c) c.removeUser('jemfinch') self.assertFalse('jemfinch' in c.users) self.assertTrue('jemfinch' in c1.users) def testAddUser(self): c = irclib.ChannelState() c.addUser('foo') self.assertTrue('foo' in c.users) self.assertFalse('foo' in c.ops) self.assertFalse('foo' in c.voices) self.assertFalse('foo' in c.halfops) c.addUser('+bar') self.assertTrue('bar' in c.users) self.assertTrue('bar' in c.voices) self.assertFalse('bar' in c.ops) self.assertFalse('bar' in c.halfops) c.addUser('%baz') self.assertTrue('baz' in c.users) self.assertTrue('baz' in c.halfops) self.assertFalse('baz' in c.voices) self.assertFalse('baz' in c.ops) c.addUser('@quuz') self.assertTrue('quuz' in c.users) self.assertTrue('quuz' in c.ops) self.assertFalse('quuz' in c.halfops) self.assertFalse('quuz' in c.voices) class IrcStateTestCase(SupyTestCase): class FakeIrc: nick = 'nick' prefix = 'nick!user@host' def isChannel(self, s): return ircutils.isChannel(s) irc = FakeIrc() def testKickRemovesChannel(self): st = irclib.IrcState() st.channels['#foo'] = irclib.ChannelState() m = ircmsgs.kick('#foo', self.irc.nick, prefix=self.irc.prefix) st.addMsg(self.irc, m) self.assertFalse('#foo' in st.channels) def testAddMsgRemovesOpsProperly(self): st = irclib.IrcState() st.channels['#foo'] = irclib.ChannelState() st.channels['#foo'].ops.add('bar') m = ircmsgs.mode('#foo', ('-o', 'bar')) st.addMsg(self.irc, m) self.assertFalse('bar' in st.channels['#foo'].ops) def testNickChangesChangeChannelUsers(self): st = irclib.IrcState() st.channels['#foo'] = irclib.ChannelState() st.channels['#foo'].addUser('@bar') self.assertTrue('bar' in st.channels['#foo'].users) self.assertTrue(st.channels['#foo'].isOp('bar')) st.addMsg(self.irc, ircmsgs.IrcMsg(':bar!asfd@asdf.com NICK baz')) self.assertFalse('bar' in st.channels['#foo'].users) self.assertFalse(st.channels['#foo'].isOp('bar')) self.assertTrue('baz' in st.channels['#foo'].users) self.assertTrue(st.channels['#foo'].isOp('baz')) def testHistory(self): if len(msgs) < 10: return maxHistoryLength = conf.supybot.protocols.irc.maxHistoryLength with maxHistoryLength.context(10): state = irclib.IrcState() for msg in msgs: try: state.addMsg(self.irc, msg) except Exception: pass self.assertFalse(len(state.history) > maxHistoryLength()) self.assertEqual(len(state.history), maxHistoryLength()) self.assertEqual(list(state.history), msgs[len(msgs) - maxHistoryLength():]) def testWasteland005(self): state = irclib.IrcState() # Here we're testing if PREFIX works without the (ov) there. state.addMsg(self.irc, ircmsgs.IrcMsg(':desolate.wasteland.org 005 jemfinch NOQUIT WATCH=128 SAFELIST MODES=6 MAXCHANNELS=10 MAXBANS=100 NICKLEN=30 TOPICLEN=307 KICKLEN=307 CHANTYPES=&# PREFIX=@+ NETWORK=DALnet SILENCE=10 :are available on this server')) self.assertEqual(state.supported['prefix']['o'], '@') self.assertEqual(state.supported['prefix']['v'], '+') def testIRCNet005(self): state = irclib.IrcState() # Testing IRCNet's misuse of MAXBANS state.addMsg(self.irc, ircmsgs.IrcMsg(':irc.inet.tele.dk 005 adkwbot WALLCHOPS KNOCK EXCEPTS INVEX MODES=4 MAXCHANNELS=20 MAXBANS=beI:100 MAXTARGETS=4 NICKLEN=9 TOPICLEN=120 KICKLEN=90 :are supported by this server')) self.assertEqual(state.supported['maxbans'], 100) def testSupportedUmodes(self): state = irclib.IrcState() state.addMsg(self.irc, ircmsgs.IrcMsg(':coulomb.oftc.net 004 testnick coulomb.oftc.net hybrid-7.2.2+oftc1.6.8 CDGPRSabcdfgiklnorsuwxyz biklmnopstveI bkloveI')) self.assertEqual(state.supported['umodes'], frozenset('CDGPRSabcdfgiklnorsuwxyz')) self.assertEqual(state.supported['chanmodes'], frozenset('biklmnopstveI')) def testEmptyTopic(self): state = irclib.IrcState() state.addMsg(self.irc, ircmsgs.topic('#foo')) def testPickleCopy(self): state = irclib.IrcState() self.assertEqual(state, pickle.loads(pickle.dumps(state))) for msg in msgs: try: state.addMsg(self.irc, msg) except Exception: pass self.assertEqual(state, pickle.loads(pickle.dumps(state))) def testCopy(self): state = irclib.IrcState() self.assertEqual(state, state.copy()) for msg in msgs: try: state.addMsg(self.irc, msg) except Exception: pass self.assertEqual(state, state.copy()) def testCopyCopiesChannels(self): state = irclib.IrcState() stateCopy = state.copy() state.channels['#foo'] = None self.assertFalse('#foo' in stateCopy.channels) def testJoin(self): st = irclib.IrcState() st.addMsg(self.irc, ircmsgs.join('#foo', prefix=self.irc.prefix)) self.assertTrue('#foo' in st.channels) self.assertTrue(self.irc.nick in st.channels['#foo'].users) st.addMsg(self.irc, ircmsgs.join('#foo', prefix='foo!bar@baz')) self.assertTrue('foo' in st.channels['#foo'].users) st2 = st.copy() st.addMsg(self.irc, ircmsgs.quit(prefix='foo!bar@baz')) self.assertFalse('foo' in st.channels['#foo'].users) self.assertTrue('foo' in st2.channels['#foo'].users) def testEq(self): state1 = irclib.IrcState() state2 = irclib.IrcState() self.assertEqual(state1, state2) for msg in msgs: try: state1.addMsg(self.irc, msg) state2.addMsg(self.irc, msg) self.assertEqual(state1, state2) except Exception: pass def testHandlesModes(self): st = irclib.IrcState() st.addMsg(self.irc, ircmsgs.join('#foo', prefix=self.irc.prefix)) self.assertFalse('bar' in st.channels['#foo'].ops) st.addMsg(self.irc, ircmsgs.op('#foo', 'bar')) self.assertTrue('bar' in st.channels['#foo'].ops) st.addMsg(self.irc, ircmsgs.deop('#foo', 'bar')) self.assertFalse('bar' in st.channels['#foo'].ops) self.assertFalse('bar' in st.channels['#foo'].voices) st.addMsg(self.irc, ircmsgs.voice('#foo', 'bar')) self.assertTrue('bar' in st.channels['#foo'].voices) st.addMsg(self.irc, ircmsgs.devoice('#foo', 'bar')) self.assertFalse('bar' in st.channels['#foo'].voices) self.assertFalse('bar' in st.channels['#foo'].halfops) st.addMsg(self.irc, ircmsgs.halfop('#foo', 'bar')) self.assertTrue('bar' in st.channels['#foo'].halfops) st.addMsg(self.irc, ircmsgs.dehalfop('#foo', 'bar')) self.assertFalse('bar' in st.channels['#foo'].halfops) def testDoModeOnlyChannels(self): st = irclib.IrcState() self.assert_(st.addMsg(self.irc, ircmsgs.IrcMsg('MODE foo +i')) or 1) class IrcCapsTestCase(SupyTestCase): def testReqLineLength(self): self.irc = irclib.Irc('test') m = self.irc.takeMsg() self.assertTrue(m.command == 'CAP', 'Expected CAP, got %r.' % m) self.assertTrue(m.args == ('LS', '302'), 'Expected CAP LS 302, got %r.' % m) m = self.irc.takeMsg() self.assertTrue(m.command == 'NICK', 'Expected NICK, got %r.' % m) m = self.irc.takeMsg() self.assertTrue(m.command == 'USER', 'Expected USER, got %r.' % m) self.irc.REQUEST_CAPABILITIES = set(['a'*400, 'b'*400]) caps = ' '.join(self.irc.REQUEST_CAPABILITIES) self.irc.feedMsg(ircmsgs.IrcMsg(command='CAP', args=('*', 'LS', '*', 'a'*400))) self.irc.feedMsg(ircmsgs.IrcMsg(command='CAP', args=('*', 'LS', 'b'*400))) m = self.irc.takeMsg() self.assertTrue(m.command == 'CAP', 'Expected CAP, got %r.' % m) self.assertEqual(m.args[0], 'REQ', m) self.assertEqual(m.args[1], 'a'*400) m = self.irc.takeMsg() self.assertTrue(m.command == 'CAP', 'Expected CAP, got %r.' % m) self.assertEqual(m.args[0], 'REQ', m) self.assertEqual(m.args[1], 'b'*400) class IrcTestCase(SupyTestCase): def setUp(self): self.irc = irclib.Irc('test') #m = self.irc.takeMsg() #self.assertTrue(m.command == 'PASS', 'Expected PASS, got %r.' % m) m = self.irc.takeMsg() self.assertTrue(m.command == 'CAP', 'Expected CAP, got %r.' % m) self.assertTrue(m.args == ('LS', '302'), 'Expected CAP LS 302, got %r.' % m) m = self.irc.takeMsg() self.assertTrue(m.command == 'NICK', 'Expected NICK, got %r.' % m) m = self.irc.takeMsg() self.assertTrue(m.command == 'USER', 'Expected USER, got %r.' % m) # TODO self.irc.feedMsg(ircmsgs.IrcMsg(command='CAP', args=('*', 'LS', '*', 'account-tag multi-prefix'))) self.irc.feedMsg(ircmsgs.IrcMsg(command='CAP', args=('*', 'LS', 'extended-join'))) m = self.irc.takeMsg() self.assertTrue(m.command == 'CAP', 'Expected CAP, got %r.' % m) self.assertEqual(m.args[0], 'REQ', m) # NOTE: Capabilities are requested in alphabetic order, because # sets are unordered, and their "order" is nondeterministic. self.assertEqual(m.args[1], 'account-tag extended-join multi-prefix') self.irc.feedMsg(ircmsgs.IrcMsg(command='CAP', args=('*', 'ACK', 'account-tag multi-prefix extended-join'))) m = self.irc.takeMsg() self.assertTrue(m.command == 'CAP', 'Expected CAP, got %r.' % m) self.assertEqual(m.args, ('END',), m) m = self.irc.takeMsg() self.assertTrue(m is None, m) def testPingResponse(self): self.irc.feedMsg(ircmsgs.ping('123')) self.assertEqual(ircmsgs.pong('123'), self.irc.takeMsg()) def test433Response(self): # This is necessary; it won't change nick if irc.originalName==irc.nick self.irc.nick = 'somethingElse' self.irc.feedMsg(ircmsgs.IrcMsg('433 * %s :Nickname already in use.' %\ self.irc.nick)) msg = self.irc.takeMsg() self.assertTrue(msg.command == 'NICK' and msg.args[0] != self.irc.nick) self.irc.feedMsg(ircmsgs.IrcMsg('433 * %s :Nickname already in use.' %\ self.irc.nick)) msg = self.irc.takeMsg() self.assertTrue(msg.command == 'NICK' and msg.args[0] != self.irc.nick) def testSendBeforeQueue(self): while self.irc.takeMsg() is not None: self.irc.takeMsg() self.irc.queueMsg(ircmsgs.IrcMsg('NOTICE #foo bar')) self.irc.sendMsg(ircmsgs.IrcMsg('PRIVMSG #foo yeah!')) msg = self.irc.takeMsg() self.assertTrue(msg.command == 'PRIVMSG') msg = self.irc.takeMsg() self.assertTrue(msg.command == 'NOTICE') def testNoMsgLongerThan512(self): self.irc.queueMsg(ircmsgs.privmsg('whocares', 'x'*1000)) msg = self.irc.takeMsg() self.assertTrue(len(msg) <= 512, 'len(msg) was %s' % len(msg)) def testReset(self): for msg in msgs: try: self.irc.feedMsg(msg) except: pass self.irc.reset() self.assertFalse(self.irc.state.history) self.assertFalse(self.irc.state.channels) self.assertFalse(self.irc.outstandingPing) def testHistory(self): self.irc.reset() msg1 = ircmsgs.IrcMsg('PRIVMSG #linux :foo bar baz!') self.irc.feedMsg(msg1) self.assertEqual(self.irc.state.history[0], msg1) msg2 = ircmsgs.IrcMsg('JOIN #sourcereview') self.irc.feedMsg(msg2) self.assertEqual(list(self.irc.state.history), [msg1, msg2]) def testMsgChannel(self): self.irc.reset() self.irc.state.supported['statusmsg'] = '@' self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG #linux :foo bar baz!')) self.assertEqual(self.irc.state.history[-1].channel, '#linux') self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG @#linux2 :foo bar baz!')) self.assertEqual(self.irc.state.history[-1].channel, '#linux2') self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG +#linux3 :foo bar baz!')) self.assertEqual(self.irc.state.history[-1].channel, None) self.irc.state.supported['statusmsg'] = '+@' self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG #linux :foo bar baz!')) self.assertEqual(self.irc.state.history[-1].channel, '#linux') self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG @#linux2 :foo bar baz!')) self.assertEqual(self.irc.state.history[-1].channel, '#linux2') self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG +#linux3 :foo bar baz!')) self.assertEqual(self.irc.state.history[-1].channel, '#linux3') del self.irc.state.supported['statusmsg'] self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG #linux :foo bar baz!')) self.assertEqual(self.irc.state.history[-1].channel, '#linux') self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG @#linux2 :foo bar baz!')) self.assertEqual(self.irc.state.history[-1].channel, None) self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG +#linux3 :foo bar baz!')) self.assertEqual(self.irc.state.history[-1].channel, None) # Test msg.channel is set only for PRIVMSG and NOTICE self.irc.state.supported['statusmsg'] = '+@' self.irc.feedMsg(ircmsgs.IrcMsg('NOTICE @#linux :foo bar baz!')) self.assertEqual(self.irc.state.history[-1].channel, '#linux') self.irc.feedMsg(ircmsgs.IrcMsg('NOTICE @#linux2 :foo bar baz!')) self.assertEqual(self.irc.state.history[-1].channel, '#linux2') self.irc.feedMsg(ircmsgs.IrcMsg('MODE @#linux3 +v foo')) self.assertEqual(self.irc.state.history[-1].channel, None) def testQuit(self): self.irc.reset() self.irc.feedMsg(ircmsgs.IrcMsg(':someuser JOIN #foo')) self.irc.feedMsg(ircmsgs.IrcMsg(':someuser JOIN #bar')) self.irc.feedMsg(ircmsgs.IrcMsg(':someuser2 JOIN #bar2')) class Callback(irclib.IrcCallback): channels_set = None def name(self): return 'testcallback' def doQuit(self2, irc, msg): self2.channels_set = msg.tagged('channels') c = Callback() self.irc.addCallback(c) try: self.irc.feedMsg(ircmsgs.IrcMsg(':someuser QUIT')) finally: self.irc.removeCallback(c.name()) self.assertEqual(c.channels_set, ircutils.IrcSet(['#foo', '#bar'])) def testNick(self): self.irc.reset() self.irc.feedMsg(ircmsgs.IrcMsg(':someuser JOIN #foo')) self.irc.feedMsg(ircmsgs.IrcMsg(':someuser JOIN #bar')) self.irc.feedMsg(ircmsgs.IrcMsg(':someuser2 JOIN #bar2')) class Callback(irclib.IrcCallback): channels_set = None def name(self): return 'testcallback' def doNick(self2, irc, msg): self2.channels_set = msg.tagged('channels') c = Callback() self.irc.addCallback(c) try: self.irc.feedMsg(ircmsgs.IrcMsg(':someuser NICK newuser')) finally: self.irc.removeCallback(c.name()) self.assertEqual(c.channels_set, ircutils.IrcSet(['#foo', '#bar'])) def testBatch(self): self.irc.reset() self.irc.feedMsg(ircmsgs.IrcMsg(':someuser1 JOIN #foo')) self.irc.feedMsg(ircmsgs.IrcMsg(':host BATCH +name netjoin')) m1 = ircmsgs.IrcMsg('@batch=name :someuser2 JOIN #foo') self.irc.feedMsg(m1) self.irc.feedMsg(ircmsgs.IrcMsg(':someuser3 JOIN #foo')) m2 = ircmsgs.IrcMsg('@batch=name :someuser4 JOIN #foo') self.irc.feedMsg(m2) class Callback(irclib.IrcCallback): batch = None def name(self): return 'testcallback' def doBatch(self2, irc, msg): self2.batch = msg.tagged('batch') c = Callback() self.irc.addCallback(c) try: self.irc.feedMsg(ircmsgs.IrcMsg(':host BATCH -name')) finally: self.irc.removeCallback(c.name()) self.assertEqual(c.batch, irclib.Batch('netjoin', (), [m1, m2])) class SaslTestCase(SupyTestCase): def setUp(self): pass def startCapNegociation(self, caps='sasl'): m = self.irc.takeMsg() self.assertTrue(m.command == 'CAP', 'Expected CAP, got %r.' % m) self.assertTrue(m.args == ('LS', '302'), 'Expected CAP LS 302, got %r.' % m) m = self.irc.takeMsg() self.assertTrue(m.command == 'NICK', 'Expected NICK, got %r.' % m) m = self.irc.takeMsg() self.assertTrue(m.command == 'USER', 'Expected USER, got %r.' % m) self.irc.feedMsg(ircmsgs.IrcMsg(command='CAP', args=('*', 'LS', caps))) if caps: m = self.irc.takeMsg() self.assertTrue(m.command == 'CAP', 'Expected CAP, got %r.' % m) self.assertEqual(m.args[0], 'REQ', m) self.assertEqual(m.args[1], 'sasl') self.irc.feedMsg(ircmsgs.IrcMsg(command='CAP', args=('*', 'ACK', 'sasl'))) def endCapNegociation(self): m = self.irc.takeMsg() self.assertTrue(m.command == 'CAP', 'Expected CAP, got %r.' % m) self.assertEqual(m.args, ('END',), m) def testPlain(self): try: conf.supybot.networks.test.sasl.username.setValue('jilles') conf.supybot.networks.test.sasl.password.setValue('sesame') self.irc = irclib.Irc('test') finally: conf.supybot.networks.test.sasl.username.setValue('') conf.supybot.networks.test.sasl.password.setValue('') self.assertEqual(self.irc.sasl_current_mechanism, None) self.assertEqual(self.irc.sasl_next_mechanisms, ['plain']) self.startCapNegociation() m = self.irc.takeMsg() self.assertEqual(m, ircmsgs.IrcMsg(command='AUTHENTICATE', args=('PLAIN',))) self.irc.feedMsg(ircmsgs.IrcMsg(command='AUTHENTICATE', args=('+',))) m = self.irc.takeMsg() self.assertEqual(m, ircmsgs.IrcMsg(command='AUTHENTICATE', args=('amlsbGVzAGppbGxlcwBzZXNhbWU=',))) self.irc.feedMsg(ircmsgs.IrcMsg(command='900', args=('jilles',))) self.irc.feedMsg(ircmsgs.IrcMsg(command='903', args=('jilles',))) self.endCapNegociation() def testExternalFallbackToPlain(self): try: conf.supybot.networks.test.sasl.username.setValue('jilles') conf.supybot.networks.test.sasl.password.setValue('sesame') conf.supybot.networks.test.certfile.setValue('foo') self.irc = irclib.Irc('test') finally: conf.supybot.networks.test.sasl.username.setValue('') conf.supybot.networks.test.sasl.password.setValue('') conf.supybot.networks.test.certfile.setValue('') self.assertEqual(self.irc.sasl_current_mechanism, None) self.assertEqual(self.irc.sasl_next_mechanisms, ['external', 'plain']) self.startCapNegociation() m = self.irc.takeMsg() self.assertEqual(m, ircmsgs.IrcMsg(command='AUTHENTICATE', args=('EXTERNAL',))) self.irc.feedMsg(ircmsgs.IrcMsg(command='904', args=('mechanism not available',))) m = self.irc.takeMsg() self.assertEqual(m, ircmsgs.IrcMsg(command='AUTHENTICATE', args=('PLAIN',))) self.irc.feedMsg(ircmsgs.IrcMsg(command='AUTHENTICATE', args=('+',))) m = self.irc.takeMsg() self.assertEqual(m, ircmsgs.IrcMsg(command='AUTHENTICATE', args=('amlsbGVzAGppbGxlcwBzZXNhbWU=',))) self.irc.feedMsg(ircmsgs.IrcMsg(command='900', args=('jilles',))) self.irc.feedMsg(ircmsgs.IrcMsg(command='903', args=('jilles',))) self.endCapNegociation() def testFilter(self): try: conf.supybot.networks.test.sasl.username.setValue('jilles') conf.supybot.networks.test.sasl.password.setValue('sesame') conf.supybot.networks.test.certfile.setValue('foo') self.irc = irclib.Irc('test') finally: conf.supybot.networks.test.sasl.username.setValue('') conf.supybot.networks.test.sasl.password.setValue('') conf.supybot.networks.test.certfile.setValue('') self.assertEqual(self.irc.sasl_current_mechanism, None) self.assertEqual(self.irc.sasl_next_mechanisms, ['external', 'plain']) self.startCapNegociation(caps='sasl=foo,plain,bar') m = self.irc.takeMsg() self.assertEqual(m, ircmsgs.IrcMsg(command='AUTHENTICATE', args=('PLAIN',))) self.irc.feedMsg(ircmsgs.IrcMsg(command='AUTHENTICATE', args=('+',))) m = self.irc.takeMsg() self.assertEqual(m, ircmsgs.IrcMsg(command='AUTHENTICATE', args=('amlsbGVzAGppbGxlcwBzZXNhbWU=',))) self.irc.feedMsg(ircmsgs.IrcMsg(command='900', args=('jilles',))) self.irc.feedMsg(ircmsgs.IrcMsg(command='903', args=('jilles',))) self.endCapNegociation() def testReauthenticate(self): try: conf.supybot.networks.test.sasl.username.setValue('jilles') conf.supybot.networks.test.sasl.password.setValue('sesame') self.irc = irclib.Irc('test') finally: conf.supybot.networks.test.sasl.username.setValue('') conf.supybot.networks.test.sasl.password.setValue('') self.assertEqual(self.irc.sasl_current_mechanism, None) self.assertEqual(self.irc.sasl_next_mechanisms, ['plain']) self.startCapNegociation(caps='') self.endCapNegociation() while self.irc.takeMsg(): pass self.irc.feedMsg(ircmsgs.IrcMsg(command='CAP', args=('*', 'NEW', 'sasl=EXTERNAL'))) self.irc.takeMsg() # None. But even if it was CAP REQ sasl, it would be ok self.assertEqual(self.irc.takeMsg(), None) try: conf.supybot.networks.test.sasl.username.setValue('jilles') conf.supybot.networks.test.sasl.password.setValue('sesame') self.irc.feedMsg(ircmsgs.IrcMsg(command='CAP', args=('*', 'DEL', 'sasl'))) self.irc.feedMsg(ircmsgs.IrcMsg(command='CAP', args=('*', 'NEW', 'sasl=PLAIN'))) finally: conf.supybot.networks.test.sasl.username.setValue('') conf.supybot.networks.test.sasl.password.setValue('') m = self.irc.takeMsg() self.assertTrue(m.command == 'CAP', 'Expected CAP, got %r.' % m) self.assertEqual(m.args[0], 'REQ', m) self.assertEqual(m.args[1], 'sasl') self.irc.feedMsg(ircmsgs.IrcMsg(command='CAP', args=('*', 'ACK', 'sasl'))) m = self.irc.takeMsg() self.assertEqual(m, ircmsgs.IrcMsg(command='AUTHENTICATE', args=('PLAIN',))) self.irc.feedMsg(ircmsgs.IrcMsg(command='AUTHENTICATE', args=('+',))) m = self.irc.takeMsg() self.assertEqual(m, ircmsgs.IrcMsg(command='AUTHENTICATE', args=('amlsbGVzAGppbGxlcwBzZXNhbWU=',))) self.irc.feedMsg(ircmsgs.IrcMsg(command='900', args=('jilles',))) self.irc.feedMsg(ircmsgs.IrcMsg(command='903', args=('jilles',))) class IrcCallbackTestCase(SupyTestCase): class FakeIrc: pass irc = FakeIrc() def testName(self): class UnnamedIrcCallback(irclib.IrcCallback): pass unnamed = UnnamedIrcCallback() class NamedIrcCallback(irclib.IrcCallback): myName = 'foobar' def name(self): return self.myName named = NamedIrcCallback() self.assertEqual(unnamed.name(), unnamed.__class__.__name__) self.assertEqual(named.name(), named.myName) def testDoCommand(self): def makeCommand(msg): return 'do' + msg.command.capitalize() class DoCommandCatcher(irclib.IrcCallback): def __init__(self): self.L = [] def __getattr__(self, attr): self.L.append(attr) return lambda *args: None doCommandCatcher = DoCommandCatcher() for msg in msgs: doCommandCatcher(self.irc, msg) commands = list(map(makeCommand, msgs)) self.assertEqual(doCommandCatcher.L, commands) def testFirstCommands(self): try: originalNick = conf.supybot.nick() originalUser = conf.supybot.user() originalPassword = conf.supybot.networks.test.password() nick = 'nick' conf.supybot.nick.setValue(nick) user = 'user any user' conf.supybot.user.setValue(user) expected = [ ircmsgs.IrcMsg(command='CAP', args=('LS', '302')), ircmsgs.nick(nick), ircmsgs.user('limnoria', user), ] irc = irclib.Irc('test') msgs = [irc.takeMsg()] while msgs[-1] is not None: msgs.append(irc.takeMsg()) msgs.pop() self.assertEqual(msgs, expected) password = 'password' conf.supybot.networks.test.password.setValue(password) irc = irclib.Irc('test') msgs = [irc.takeMsg()] while msgs[-1] is not None: msgs.append(irc.takeMsg()) msgs.pop() expected.insert(1, ircmsgs.password(password)) self.assertEqual(msgs, expected) finally: conf.supybot.nick.setValue(originalNick) conf.supybot.user.setValue(originalUser) conf.supybot.networks.test.password.setValue(originalPassword) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/test/test_ircmsgs.py0000644000175000017500000003050113634634533017356 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * import time import copy import pickle import supybot.conf as conf import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils # The test framework used to provide these, but not it doesn't. We'll add # messages to as we find bugs (if indeed we find bugs). msgs = [] rawmsgs = [] class IrcMsgTestCase(SupyTestCase): def testLen(self): for msg in msgs: if msg.prefix: strmsg = str(msg) self.assertFalse(len(msg) != len(strmsg) and \ strmsg.replace(':', '') == strmsg) def testRepr(self): IrcMsg = ircmsgs.IrcMsg for msg in msgs: self.assertEqual(msg, eval(repr(msg))) def testStr(self): for (rawmsg, msg) in zip(rawmsgs, msgs): strmsg = str(msg).strip() self.assertFalse(rawmsg != strmsg and \ strmsg.replace(':', '') == strmsg) def testEq(self): for msg in msgs: self.assertEqual(msg, msg) self.assertFalse(msgs and msgs[0] == []) # Comparison to unhashable type. def testNe(self): for msg in msgs: self.assertFalse(msg != msg) ## def testImmutability(self): ## s = 'something else' ## t = ('foo', 'bar', 'baz') ## for msg in msgs: ## self.assertRaises(AttributeError, setattr, msg, 'prefix', s) ## self.assertRaises(AttributeError, setattr, msg, 'nick', s) ## self.assertRaises(AttributeError, setattr, msg, 'user', s) ## self.assertRaises(AttributeError, setattr, msg, 'host', s) ## self.assertRaises(AttributeError, setattr, msg, 'command', s) ## self.assertRaises(AttributeError, setattr, msg, 'args', t) ## if msg.args: ## def setArgs(msg): ## msg.args[0] = s ## self.assertRaises(TypeError, setArgs, msg) def testInit(self): for msg in msgs: self.assertEqual(msg, ircmsgs.IrcMsg(prefix=msg.prefix, command=msg.command, args=msg.args)) self.assertEqual(msg, ircmsgs.IrcMsg(msg=msg)) self.assertRaises(ValueError, ircmsgs.IrcMsg, args=('foo', 'bar'), prefix='foo!bar@baz') m = ircmsgs.IrcMsg(prefix='foo!bar@baz', args=('foo', 'bar'), command='CMD') self.assertIs(m.time, None) m.time = 24 self.assertEqual(ircmsgs.IrcMsg(msg=m).time, 24) def testPickleCopy(self): for msg in msgs: self.assertEqual(msg, pickle.loads(pickle.dumps(msg))) self.assertEqual(msg, copy.copy(msg)) def testHashNotZero(self): zeroes = 0 for msg in msgs: if hash(msg) == 0: zeroes += 1 self.assertFalse(zeroes > (len(msgs)/10), 'Too many zero hashes.') def testMsgKeywordHandledProperly(self): msg = ircmsgs.notice('foo', 'bar') msg2 = ircmsgs.IrcMsg(msg=msg, command='PRIVMSG') self.assertEqual(msg2.command, 'PRIVMSG') self.assertEqual(msg2.args, msg.args) def testMalformedIrcMsgRaised(self): self.assertRaises(ircmsgs.MalformedIrcMsg, ircmsgs.IrcMsg, ':foo') self.assertRaises(ircmsgs.MalformedIrcMsg, ircmsgs.IrcMsg, args=('biff',), prefix='foo!bar@baz') def testTags(self): m = ircmsgs.privmsg('foo', 'bar') self.assertFalse(m.repliedTo) m.tag('repliedTo') self.assertTrue(m.repliedTo) m.tag('repliedTo') self.assertTrue(m.repliedTo) m.tag('repliedTo', 12) self.assertEqual(m.repliedTo, 12) def testServerTags(self): s = '@aaa=b\\:bb;ccc;example.com/ddd=ee\\\\se ' \ ':nick!ident@host.com PRIVMSG me :Hello' m = ircmsgs.IrcMsg(s) self.assertEqual(m.server_tags, { 'aaa': 'b;bb', 'ccc': None, 'example.com/ddd': 'ee\\se'}) self.assertEqual(m.prefix, 'nick!ident@host.com') self.assertEqual(m.command, 'PRIVMSG') self.assertEqual(m.args, ('me', 'Hello')) self.assertEqual(str(m), s + '\n') s = '@foo=;bar=baz;qux= ' \ ':nick!ident@host.com PRIVMSG me :Hello' m = ircmsgs.IrcMsg(s) self.assertEqual(m.server_tags, { 'foo': None, 'bar': 'baz', 'qux': None}) def testTime(self): before = time.time() msg = ircmsgs.IrcMsg('PRIVMSG #foo :foo') after = time.time() self.assertTrue(before <= msg.time <= after) msg = ircmsgs.IrcMsg('@time=2011-10-19T16:40:51.620Z ' ':Angel!angel@example.org PRIVMSG Wiz :Hello') self.assertEqual(msg.time, 1319042451.62) class FunctionsTestCase(SupyTestCase): def testIsAction(self): L = [':jemfinch!~jfincher@ts26-2.homenet.ohio-state.edu PRIVMSG' ' #sourcereview :ACTION does something', ':supybot!~supybot@underthemain.net PRIVMSG #sourcereview ' ':ACTION beats angryman senseless with a Unix manual (#2)', ':supybot!~supybot@underthemain.net PRIVMSG #sourcereview ' ':ACTION beats ang senseless with a 50lb Unix manual (#2)', ':supybot!~supybot@underthemain.net PRIVMSG #sourcereview ' ':ACTION resizes angryman\'s terminal to 40x24 (#16)'] msgs = list(map(ircmsgs.IrcMsg, L)) for msg in msgs: self.assertTrue(ircmsgs.isAction(msg)) def testIsActionIsntStupid(self): m = ircmsgs.privmsg('#x', '\x01NOTANACTION foo\x01') self.assertFalse(ircmsgs.isAction(m)) m = ircmsgs.privmsg('#x', '\x01ACTION foo bar\x01') self.assertTrue(ircmsgs.isAction(m)) def testIsCtcp(self): self.assertTrue(ircmsgs.isCtcp(ircmsgs.privmsg('foo', '\x01VERSION\x01'))) self.assertFalse(ircmsgs.isCtcp(ircmsgs.privmsg('foo', '\x01'))) def testIsActionFalseWhenNoSpaces(self): msg = ircmsgs.IrcMsg('PRIVMSG #foo :\x01ACTIONfoobar\x01') self.assertFalse(ircmsgs.isAction(msg)) def testUnAction(self): s = 'foo bar baz' msg = ircmsgs.action('#foo', s) self.assertEqual(ircmsgs.unAction(msg), s) def testPrivmsg(self): self.assertEqual(str(ircmsgs.privmsg('foo', 'bar')), 'PRIVMSG foo :bar\r\n') self.assertEqual(str(ircmsgs.privmsg('foo,bar', 'baz')), 'PRIVMSG foo,bar :baz\r\n') def testWhois(self): with conf.supybot.protocols.irc.strictRfc.context(True): self.assertEqual(str(ircmsgs.whois('foo')), 'WHOIS :foo\r\n') self.assertEqual(str(ircmsgs.whois('foo,bar')), 'WHOIS :foo,bar\r\n') self.assertRaises(AssertionError, ircmsgs.whois, '#foo') self.assertRaises(AssertionError, ircmsgs.whois, 'foo,#foo') def testBan(self): channel = '#osu' ban = '*!*@*.edu' exception = '*!*@*ohio-state.edu' noException = ircmsgs.ban(channel, ban) self.assertEqual(ircutils.separateModes(noException.args[1:]), [('+b', ban)]) withException = ircmsgs.ban(channel, ban, exception) self.assertEqual(ircutils.separateModes(withException.args[1:]), [('+b', ban), ('+e', exception)]) def testBans(self): channel = '#osu' bans = ['*!*@*', 'jemfinch!*@*'] exceptions = ['*!*@*ohio-state.edu'] noException = ircmsgs.bans(channel, bans) self.assertEqual(ircutils.separateModes(noException.args[1:]), [('+b', bans[0]), ('+b', bans[1])]) withExceptions = ircmsgs.bans(channel, bans, exceptions) self.assertEqual(ircutils.separateModes(withExceptions.args[1:]), [('+b', bans[0]), ('+b', bans[1]), ('+e', exceptions[0])]) def testUnban(self): channel = '#supybot' ban = 'foo!bar@baz' self.assertEqual(str(ircmsgs.unban(channel, ban)), 'MODE %s -b :%s\r\n' % (channel, ban)) def testJoin(self): channel = '#osu' key = 'michiganSucks' self.assertEqual(ircmsgs.join(channel).args, ('#osu',)) self.assertEqual(ircmsgs.join(channel, key).args, ('#osu', 'michiganSucks')) def testJoins(self): channels = ['#osu', '#umich'] keys = ['michiganSucks', 'osuSucks'] self.assertEqual(ircmsgs.joins(channels).args, ('#osu,#umich',)) self.assertEqual(ircmsgs.joins(channels, keys).args, ('#osu,#umich', 'michiganSucks,osuSucks')) keys.pop() self.assertEqual(ircmsgs.joins(channels, keys).args, ('#osu,#umich', 'michiganSucks')) def testQuit(self): self.assertTrue(ircmsgs.quit(prefix='foo!bar@baz')) def testOps(self): m = ircmsgs.ops('#foo', ['foo', 'bar', 'baz']) self.assertEqual(str(m), 'MODE #foo +ooo foo bar :baz\r\n') def testDeops(self): m = ircmsgs.deops('#foo', ['foo', 'bar', 'baz']) self.assertEqual(str(m), 'MODE #foo -ooo foo bar :baz\r\n') def testVoices(self): m = ircmsgs.voices('#foo', ['foo', 'bar', 'baz']) self.assertEqual(str(m), 'MODE #foo +vvv foo bar :baz\r\n') def testDevoices(self): m = ircmsgs.devoices('#foo', ['foo', 'bar', 'baz']) self.assertEqual(str(m), 'MODE #foo -vvv foo bar :baz\r\n') def testHalfops(self): m = ircmsgs.halfops('#foo', ['foo', 'bar', 'baz']) self.assertEqual(str(m), 'MODE #foo +hhh foo bar :baz\r\n') def testDehalfops(self): m = ircmsgs.dehalfops('#foo', ['foo', 'bar', 'baz']) self.assertEqual(str(m), 'MODE #foo -hhh foo bar :baz\r\n') def testMode(self): m = ircmsgs.mode('#foo', ('-b', 'foo!bar@baz')) s = str(m) self.assertEqual(s, 'MODE #foo -b :foo!bar@baz\r\n') def testIsSplit(self): m = ircmsgs.IrcMsg(prefix="caker!~caker@ns.theshore.net", command="QUIT", args=('jupiter.oftc.net quasar.oftc.net',)) self.assertTrue(ircmsgs.isSplit(m)) m = ircmsgs.IrcMsg(prefix="bzbot!Brad2901@ACC87473.ipt.aol.com", command="QUIT", args=('Read error: 110 (Connection timed out)',)) self.assertFalse(ircmsgs.isSplit(m)) m = ircmsgs.IrcMsg(prefix="JibberJim!~none@8212cl.b0nwbeoe.co.uk", command="QUIT", args=('"Bye!"',)) self.assertFalse(ircmsgs.isSplit(m)) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/test/test_ircutils.py0000644000175000017500000004761513634634533017563 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * import copy import random import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils # The test framework used to provide these, but not it doesn't. We'll add # messages to as we find bugs (if indeed we find bugs). msgs = [] rawmsgs = [] class FunctionsTestCase(SupyTestCase): hostmask = 'foo!bar@baz' def testHostmaskPatternEqual(self): for msg in msgs: if msg.prefix and ircutils.isUserHostmask(msg.prefix): s = msg.prefix self.assertTrue(ircutils.hostmaskPatternEqual(s, s), '%r did not match itself.' % s) banmask = ircutils.banmask(s) self.assertTrue(ircutils.hostmaskPatternEqual(banmask, s), '%r did not match %r' % (s, banmask)) s = 'supybot!~supybot@dhcp065-024-075-056.columbus.rr.com' self.assertTrue(ircutils.hostmaskPatternEqual(s, s)) s = 'jamessan|work!~jamessan@209-6-166-196.c3-0.' \ 'abr-ubr1.sbo-abr.ma.cable.rcn.com' self.assertTrue(ircutils.hostmaskPatternEqual(s, s)) def testIsUserHostmask(self): self.assertTrue(ircutils.isUserHostmask(self.hostmask)) self.assertTrue(ircutils.isUserHostmask('a!b@c')) self.assertFalse(ircutils.isUserHostmask('!bar@baz')) self.assertFalse(ircutils.isUserHostmask('!@baz')) self.assertFalse(ircutils.isUserHostmask('!bar@')) self.assertFalse(ircutils.isUserHostmask('!@')) self.assertFalse(ircutils.isUserHostmask('foo!@baz')) self.assertFalse(ircutils.isUserHostmask('foo!bar@')) self.assertFalse(ircutils.isUserHostmask('')) self.assertFalse(ircutils.isUserHostmask('!')) self.assertFalse(ircutils.isUserHostmask('@')) self.assertFalse(ircutils.isUserHostmask('!bar@baz')) def testIsChannel(self): self.assertTrue(ircutils.isChannel('#')) self.assertTrue(ircutils.isChannel('&')) self.assertFalse(ircutils.isChannel('+')) self.assertTrue(ircutils.isChannel('+', chantypes='#&+!')) self.assertTrue(ircutils.isChannel('!')) self.assertTrue(ircutils.isChannel('#foo')) self.assertTrue(ircutils.isChannel('&foo')) self.assertFalse(ircutils.isChannel('+foo')) self.assertTrue(ircutils.isChannel('+foo', chantypes='#&+!')) self.assertTrue(ircutils.isChannel('!foo')) self.assertFalse(ircutils.isChannel('#foo bar')) self.assertFalse(ircutils.isChannel('#foo,bar')) self.assertFalse(ircutils.isChannel('#foobar\x07')) self.assertFalse(ircutils.isChannel('foo')) self.assertFalse(ircutils.isChannel('')) def testBold(self): s = ircutils.bold('foo') self.assertEqual(s[0], '\x02') self.assertEqual(s[-1], '\x02') def testItalic(self): s = ircutils.italic('foo') self.assertEqual(s[0], '\x1d') self.assertEqual(s[-1], '\x1d') def testUnderline(self): s = ircutils.underline('foo') self.assertEqual(s[0], '\x1f') self.assertEqual(s[-1], '\x1f') def testReverse(self): s = ircutils.reverse('foo') self.assertEqual(s[0], '\x16') self.assertEqual(s[-1], '\x16') def testMircColor(self): # No colors provided should return the same string s = 'foo' self.assertEqual(s, ircutils.mircColor(s)) # Test positional args self.assertEqual('\x0300foo\x03', ircutils.mircColor(s, 'white')) self.assertEqual('\x031,02foo\x03',ircutils.mircColor(s,'black','blue')) self.assertEqual('\x0300,03foo\x03', ircutils.mircColor(s, None, 'green')) # Test keyword args self.assertEqual('\x0304foo\x03', ircutils.mircColor(s, fg='red')) self.assertEqual('\x0300,05foo\x03', ircutils.mircColor(s, bg='brown')) self.assertEqual('\x036,07foo\x03', ircutils.mircColor(s, bg='orange', fg='purple')) # Commented out because we don't map numbers to colors anymore. ## def testMircColors(self): ## # Make sure all (k, v) pairs are also (v, k) pairs. ## for (k, v) in ircutils.mircColors.items(): ## if k: ## self.assertEqual(ircutils.mircColors[v], k) def testStripBold(self): self.assertEqual(ircutils.stripBold(ircutils.bold('foo')), 'foo') def testStripItalic(self): self.assertEqual(ircutils.stripItalic(ircutils.italic('foo')), 'foo') def testStripColor(self): self.assertEqual(ircutils.stripColor('\x02bold\x0302,04foo\x03bar\x0f'), '\x02boldfoobar\x0f') self.assertEqual(ircutils.stripColor('\x03foo\x03'), 'foo') self.assertEqual(ircutils.stripColor('\x03foo\x0F'), 'foo\x0F') self.assertEqual(ircutils.stripColor('\x0312foo\x03'), 'foo') self.assertEqual(ircutils.stripColor('\x0312,14foo\x03'), 'foo') self.assertEqual(ircutils.stripColor('\x03,14foo\x03'), 'foo') self.assertEqual(ircutils.stripColor('\x03,foo\x03'), ',foo') self.assertEqual(ircutils.stripColor('\x0312foo\x0F'), 'foo\x0F') self.assertEqual(ircutils.stripColor('\x0312,14foo\x0F'), 'foo\x0F') self.assertEqual(ircutils.stripColor('\x03,14foo\x0F'), 'foo\x0F') self.assertEqual(ircutils.stripColor('\x03,foo\x0F'), ',foo\x0F') def testStripReverse(self): self.assertEqual(ircutils.stripReverse(ircutils.reverse('foo')), 'foo') def testStripUnderline(self): self.assertEqual(ircutils.stripUnderline(ircutils.underline('foo')), 'foo') def testStripFormatting(self): self.assertEqual(ircutils.stripFormatting(ircutils.bold('foo')), 'foo') self.assertEqual(ircutils.stripFormatting(ircutils.italic('foo')), 'foo') self.assertEqual(ircutils.stripFormatting(ircutils.reverse('foo')), 'foo') self.assertEqual(ircutils.stripFormatting(ircutils.underline('foo')), 'foo') self.assertEqual(ircutils.stripFormatting('\x02bold\x0302,04foo\x03' 'bar\x0f'), 'boldfoobar') s = ircutils.mircColor('[', 'blue') + ircutils.bold('09:21') self.assertEqual(ircutils.stripFormatting(s), '[09:21') def testWrap(self): if sys.version_info[0] < 3: pred = len else: pred = lambda s:len(s.encode()) s = ('foo bar baz qux ' * 100)[0:-1] r = ircutils.wrap(s, 10) self.assertTrue(max(map(pred, r)) <= 10) self.assertEqual(''.join(r), s) r = ircutils.wrap(s, 100) self.assertTrue(max(map(pred, r)) <= 100) self.assertEqual(''.join(r), s) if sys.version_info[0] < 3: uchr = unichr u = lambda s: s.decode('utf8') else: uchr = chr u = lambda x: x s = (u('').join([uchr(0x1f527), uchr(0x1f527), uchr(0x1f527), u(' ')]) * 100)\ [0:-1] r = ircutils.wrap(s, 20) self.assertTrue(max(map(pred, r)) <= 20, (max(map(pred, r)), repr(r))) self.assertEqual(''.join(r), s) r = ircutils.wrap(s, 100) self.assertTrue(max(map(pred, r)) <= 100) self.assertEqual(''.join(r), s) s = ('foobarbazqux ' * 100)[0:-1] r = ircutils.wrap(s, 10) self.assertTrue(max(map(pred, r)) <= 10) self.assertEqual(''.join(r), s) r = ircutils.wrap(s, 100) self.assertTrue(max(map(pred, r)) <= 100) self.assertEqual(''.join(r), s) s = ('foobarbazqux' * 100)[0:-1] r = ircutils.wrap(s, 10) self.assertTrue(max(map(pred, r)) <= 10) self.assertEqual(''.join(r), s) r = ircutils.wrap(s, 100) self.assertTrue(max(map(pred, r)) <= 100) self.assertEqual(''.join(r), s) s = uchr(233)*500 r = ircutils.wrap(s, 500) self.assertTrue(max(map(pred, r)) <= 500) r = ircutils.wrap(s, 139) self.assertTrue(max(map(pred, r)) <= 139) s = '\x02\x16 barbazqux' + ('foobarbazqux ' * 20)[0:-1] r = ircutils.wrap(s, 91) self.assertTrue(max(map(pred, r)) <= 91) def testSafeArgument(self): s = 'I have been running for 9 seconds' bolds = ircutils.bold(s) colors = ircutils.mircColor(s, 'pink', 'orange') self.assertEqual(s, ircutils.safeArgument(s)) self.assertEqual(bolds, ircutils.safeArgument(bolds)) self.assertEqual(colors, ircutils.safeArgument(colors)) def testSafeArgumentConvertsToString(self): self.assertEqual('1', ircutils.safeArgument(1)) self.assertEqual(str(None), ircutils.safeArgument(None)) def testIsNick(self): try: original = conf.supybot.protocols.irc.strictRfc() conf.supybot.protocols.irc.strictRfc.setValue(True) self.assertTrue(ircutils.isNick('jemfinch')) self.assertTrue(ircutils.isNick('jemfinch0')) self.assertTrue(ircutils.isNick('[0]')) self.assertTrue(ircutils.isNick('{jemfinch}')) self.assertTrue(ircutils.isNick('[jemfinch]')) self.assertTrue(ircutils.isNick('jem|finch')) self.assertTrue(ircutils.isNick('\\```')) self.assertTrue(ircutils.isNick('`')) self.assertTrue(ircutils.isNick('A')) self.assertFalse(ircutils.isNick('')) self.assertFalse(ircutils.isNick('8foo')) self.assertFalse(ircutils.isNick('10')) self.assertFalse(ircutils.isNick('-')) self.assertFalse(ircutils.isNick('-foo')) conf.supybot.protocols.irc.strictRfc.setValue(False) self.assertTrue(ircutils.isNick('services@something.undernet.net')) finally: conf.supybot.protocols.irc.strictRfc.setValue(original) def testIsNickNeverAllowsSpaces(self): try: original = conf.supybot.protocols.irc.strictRfc() conf.supybot.protocols.irc.strictRfc.setValue(True) self.assertFalse(ircutils.isNick('foo bar')) conf.supybot.protocols.irc.strictRfc.setValue(False) self.assertFalse(ircutils.isNick('foo bar')) finally: conf.supybot.protocols.irc.strictRfc.setValue(original) def testStandardSubstitute(self): # Stub out random msg and irc objects that provide what # standardSubstitute wants irc = getTestIrc() msg = ircmsgs.IrcMsg(':%s PRIVMSG #channel :stuff' % self.hostmask) irc._tagMsg(msg) f = ircutils.standardSubstitute vars = {'foo': 'bar', 'b': 'c', 'i': 100, 'f': lambda: 'called'} self.assertEqual(f(irc, msg, '$foo', vars), 'bar') self.assertEqual(f(irc, None, '$foo', vars), 'bar') self.assertEqual(f(None, None, '$foo', vars), 'bar') self.assertEqual(f(None, msg, '$foo', vars), 'bar') self.assertEqual(f(irc, msg, '${foo}', vars), 'bar') self.assertEqual(f(irc, msg, '$b', vars), 'c') self.assertEqual(f(irc, msg, '${b}', vars), 'c') self.assertEqual(f(irc, msg, '$i', vars), '100') self.assertEqual(f(irc, msg, '${i}', vars), '100') self.assertEqual(f(irc, msg, '$f', vars), 'called') self.assertEqual(f(irc, msg, '${f}', vars), 'called') self.assertEqual(f(irc, msg, '$b:$i', vars), 'c:100') def testBanmask(self): for msg in msgs: if ircutils.isUserHostmask(msg.prefix): banmask = ircutils.banmask(msg.prefix) self.assertTrue(ircutils.hostmaskPatternEqual(banmask, msg.prefix), '%r didn\'t match %r' % (msg.prefix, banmask)) self.assertEqual(ircutils.banmask('foobar!user@host'), '*!*@host') self.assertEqual(ircutils.banmask('foobar!user@host.tld'), '*!*@host.tld') self.assertEqual(ircutils.banmask('foobar!user@sub.host.tld'), '*!*@*.host.tld') self.assertEqual(ircutils.banmask('foo!bar@2001::'), '*!*@2001::*') def testSeparateModes(self): self.assertEqual(ircutils.separateModes(['+ooo', 'x', 'y', 'z']), [('+o', 'x'), ('+o', 'y'), ('+o', 'z')]) self.assertEqual(ircutils.separateModes(['+o-o', 'x', 'y']), [('+o', 'x'), ('-o', 'y')]) self.assertEqual(ircutils.separateModes(['+s-o', 'x']), [('+s', None), ('-o', 'x')]) self.assertEqual(ircutils.separateModes(['+sntl', '100']), [('+s', None),('+n', None),('+t', None),('+l', 100)]) def testNickFromHostmask(self): self.assertEqual(ircutils.nickFromHostmask('nick!user@host.domain.tld'), 'nick') # Hostmasks with user prefixes are sent via userhost-in-names. We need to # properly handle the case where ! is a prefix and not grab '' as the nick # instead. self.assertEqual(ircutils.nickFromHostmask('@nick!user@some.other.host'), '@nick') self.assertEqual(ircutils.nickFromHostmask('!@nick!user@some.other.host'), '!@nick') def testToLower(self): self.assertEqual('jemfinch', ircutils.toLower('jemfinch')) self.assertEqual('{}|^', ircutils.toLower('[]\\~')) def testReplyTo(self): irc = getTestIrc() prefix = 'foo!bar@baz' channel = ircmsgs.privmsg('#foo', 'bar baz', prefix=prefix) private = ircmsgs.privmsg('jemfinch', 'bar baz', prefix=prefix) irc._tagMsg(channel) irc._tagMsg(private) self.assertEqual(ircutils.replyTo(channel), channel.args[0]) self.assertEqual(ircutils.replyTo(private), private.nick) def testJoinModes(self): plusE = ('+e', '*!*@*ohio-state.edu') plusB = ('+b', '*!*@*umich.edu') minusL = ('-l', None) modes = [plusB, plusE, minusL] self.assertEqual(ircutils.joinModes(modes), ['+be-l', plusB[1], plusE[1]]) def testDccIpStuff(self): def randomIP(): def rand(): return random.randrange(0, 256) return '.'.join(map(str, [rand(), rand(), rand(), rand()])) for _ in range(100): # 100 should be good :) ip = randomIP() self.assertEqual(ip, ircutils.unDccIP(ircutils.dccIP(ip))) class IrcDictTestCase(SupyTestCase): def test(self): d = ircutils.IrcDict() d['#FOO'] = 'bar' self.assertEqual(d['#FOO'], 'bar') self.assertEqual(d['#Foo'], 'bar') self.assertEqual(d['#foo'], 'bar') del d['#fOO'] d['jemfinch{}'] = 'bar' self.assertEqual(d['jemfinch{}'], 'bar') self.assertEqual(d['jemfinch[]'], 'bar') self.assertEqual(d['JEMFINCH[]'], 'bar') def testKeys(self): d = ircutils.IrcDict() self.assertEqual(d.keys(), []) def testSetdefault(self): d = ircutils.IrcDict() d.setdefault('#FOO', []).append(1) self.assertEqual(d['#foo'], [1]) self.assertEqual(d['#fOO'], [1]) self.assertEqual(d['#FOO'], [1]) def testGet(self): d = ircutils.IrcDict() self.assertEqual(d.get('#FOO'), None) d['#foo'] = 1 self.assertEqual(d.get('#FOO'), 1) def testContains(self): d = ircutils.IrcDict() d['#FOO'] = None self.assertTrue('#foo' in d) d['#fOOBAR[]'] = None self.assertTrue('#foobar{}' in d) def testGetSetItem(self): d = ircutils.IrcDict() d['#FOO'] = 12 self.assertEqual(12, d['#foo']) d['#fOOBAR[]'] = 'blah' self.assertEqual('blah', d['#foobar{}']) def testCopyable(self): d = ircutils.IrcDict() d['foo'] = 'bar' self.assertTrue(d == copy.copy(d)) self.assertTrue(d == copy.deepcopy(d)) class IrcSetTestCase(SupyTestCase): def test(self): s = ircutils.IrcSet() s.add('foo') s.add('bar') self.assertTrue('foo' in s) self.assertTrue('FOO' in s) s.discard('alfkj') s.remove('FOo') self.assertFalse('foo' in s) self.assertFalse('FOo' in s) def testCopy(self): s = ircutils.IrcSet() s.add('foo') s.add('bar') s1 = copy.deepcopy(s) self.assertTrue('foo' in s) self.assertTrue('FOO' in s) s.discard('alfkj') s.remove('FOo') self.assertFalse('foo' in s) self.assertFalse('FOo' in s) self.assertTrue('foo' in s1) self.assertTrue('FOO' in s1) s1.discard('alfkj') s1.remove('FOo') self.assertFalse('foo' in s1) self.assertFalse('FOo' in s1) class IrcStringTestCase(SupyTestCase): def testEquality(self): self.assertEqual('#foo', ircutils.IrcString('#foo')) self.assertEqual('#foo', ircutils.IrcString('#FOO')) self.assertEqual('#FOO', ircutils.IrcString('#foo')) self.assertEqual('#FOO', ircutils.IrcString('#FOO')) self.assertEqual(hash(ircutils.IrcString('#FOO')), hash(ircutils.IrcString('#foo'))) def testInequality(self): s1 = 'supybot' s2 = ircutils.IrcString('Supybot') self.assertTrue(s1 == s2) self.assertFalse(s1 != s2) class AuthenticateTestCase(SupyTestCase): PAIRS = [ (b'', ['+']), (b'foo'*150, [ 'Zm9v'*100, 'Zm9v'*50 ]), (b'foo'*200, [ 'Zm9v'*100, 'Zm9v'*100, '+']) ] def assertMessages(self, got, should): got = list(got) for (s1, s2) in zip(got, should): self.assertEqual(s1, s2, (got, should)) def testGenerator(self): for (decoded, encoded) in self.PAIRS: self.assertMessages( ircutils.authenticate_generator(decoded), encoded) def testDecoder(self): for (decoded, encoded) in self.PAIRS: decoder = ircutils.AuthenticateDecoder() for chunk in encoded: self.assertFalse(decoder.ready, (decoded, encoded)) decoder.feed(ircmsgs.IrcMsg(command='AUTHENTICATE', args=(chunk,))) self.assertTrue(decoder.ready) self.assertEqual(decoder.get(), decoded) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/test/test_misc.py0000644000175000017500000000634113634634533016647 0ustar valval00000000000000### # Copyright (c) 2019, James Lu # 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 from supybot.test import * class MiscTestCase(SupyTestCase): def testAuthorExpand(self): # The standard 3 pair: name, nick, email self.assertEqual(str(supybot.authors.progval), 'Valentin Lorentz (ProgVal) ') # All 3 provided, but name == nick self.assertEqual(str(supybot.Author('foobar', 'foobar', 'foobar@example.net')), 'foobar ') # Only name provided self.assertEqual(str(supybot.Author('somedev')), 'somedev') # Only nick provided self.assertEqual(str(supybot.Author(nick='somedev')), 'somedev') # Only name and nick provided self.assertEqual(str(supybot.Author('James Lu', 'tacocat')), 'James Lu (tacocat)') # Only name and nick provided, but name == nick self.assertEqual(str(supybot.Author('tacocat', 'tacocat')), 'tacocat') # Only name and email self.assertEqual(str(supybot.authors.jlu), 'James Lu ') # Only nick and email self.assertEqual(str(supybot.Author(nick='abcdef', email='abcdef@example.org')), 'abcdef ') # Only email? self.assertEqual(str(supybot.Author(email='xyzzy@localhost.localdomain')), 'Unknown author ') def testAuthorExpandShort(self): self.assertEqual(supybot.authors.progval.format(short=True), 'Valentin Lorentz (ProgVal)') self.assertEqual(supybot.authors.jlu.format(short=True), 'James Lu') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/test/test_plugin.py0000644000175000017500000000404313634634533017207 0ustar valval00000000000000### # Copyright (c) 2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * import supybot.plugin as plugin class FunctionsTestCase(SupyTestCase): def testLoadPluginModule(self): self.assertRaises(ImportError, plugin.loadPluginModule, 'asldj') self.assertTrue(plugin.loadPluginModule('Owner')) # I haven't yet figured out a way to get case-insensitivity back for # "directoried" plugins. #self.assertTrue(plugin.loadPluginModule('owner')) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/test/test_plugin_create.py0000644000175000017500000001126413634634533020535 0ustar valval00000000000000### # Copyright (c) 2018, James Lu # 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 from supybot.test import * import subprocess import unittest import os import shutil import compileall try: from shutil import which except ImportError: # Python < 3.3 from distutils.spawn import find_executable as which TEST_PLUGIN_NAME = "TestPlugin" class PluginCreateTestCase(SupyTestCase): @staticmethod def _communicate(proc, text): outs, errs = proc.communicate(input=text) supybot.log.info("testPluginCreate: supybot-plugin-create outs:") for line in outs.splitlines(): supybot.log.info(" %s", line.decode()) supybot.log.info("testPluginCreate: supybot-plugin-create errs:") for line in errs.splitlines(): supybot.log.info(" %s", line.decode()) def _makeplugin(self): proc = subprocess.Popen(['supybot-plugin-create'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # In order: plugin name, threaded?, author, use Supybot license?, description cmdinput = TEST_PLUGIN_NAME.encode() + b""" n Test Case Runner y Dummy test plugin """ self._communicate(proc, cmdinput) def testPluginCreate(self): if not which('supybot-plugin-create'): # When tests are run automatically as part of a build process (e.g. in Debian), # don't assume that the bot is installed yet. print('Skipping supybot-plugin-create test because Limnoria has not been installed yet') return tmpdir = conf.supybot.directories.data.tmp() curdir = os.getcwd() try: os.chdir(tmpdir) if TEST_PLUGIN_NAME in os.listdir('.'): supybot.log.info("testPluginCreate: Removing old TestPlugin directory") shutil.rmtree(TEST_PLUGIN_NAME) self._makeplugin() self.assertIn(TEST_PLUGIN_NAME, os.listdir('.')) # Make sure that out generated plugin is valid compileall.compile_dir(TEST_PLUGIN_NAME) finally: os.chdir(curdir) class PluginCreateNoninteractiveTestCase(PluginCreateTestCase): def _makeplugin(self): with open(os.devnull, 'w') as devnull: # Compat with Python < 3.3 retcode = subprocess.call(['supybot-plugin-create', '-n', TEST_PLUGIN_NAME, '--author=skynet', '--desc=Some description'], stdin=devnull) self.assertFalse(retcode) # Check that the return code is 0 class PluginCreatePartialArgsTestCase(PluginCreateTestCase): def _makeplugin(self): # We passed in a subset of args, so the script should only prompt for the # ones not given proc = subprocess.Popen(['supybot-plugin-create', '-n', TEST_PLUGIN_NAME], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # In order: threaded?, author, use Supybot license?, description cmdinput = TEST_PLUGIN_NAME.encode() + b""" Test Case Runner y Dummy test plugin """ self._communicate(proc, cmdinput) limnoria-2020.03.17/test/test_plugins.py0000644000175000017500000000452213634634533017374 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2008, James McCoy # 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 * import supybot.conf as conf import supybot.irclib as irclib import supybot.plugins as plugins class PluginsTestCase(SupyTestCase): def testMakeChannelFilename(self): self.assertEqual( plugins.makeChannelFilename('dir', '#foo'), conf.supybot.directories.data() + '/#foo/dir') self.assertEqual( plugins.makeChannelFilename('dir', '#f/../oo'), conf.supybot.directories.data() + '/#f..oo/dir') self.assertEqual( plugins.makeChannelFilename('dir', '/./'), conf.supybot.directories.data() + '/_/dir') self.assertEqual( plugins.makeChannelFilename('dir', '/../'), conf.supybot.directories.data() + '/__/dir') limnoria-2020.03.17/test/test_registry.py0000644000175000017500000002234613634634533017567 0ustar valval00000000000000### # Copyright (c) 2004, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * import re import supybot.conf as conf import supybot.registry as registry join = registry.join split = registry.split escape = registry.escape unescape = registry.unescape class FunctionsTestCase(SupyTestCase): def testEscape(self): self.assertEqual('foo', escape('foo')) self.assertEqual('foo\\.bar', escape('foo.bar')) self.assertEqual('foo\\:bar', escape('foo:bar')) def testUnescape(self): self.assertEqual('foo', unescape('foo')) self.assertEqual('foo.bar', unescape('foo\\.bar')) self.assertEqual('foo:bar', unescape('foo\\:bar')) def testEscapeAndUnescapeAreInverses(self): for s in ['foo', 'foo.bar']: self.assertEqual(s, unescape(escape(s))) self.assertEqual(escape(s), escape(unescape(escape(s)))) def testSplit(self): self.assertEqual(['foo'], split('foo')) self.assertEqual(['foo', 'bar'], split('foo.bar')) self.assertEqual(['foo.bar'], split('foo\\.bar')) def testJoin(self): self.assertEqual('foo', join(['foo'])) self.assertEqual('foo.bar', join(['foo', 'bar'])) self.assertEqual('foo\\.bar', join(['foo.bar'])) def testJoinAndSplitAreInverses(self): for s in ['foo', 'foo.bar', 'foo\\.bar']: self.assertEqual(s, join(split(s))) self.assertEqual(split(s), split(join(split(s)))) class ValuesTestCase(SupyTestCase): def testBoolean(self): v = registry.Boolean(True, """Help""") self.assertTrue(v()) v.setValue(False) self.assertFalse(v()) v.set('True') self.assertTrue(v()) v.set('False') self.assertFalse(v()) v.set('On') self.assertTrue(v()) v.set('Off') self.assertFalse(v()) v.set('enable') self.assertTrue(v()) v.set('disable') self.assertFalse(v()) v.set('toggle') self.assertTrue(v()) v.set('toggle') self.assertFalse(v()) def testInteger(self): v = registry.Integer(1, 'help') self.assertEqual(v(), 1) v.setValue(10) self.assertEqual(v(), 10) v.set('100') self.assertEqual(v(), 100) v.set('-1000') self.assertEqual(v(), -1000) def testPositiveInteger(self): v = registry.PositiveInteger(1, 'help') self.assertEqual(v(), 1) self.assertRaises(registry.InvalidRegistryValue, v.setValue, -1) self.assertRaises(registry.InvalidRegistryValue, v.set, '-1') def testFloat(self): v = registry.Float(1.0, 'help') self.assertEqual(v(), 1.0) v.setValue(10) self.assertEqual(v(), 10.0) v.set('0') self.assertEqual(v(), 0.0) def testString(self): v = registry.String('foo', 'help') self.assertEqual(v(), 'foo') v.setValue('bar') self.assertEqual(v(), 'bar') v.set('"biff"') self.assertEqual(v(), 'biff') v.set("'buff'") self.assertEqual(v(), 'buff') v.set('"xyzzy') self.assertEqual(v(), '"xyzzy') def testJson(self): data = {'foo': ['bar', 'baz', 5], 'qux': None} v = registry.Json('foo', 'help') self.assertEqual(v(), 'foo') v.setValue(data) self.assertEqual(v(), data) self.assertIsNot(v(), data) with v.editable() as dict_: dict_['supy'] = 'bot' del dict_['qux'] self.assertNotIn('supy', v()) self.assertIn('qux', v()) self.assertIn('supy', v()) self.assertEqual(v()['supy'], 'bot') self.assertIsNot(v()['supy'], 'bot') self.assertNotIn('qux', v()) def testNormalizedString(self): v = registry.NormalizedString("""foo bar baz biff """, 'help') self.assertEqual(v(), 'foo bar baz biff') v.setValue('foo bar baz') self.assertEqual(v(), 'foo bar baz') v.set('"foo bar baz"') self.assertEqual(v(), 'foo bar baz') def testStringSurroundedBySpaces(self): v = registry.StringSurroundedBySpaces('foo', 'help') self.assertEqual(v(), ' foo ') v.setValue('||') self.assertEqual(v(), ' || ') v.set('&&') self.assertEqual(v(), ' && ') def testCommaSeparatedListOfStrings(self): v = registry.CommaSeparatedListOfStrings(['foo', 'bar'], 'help') self.assertEqual(v(), ['foo', 'bar']) v.setValue(['foo', 'bar', 'baz']) self.assertEqual(v(), ['foo', 'bar', 'baz']) v.set('foo,bar') self.assertEqual(v(), ['foo', 'bar']) def testRegexp(self): v = registry.Regexp(None, 'help') self.assertEqual(v(), None) v.set('m/foo/') self.assertTrue(v().match('foo')) v.set('') self.assertEqual(v(), None) self.assertRaises(registry.InvalidRegistryValue, v.setValue, re.compile(r'foo')) def testBackslashesKeys(self): conf.supybot.reply.whenAddressedBy.strings.get(':foo').set('=/*') filename = conf.supybot.directories.conf.dirize('backslashes1.conf') registry.close(conf.supybot, filename) registry.open_registry(filename) value = conf.supybot.reply.whenAddressedBy.strings.get(':foo')() self.assertEqual(value, set(['=/*'])) def testBackslashesValues(self): conf.supybot.reply.whenAddressedBy.chars.set('\\') filename = conf.supybot.directories.conf.dirize('backslashes2.conf') registry.close(conf.supybot, filename) registry.open_registry(filename) self.assertEqual(conf.supybot.reply.whenAddressedBy.chars(), '\\') def testWith(self): v = registry.String('foo', 'help') self.assertEqual(v(), 'foo') with v.context('bar'): self.assertEqual(v(), 'bar') self.assertEqual(v(), 'foo') class SecurityTestCase(SupyTestCase): def testPrivate(self): v = registry.String('foo', 'help') self.assertFalse(v._private) v = registry.String('foo', 'help', private=True) self.assertTrue(v._private) g = registry.Group('foo') v = registry.String('foo', 'help') g.register('val', v) self.assertFalse(g._private) self.assertFalse(g.val._private) g = registry.Group('foo', private=True) v = registry.String('foo', 'help') g.register('val', v) self.assertTrue(g._private) self.assertTrue(g.val._private) g = registry.Group('foo') v = registry.String('foo', 'help', private=True) g.register('val', v) self.assertFalse(g._private) self.assertTrue(g.val._private) class InheritanceTestCase(SupyTestCase): def testChild(self): parent = registry.String('foo', 'help') parent._supplyDefault = True self.assertTrue(parent._wasSet) self.assertEqual(parent(), 'foo') child = parent.get('child') self.assertFalse(child._wasSet) self.assertEqual(child(), 'foo') parent.setValue('bar') self.assertTrue(parent._wasSet) self.assertEqual(parent(), 'bar') self.assertFalse(child._wasSet) self.assertEqual(child(), 'bar') # Takes the new parent value child.setValue('baz') self.assertTrue(parent._wasSet) self.assertEqual(parent(), 'bar') self.assertTrue(child._wasSet) self.assertEqual(child(), 'baz') parent.setValue('qux') self.assertTrue(parent._wasSet) self.assertEqual(parent(), 'qux') self.assertTrue(child._wasSet) self.assertEqual(child(), 'baz') # Keeps its own value # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/test/test_schedule.py0000644000175000017500000001066313634634533017512 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * import time import supybot.schedule as schedule # Use a different driver name than the main Schedule, because Schedule # is a driver so it self-registers in the dict of drivers with its name. # So by keeping the name we overwrite the main Schedule in the dict of # drivers (which is supposed to be in sync with the one accessible # as supybot.schedule.schedule). class FakeSchedule(schedule.Schedule): def name(self): return 'FakeSchedule' class TestSchedule(SupyTestCase): def testSchedule(self): sched = FakeSchedule() i = [0] def add10(): i[0] = i[0] + 10 def add1(): i[0] = i[0] + 1 sched.addEvent(add10, time.time() + 3) sched.addEvent(add1, time.time() + 1) timeFastForward(1.2) sched.run() self.assertEqual(i[0], 1) timeFastForward(1.9) sched.run() self.assertEqual(i[0], 11) sched.addEvent(add10, time.time() + 3, 'test') sched.run() self.assertEqual(i[0], 11) sched.removeEvent('test') self.assertEqual(i[0], 11) timeFastForward(3) self.assertEqual(i[0], 11) def testReschedule(self): sched = FakeSchedule() i = [0] def inc(): i[0] += 1 n = sched.addEvent(inc, time.time() + 1) sched.rescheduleEvent(n, time.time() + 3) timeFastForward(1.2) sched.run() self.assertEqual(i[0], 0) timeFastForward(2) sched.run() self.assertEqual(i[0], 1) def testPeriodic(self): sched = FakeSchedule() i = [0] def inc(): i[0] += 1 n = sched.addPeriodicEvent(inc, 1, name='test_periodic') timeFastForward(0.6) sched.run() # 0.6 self.assertEqual(i[0], 1) timeFastForward(0.6) sched.run() # 1.2 self.assertEqual(i[0], 2) timeFastForward(0.6) sched.run() # 1.8 self.assertEqual(i[0], 2) timeFastForward(0.6) sched.run() # 2.4 self.assertEqual(i[0], 3) sched.removePeriodicEvent(n) timeFastForward(1) sched.run() # 3.4 self.assertEqual(i[0], 3) def testCountedPeriodic(self): sched = FakeSchedule() i = [0] def inc(): i[0] += 1 n = sched.addPeriodicEvent(inc, 1, name='test_periodic', count=3) timeFastForward(0.6) sched.run() # 0.6 self.assertEqual(i[0], 1) timeFastForward(0.6) sched.run() # 1.2 self.assertEqual(i[0], 2) timeFastForward(0.6) sched.run() # 1.8 self.assertEqual(i[0], 2) timeFastForward(0.6) sched.run() # 2.4 self.assertEqual(i[0], 3) timeFastForward(1) sched.run() # 3.4 self.assertEqual(i[0], 3) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/test/test_standardSubstitute.py0000644000175000017500000000721313634634533021607 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions, and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions, and the following disclaimer in the # documentation and/or other materials provided with the distribution. # * Neither the name of the author of this software nor the name of # contributors to this software may be used to endorse or promote products # derived from this software without specific prior written consent. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### from supybot.test import * import supybot.irclib as irclib from supybot.utils.iter import all import supybot.ircutils as ircutils class holder: users = set(map(str, range(1000))) class FunctionsTestCase(SupyTestCase): @retry() def testStandardSubstitute(self): irc = getTestIrc() irc.state.channels = {'#foo': holder()} f = ircutils.standardSubstitute msg = ircmsgs.privmsg('#foo', 'filler', prefix='biff!quux@xyzzy') irc._tagMsg(msg) s = f(irc, msg, '$rand') try: int(s) except ValueError: self.fail('$rand wasn\'t an int.') s = f(irc, msg, '$randomInt') try: int(s) except ValueError: self.fail('$randomint wasn\'t an int.') self.assertEqual(f(irc, msg, '$botnick'), irc.nick) self.assertEqual(f(irc, msg, '$who'), msg.nick) self.assertEqual(f(irc, msg, '$WHO'), msg.nick, 'stand. sub. not case-insensitive.') self.assertEqual(f(irc, msg, '$nick'), msg.nick) self.assertNotEqual(f(irc, msg, '$randomdate'), '$randomdate') q = f(irc,msg,'$randomdate\t$randomdate') dl = q.split('\t') if dl[0] == dl[1]: self.fail ('Two $randomdates in the same string were the same') q = f(irc, msg, '$randomint\t$randomint') dl = q.split('\t') if dl[0] == dl[1]: self.fail ('Two $randomints in the same string were the same') self.assertNotEqual(f(irc, msg, '$today'), '$today') self.assertNotEqual(f(irc, msg, '$now'), '$now') n = f(irc, msg, '$randnick') self.assertTrue(n in irc.state.channels['#foo'].users) n = f(irc, msg, '$randomnick') self.assertTrue(n in irc.state.channels['#foo'].users) n = f(irc, msg, '$randomnick '*100) L = n.split() self.assertFalse(all(L[0].__eq__, L), 'all $randomnicks were the same') c = f(irc, msg, '$channel') self.assertEqual(c, msg.args[0]) net = f(irc, msg, '$network') self.assertEqual(net, irc.network) limnoria-2020.03.17/test/test_utils.py0000644000175000017500000013054313634634533017056 0ustar valval00000000000000### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009,2011, James McCoy # 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 * import sys import time import pickle import supybot.conf as conf import supybot.utils as utils from supybot.utils.structures import * import supybot.utils.minisix as minisix if sys.version_info[0] >= 0: xrange = range class UtilsTest(SupyTestCase): def testReversed(self): L = list(range(10)) revL = list(reversed(L)) L.reverse() self.assertEqual(L, revL, 'reversed didn\'t return reversed list') for _ in reversed([]): self.fail('reversed caused iteration over empty sequence') class SeqTest(SupyTestCase): def testRenumerate(self): for i in xrange(5): L = list(enumerate(range(i))) LL = list(utils.seq.renumerate(range(i))) self.assertEqual(L, LL[::-1]) def testWindow(self): L = list(range(10)) def wwindow(*args): return list(utils.seq.window(*args)) self.assertEqual(wwindow([], 1), [], 'Empty sequence, empty window') self.assertEqual(wwindow([], 2), [], 'Empty sequence, empty window') self.assertEqual(wwindow([], 5), [], 'Empty sequence, empty window') self.assertEqual(wwindow([], 100), [], 'Empty sequence, empty window') self.assertEqual(wwindow(L, 1), [[x] for x in L], 'Window length 1') self.assertRaises(ValueError, wwindow, [], 0) self.assertRaises(ValueError, wwindow, [], -1) class GenTest(SupyTestCase): def testInsensitivePreservingDict(self): ipd = utils.InsensitivePreservingDict d = ipd(dict(Foo=10)) self.assertTrue(d['foo'] == 10) self.assertEqual(d.keys(), ['Foo']) self.assertEqual(d.get('foo'), 10) self.assertEqual(d.get('Foo'), 10) def testFindBinaryInPath(self): if os.name == 'posix': self.assertEqual(None, utils.findBinaryInPath('asdfhjklasdfhjkl')) self.assertTrue(utils.findBinaryInPath('sh').endswith('/bin/sh')) def testExnToString(self): try: raise KeyError(1) except Exception as e: self.assertEqual(utils.exnToString(e), 'KeyError: 1') try: raise EOFError() except Exception as e: self.assertEqual(utils.exnToString(e), 'EOFError') def testSaltHash(self): s = utils.saltHash('jemfinch') (salt, hash) = s.split('|') self.assertEqual(utils.saltHash('jemfinch', salt=salt), s) def testSafeEval(self): for s in ['1', '()', '(1,)', '[]', '{}', '{1:2}', '{1:(2,3)}', '1.0', '[1,2,3]', 'True', 'False', 'None', '(True,False,None)', '"foo"', '{"foo": "bar"}']: self.assertEqual(eval(s), utils.safeEval(s)) for s in ['lambda: 2', 'import foo', 'foo.bar']: self.assertRaises(ValueError, utils.safeEval, s) def testSafeEvalTurnsSyntaxErrorIntoValueError(self): self.assertRaises(ValueError, utils.safeEval, '/usr/local/') def testIterableMap(self): class alist(utils.IterableMap): def __init__(self): self.L = [] def __setitem__(self, key, value): self.L.append((key, value)) def items(self): for (k, v) in self.L: yield (k, v) AL = alist() self.assertFalse(AL) AL[1] = 2 AL[2] = 3 AL[3] = 4 self.assertTrue(AL) self.assertEqual(list(AL.items()), [(1, 2), (2, 3), (3, 4)]) self.assertEqual(list(AL.items()), [(1, 2), (2, 3), (3, 4)]) self.assertEqual(list(AL.keys()), [1, 2, 3]) if minisix.PY2: self.assertEqual(list(AL.keys()), [1, 2, 3]) self.assertEqual(list(AL.values()), [2, 3, 4]) self.assertEqual(list(AL.values()), [2, 3, 4]) self.assertEqual(len(AL), 3) def testSortBy(self): L = ['abc', 'z', 'AD'] utils.sortBy(len, L) self.assertEqual(L, ['z', 'AD', 'abc']) utils.sortBy(str.lower, L) self.assertEqual(L, ['abc', 'AD', 'z']) L = ['supybot', 'Supybot'] utils.sortBy(str.lower, L) self.assertEqual(L, ['supybot', 'Supybot']) def testSorted(self): L = ['a', 'c', 'b'] self.assertEqual(sorted(L), ['a', 'b', 'c']) self.assertEqual(L, ['a', 'c', 'b']) self.assertEqual(sorted(L, reverse=True), ['c', 'b', 'a']) def testTimeElapsed(self): self.assertRaises(ValueError, utils.timeElapsed, 0, leadingZeroes=False, seconds=False) then = 0 now = 0 for now, expected in [(0, '0 seconds'), (1, '1 second'), (60, '1 minute and 0 seconds'), (61, '1 minute and 1 second'), (62, '1 minute and 2 seconds'), (122, '2 minutes and 2 seconds'), (3722, '1 hour, 2 minutes, and 2 seconds'), (7322, '2 hours, 2 minutes, and 2 seconds'), (90061,'1 day, 1 hour, 1 minute, and 1 second'), (180122, '2 days, 2 hours, 2 minutes, ' 'and 2 seconds')]: self.assertEqual(utils.timeElapsed(now - then), expected) def timeElapsedShort(self): self.assertEqual(utils.timeElapsed(123, short=True), '2m 3s') def testAbbrev(self): L = ['abc', 'bcd', 'bbe', 'foo', 'fool'] d = utils.abbrev(L) def getItem(s): return d[s] self.assertRaises(KeyError, getItem, 'f') self.assertRaises(KeyError, getItem, 'fo') self.assertRaises(KeyError, getItem, 'b') self.assertEqual(d['bb'], 'bbe') self.assertEqual(d['bc'], 'bcd') self.assertEqual(d['a'], 'abc') self.assertEqual(d['ab'], 'abc') self.assertEqual(d['fool'], 'fool') self.assertEqual(d['foo'], 'foo') def testAbbrevFailsWithDups(self): L = ['english', 'english'] self.assertRaises(ValueError, utils.abbrev, L) class StrTest(SupyTestCase): def testRsplit(self): rsplit = utils.str.rsplit self.assertEqual(rsplit('foo bar baz'), 'foo bar baz'.split()) self.assertEqual(rsplit('foo bar baz', maxsplit=1), ['foo bar', 'baz']) self.assertEqual(rsplit('foo bar baz', maxsplit=1), ['foo bar', 'baz']) self.assertEqual(rsplit('foobarbaz', 'bar'), ['foo', 'baz']) def testMatchCase(self): f = utils.str.matchCase self.assertEqual('bar', f('foo', 'bar')) self.assertEqual('Bar', f('Foo', 'bar')) self.assertEqual('BAr', f('FOo', 'bar')) self.assertEqual('BAR', f('FOO', 'bar')) self.assertEqual('bAR', f('fOO', 'bar')) self.assertEqual('baR', f('foO', 'bar')) self.assertEqual('BaR', f('FoO', 'bar')) def testPluralize(self): f = utils.str.pluralize self.assertEqual('bikes', f('bike')) self.assertEqual('BIKES', f('BIKE')) self.assertEqual('matches', f('match')) self.assertEqual('Patches', f('Patch')) self.assertEqual('fishes', f('fish')) self.assertEqual('tries', f('try')) self.assertEqual('days', f('day')) def testDepluralize(self): f = utils.str.depluralize self.assertEqual('bike', f('bikes')) self.assertEqual('Bike', f('Bikes')) self.assertEqual('BIKE', f('BIKES')) self.assertEqual('match', f('matches')) self.assertEqual('Match', f('Matches')) self.assertEqual('fish', f('fishes')) self.assertEqual('try', f('tries')) def testDistance(self): self.assertEqual(utils.str.distance('', ''), 0) self.assertEqual(utils.str.distance('a', 'b'), 1) self.assertEqual(utils.str.distance('a', 'a'), 0) self.assertEqual(utils.str.distance('foobar', 'jemfinch'), 8) self.assertEqual(utils.str.distance('a', 'ab'), 1) self.assertEqual(utils.str.distance('foo', ''), 3) self.assertEqual(utils.str.distance('', 'foo'), 3) self.assertEqual(utils.str.distance('appel', 'nappe'), 2) self.assertEqual(utils.str.distance('nappe', 'appel'), 2) def testSoundex(self): L = [('Euler', 'E460'), ('Ellery', 'E460'), ('Gauss', 'G200'), ('Ghosh', 'G200'), ('Hilbert', 'H416'), ('Heilbronn', 'H416'), ('Knuth', 'K530'), ('Kant', 'K530'), ('Lloyd', 'L300'), ('Ladd', 'L300'), ('Lukasiewicz', 'L222'), ('Lissajous', 'L222')] for (name, key) in L: soundex = utils.str.soundex(name) self.assertEqual(soundex, key, '%s was %s, not %s' % (name, soundex, key)) self.assertRaises(ValueError, utils.str.soundex, '3') self.assertRaises(ValueError, utils.str.soundex, "'") def testDQRepr(self): L = ['foo', 'foo\'bar', 'foo"bar', '"', '\\', '', '\x00'] for s in L: r = utils.str.dqrepr(s) self.assertEqual(s, eval(r), s) self.assertTrue(r[0] == '"' and r[-1] == '"', s) def testPerlReToPythonRe(self): f = utils.str.perlReToPythonRe r = f('m/foo/') self.assertTrue(r.search('foo')) r = f('/foo/') self.assertTrue(r.search('foo')) r = f('m/\\//') self.assertTrue(r.search('/')) r = f('m/cat/i') self.assertTrue(r.search('CAT')) self.assertRaises(ValueError, f, 'm/?/') def testP2PReDifferentSeparator(self): r = utils.str.perlReToPythonRe('m!foo!') self.assertTrue(r.search('foo')) r = utils.str.perlReToPythonRe('m{cat}') self.assertTrue(r.search('cat')) def testPerlReToReplacer(self): PRTR = utils.str.perlReToReplacer f = PRTR('s/foo/bar/') self.assertEqual(f('foobarbaz'), 'barbarbaz') f = PRTR('s/fool/bar/') self.assertEqual(f('foobarbaz'), 'foobarbaz') f = PRTR('s/foo//') self.assertEqual(f('foobarbaz'), 'barbaz') f = PRTR('s/ba//') self.assertEqual(f('foobarbaz'), 'foorbaz') f = PRTR('s/ba//g') self.assertEqual(f('foobarbaz'), 'foorz') f = PRTR('s/ba\\///g') self.assertEqual(f('fooba/rba/z'), 'foorz') f = PRTR('s/ba\\\\//g') self.assertEqual(f('fooba\\rba\\z'), 'foorz') f = PRTR('s/cat/dog/i') self.assertEqual(f('CATFISH'), 'dogFISH') f = PRTR(r's/foo/foo\/bar/') self.assertEqual(f('foo'), 'foo/bar') f = PRTR('s/^/foo/') self.assertEqual(f('bar'), 'foobar') def testMultipleReplacer(self): replacer = utils.str.MultipleReplacer({'foo': 'bar', 'a': 'b'}) self.assertEqual(replacer('hi foo hi'), 'hi bar hi') def testMultipleRemover(self): remover = utils.str.MultipleRemover(['foo', 'bar']) self.assertEqual(remover('testfoobarbaz'), 'testbaz') def testPReToReplacerDifferentSeparator(self): f = utils.str.perlReToReplacer('s#foo#bar#') self.assertEqual(f('foobarbaz'), 'barbarbaz') def testPerlReToReplacerBug850931(self): f = utils.str.perlReToReplacer(r's/\b(\w+)\b/\1./g') self.assertEqual(f('foo bar baz'), 'foo. bar. baz.') def testCommaAndify(self): f = utils.str.commaAndify L = ['foo'] original = L[:] self.assertEqual(f(L), 'foo') self.assertEqual(f(L, And='or'), 'foo') self.assertEqual(L, original) L.append('bar') original = L[:] self.assertEqual(f(L), 'foo and bar') self.assertEqual(f(L, And='or'), 'foo or bar') self.assertEqual(L, original) L.append('baz') original = L[:] self.assertEqual(f(L), 'foo, bar, and baz') self.assertEqual(f(L, And='or'), 'foo, bar, or baz') self.assertEqual(f(L, comma=';'), 'foo; bar; and baz') self.assertEqual(f(L, comma=';', And='or'), 'foo; bar; or baz') self.assertEqual(L, original) self.assertTrue(f(set(L))) def testCommaAndifyRaisesTypeError(self): L = [(2,)] self.assertRaises(TypeError, utils.str.commaAndify, L) L.append((3,)) self.assertRaises(TypeError, utils.str.commaAndify, L) def testCommaAndifyConfig(self): f = utils.str.commaAndify L = ['foo', 'bar'] with conf.supybot.reply.format.list.maximumItems.context(3): self.assertEqual(f(L), 'foo and bar') L.append('baz') self.assertEqual(f(L), 'foo, bar, and baz') L.append('qux') self.assertEqual(f(L), 'foo, bar, and 2 others') L.append('quux') self.assertEqual(f(L), 'foo, bar, and 3 others') def testUnCommaThe(self): f = utils.str.unCommaThe self.assertEqual(f('foo bar'), 'foo bar') self.assertEqual(f('foo bar, the'), 'the foo bar') self.assertEqual(f('foo bar, The'), 'The foo bar') self.assertEqual(f('foo bar,the'), 'the foo bar') def testNormalizeWhitespace(self): f = utils.str.normalizeWhitespace self.assertEqual(f('foo bar'), 'foo bar') self.assertEqual(f('foo\nbar'), 'foo bar') self.assertEqual(f('foo\tbar'), 'foo bar') self.assertEqual(f('foo\rbar'), 'foo bar') def testNItems(self): nItems = utils.str.nItems self.assertEqual(nItems(0, 'tool'), '0 tools') self.assertEqual(nItems(1, 'tool', 'crazy'), '1 crazy tool') self.assertEqual(nItems(1, 'tool'), '1 tool') self.assertEqual(nItems(2, 'tool', 'crazy'), '2 crazy tools') self.assertEqual(nItems(2, 'tool'), '2 tools') def testOrdinal(self): ordinal = utils.str.ordinal self.assertEqual(ordinal(3), '3rd') self.assertEqual(ordinal('3'), '3rd') self.assertRaises(ValueError, ordinal, 'a') def testEllipsisify(self): f = utils.str.ellipsisify self.assertEqual(f('x'*30, 30), 'x'*30) self.assertTrue(len(f('x'*35, 30)) <= 30) self.assertTrue(f(' '.join(['xxxx']*10), 30)[:-3].endswith('xxxx')) class IterTest(SupyTestCase): def testLimited(self): L = range(10) self.assertEqual([], list(utils.iter.limited(L, 0))) self.assertEqual([0], list(utils.iter.limited(L, 1))) self.assertEqual([0, 1], list(utils.iter.limited(L, 2))) self.assertEqual(list(range(10)), list(utils.iter.limited(L, 10))) self.assertRaises(ValueError, list, utils.iter.limited(L, 11)) def testRandomChoice(self): choice = utils.iter.choice self.assertRaises(IndexError, choice, {}) self.assertRaises(IndexError, choice, []) self.assertRaises(IndexError, choice, ()) L = [1, 2] seenList = set() seenIterable = set() for n in xrange(300): # 2**266 > 10**80, the number of atoms in the known universe. # (ignoring dark matter, but that likely doesn't exist in atoms # anyway, so it shouldn't have a significant impact on that #) seenList.add(choice(L)) seenIterable.add(choice(iter(L))) self.assertEqual(len(L), len(seenList), 'choice isn\'t being random with lists') self.assertEqual(len(L), len(seenIterable), 'choice isn\'t being random with iterables') ## def testGroup(self): ## group = utils.iter.group ## s = '1. d4 d5 2. Nf3 Nc6 3. e3 Nf6 4. Nc3 e6 5. Bd3 a6' ## self.assertEqual(group(s.split(), 3)[:3], ## [['1.', 'd4', 'd5'], ## ['2.', 'Nf3', 'Nc6'], ## ['3.', 'e3', 'Nf6']]) def testAny(self): any = utils.iter.any self.assertTrue(any(lambda i: i == 0, range(10))) self.assertFalse(any(None, range(1))) self.assertTrue(any(None, range(2))) self.assertFalse(any(None, [])) def testAll(self): all = utils.iter.all self.assertFalse(all(lambda i: i == 0, range(10))) self.assertFalse(all(lambda i: i % 2, range(2))) self.assertFalse(all(lambda i: i % 2 == 0, [1, 3, 5])) self.assertTrue(all(lambda i: i % 2 == 0, [2, 4, 6])) self.assertTrue(all(None, ())) def testPartition(self): partition = utils.iter.partition L = range(10) def even(i): return not(i % 2) (yes, no) = partition(even, L) self.assertEqual(yes, [0, 2, 4, 6, 8]) self.assertEqual(no, [1, 3, 5, 7, 9]) def testIlen(self): ilen = utils.iter.ilen self.assertEqual(ilen(iter(range(10))), 10) def testSplit(self): itersplit = utils.iter.split L = [1, 2, 3] * 3 s = 'foo bar baz' self.assertEqual(list(itersplit(lambda x: x == 3, L)), [[1, 2], [1, 2], [1, 2]]) self.assertEqual(list(itersplit(lambda x: x == 3, L, yieldEmpty=True)), [[1, 2], [1, 2], [1, 2], []]) self.assertEqual(list(itersplit(lambda x: x, [])), []) self.assertEqual(list(itersplit(lambda c: c.isspace(), s)), list(map(list, s.split()))) self.assertEqual(list(itersplit('for'.__eq__, ['foo', 'for', 'bar'])), [['foo'], ['bar']]) self.assertEqual(list(itersplit('for'.__eq__, ['foo','for','bar','for', 'baz'], 1)), [['foo'], ['bar', 'for', 'baz']]) def testFlatten(self): def lflatten(seq): return list(utils.iter.flatten(seq)) self.assertEqual(lflatten([]), []) self.assertEqual(lflatten([1]), [1]) self.assertEqual(lflatten(range(10)), list(range(10))) twoRanges = list(range(10))*2 twoRanges.sort() self.assertEqual(lflatten(list(zip(range(10), range(10)))), twoRanges) self.assertEqual(lflatten([1, [2, 3], 4]), [1, 2, 3, 4]) self.assertEqual(lflatten([[[[[[[[[[]]]]]]]]]]), []) self.assertEqual(lflatten([1, [2, [3, 4], 5], 6]), [1, 2, 3, 4, 5, 6]) self.assertRaises(TypeError, lflatten, 1) class FileTest(SupyTestCase): def testLines(self): L = ['foo', 'bar', '#baz', ' ', 'biff'] self.assertEqual(list(utils.file.nonEmptyLines(L)), ['foo', 'bar', '#baz', 'biff']) self.assertEqual(list(utils.file.nonCommentLines(L)), ['foo', 'bar', ' ', 'biff']) self.assertEqual(list(utils.file.nonCommentNonEmptyLines(L)), ['foo', 'bar', 'biff']) def testMktemp(self): # This is mostly to test that it actually halts. self.assertTrue(utils.file.mktemp()) self.assertTrue(utils.file.mktemp()) self.assertTrue(utils.file.mktemp()) def testSanitizeName(self): self.assertEqual(utils.file.sanitizeName('#foo'), '#foo') self.assertEqual(utils.file.sanitizeName('#f/../oo'), '#f..oo') class NetTest(SupyTestCase): def testEmailRe(self): emailRe = utils.net.emailRe self.assertTrue(emailRe.match('jemfinch@supybot.com')) def testIsIP(self): isIP = utils.net.isIP self.assertFalse(isIP('a.b.c')) self.assertFalse(isIP('256.0.0.0')) self.assertFalse(isIP('127.0.0.1 127.0.0.1')) self.assertTrue(isIP('0.0.0.0')) self.assertTrue(isIP('100.100.100.100')) self.assertTrue(isIP('255.255.255.255')) def testIsIPV6(self): f = utils.net.isIPV6 self.assertFalse(f('2001:: 2001::')) self.assertTrue(f('2001::')) self.assertTrue(f('2001:888:0:1::666')) class WebTest(SupyTestCase): def testGetDomain(self): url = 'http://slashdot.org/foo/bar.exe' self.assertEqual(utils.web.getDomain(url), 'slashdot.org') if network: def testGetUrlWithSize(self): url = 'http://slashdot.org/' self.assertTrue(len(utils.web.getUrl(url, 1024)) == 1024) class FormatTestCase(SupyTestCase): def testNormal(self): format = utils.str.format self.assertEqual(format('I have %n of fruit: %L.', (3, 'kind'), ['apples', 'oranges', 'watermelon']), 'I have 3 kinds of fruit: ' 'apples, oranges, and watermelon.') def testPercentL(self): self.assertIn(format('%L', set(['apples', 'oranges', 'watermelon'])), [ 'apples, oranges, and watermelon', 'oranges, apples, and watermelon', 'apples, watermelon, and oranges', 'oranges, watermelon, and apples', 'watermelon, apples, and oranges', 'watermelon, oranges, and apples']) self.assertEqual(format('%L', (['apples', 'oranges', 'watermelon'], 'or')), 'apples, oranges, or watermelon') class RingBufferTestCase(SupyTestCase): def testInit(self): self.assertRaises(ValueError, RingBuffer, -1) self.assertRaises(ValueError, RingBuffer, 0) self.assertEqual(list(range(10)), list(RingBuffer(10, range(10)))) def testLen(self): b = RingBuffer(3) self.assertEqual(0, len(b)) b.append(1) self.assertEqual(1, len(b)) b.append(2) self.assertEqual(2, len(b)) b.append(3) self.assertEqual(3, len(b)) b.append(4) self.assertEqual(3, len(b)) b.append(5) self.assertEqual(3, len(b)) def testNonzero(self): b = RingBuffer(3) self.assertFalse(b) b.append(1) self.assertTrue(b) def testAppend(self): b = RingBuffer(3) self.assertEqual([], list(b)) b.append(1) self.assertEqual([1], list(b)) b.append(2) self.assertEqual([1, 2], list(b)) b.append(3) self.assertEqual([1, 2, 3], list(b)) b.append(4) self.assertEqual([2, 3, 4], list(b)) b.append(5) self.assertEqual([3, 4, 5], list(b)) b.append(6) self.assertEqual([4, 5, 6], list(b)) def testContains(self): b = RingBuffer(3, range(3)) self.assertTrue(0 in b) self.assertTrue(1 in b) self.assertTrue(2 in b) self.assertFalse(3 in b) def testGetitem(self): L = range(10) b = RingBuffer(len(L), L) for i in range(len(b)): self.assertEqual(L[i], b[i]) for i in range(len(b)): self.assertEqual(L[-i], b[-i]) for i in range(len(b)): b.append(i) for i in range(len(b)): self.assertEqual(L[i], b[i]) for i in range(len(b)): self.assertEqual(list(b), list(b[:i]) + list(b[i:])) def testSliceGetitem(self): L = list(range(10)) b = RingBuffer(len(L), L) for i in range(len(b)): self.assertEqual(L[:i], b[:i]) self.assertEqual(L[i:], b[i:]) self.assertEqual(L[i:len(b)-i], b[i:len(b)-i]) self.assertEqual(L[:-i], b[:-i]) self.assertEqual(L[-i:], b[-i:]) self.assertEqual(L[i:-i], b[i:-i]) for i in range(len(b)): b.append(i) for i in range(len(b)): self.assertEqual(L[:i], b[:i]) self.assertEqual(L[i:], b[i:]) self.assertEqual(L[i:len(b)-i], b[i:len(b)-i]) self.assertEqual(L[:-i], b[:-i]) self.assertEqual(L[-i:], b[-i:]) self.assertEqual(L[i:-i], b[i:-i]) def testSetitem(self): L = range(10) b = RingBuffer(len(L), [0]*len(L)) for i in range(len(b)): b[i] = i for i in range(len(b)): self.assertEqual(b[i], i) for i in range(len(b)): b.append(0) for i in range(len(b)): b[i] = i for i in range(len(b)): self.assertEqual(b[i], i) def testSliceSetitem(self): L = list(range(10)) b = RingBuffer(len(L), [0]*len(L)) self.assertRaises(ValueError, b.__setitem__, slice(0, 10), []) b[2:4] = L[2:4] self.assertEqual(b[2:4], L[2:4]) for _ in range(len(b)): b.append(0) b[2:4] = L[2:4] self.assertEqual(b[2:4], L[2:4]) def testExtend(self): b = RingBuffer(3, range(3)) self.assertEqual(list(b), list(range(3))) b.extend(range(6)) self.assertEqual(list(b), list(range(6)[3:])) def testRepr(self): b = RingBuffer(3) self.assertEqual(repr(b), 'RingBuffer(3, [])') b.append(1) self.assertEqual(repr(b), 'RingBuffer(3, [1])') b.append(2) self.assertEqual(repr(b), 'RingBuffer(3, [1, 2])') b.append(3) self.assertEqual(repr(b), 'RingBuffer(3, [1, 2, 3])') b.append(4) self.assertEqual(repr(b), 'RingBuffer(3, [2, 3, 4])') b.append(5) self.assertEqual(repr(b), 'RingBuffer(3, [3, 4, 5])') b.append(6) self.assertEqual(repr(b), 'RingBuffer(3, [4, 5, 6])') def testPickleCopy(self): b = RingBuffer(10, range(10)) self.assertEqual(pickle.loads(pickle.dumps(b)), b) def testEq(self): b = RingBuffer(3, range(3)) self.assertFalse(b == list(range(3))) b1 = RingBuffer(3) self.assertFalse(b == b1) b1.append(0) self.assertFalse(b == b1) b1.append(1) self.assertFalse(b == b1) b1.append(2) self.assertTrue(b == b1) b = RingBuffer(100, range(10)) b1 = RingBuffer(10, range(10)) self.assertFalse(b == b1) def testIter(self): b = RingBuffer(3, range(3)) L = [] for elt in b: L.append(elt) self.assertEqual(L, list(range(3))) for elt in range(3): b.append(elt) del L[:] for elt in b: L.append(elt) self.assertEqual(L, list(range(3))) class QueueTest(SupyTestCase): def testReset(self): q = queue() q.enqueue(1) self.assertEqual(len(q), 1) q.reset() self.assertEqual(len(q), 0) def testGetitem(self): q = queue() n = 10 self.assertRaises(IndexError, q.__getitem__, 0) for i in xrange(n): q.enqueue(i) for i in xrange(n): self.assertEqual(q[i], i) for i in xrange(n, 0, -1): self.assertEqual(q[-i], n-i) for i in xrange(len(q)): self.assertEqual(list(q), list(q[:i]) + list(q[i:])) self.assertRaises(IndexError, q.__getitem__, -(n+1)) self.assertRaises(IndexError, q.__getitem__, n) self.assertEqual(q[3:7], queue([3, 4, 5, 6])) def testSetitem(self): q1 = queue() self.assertRaises(IndexError, q1.__setitem__, 0, 0) for i in xrange(10): q1.enqueue(i) q2 = eval(repr(q1)) for (i, elt) in enumerate(q2): q2[i] = elt*2 self.assertEqual([x*2 for x in q1], list(q2)) def testNonzero(self): q = queue() self.assertFalse(q, 'queue not zero after initialization') q.enqueue(1) self.assertTrue(q, 'queue zero after adding element') q.dequeue() self.assertFalse(q, 'queue not zero after dequeue of only element') def testLen(self): q = queue() self.assertEqual(0, len(q), 'queue len not 0 after initialization') q.enqueue(1) self.assertEqual(1, len(q), 'queue len not 1 after enqueue') q.enqueue(2) self.assertEqual(2, len(q), 'queue len not 2 after enqueue') q.dequeue() self.assertEqual(1, len(q), 'queue len not 1 after dequeue') q.dequeue() self.assertEqual(0, len(q), 'queue len not 0 after dequeue') for i in range(10): L = range(i) q = queue(L) self.assertEqual(len(q), i) def testEq(self): q1 = queue() q2 = queue() self.assertTrue(q1 == q1, 'queue not equal to itself') self.assertTrue(q2 == q2, 'queue not equal to itself') self.assertTrue(q1 == q2, 'initialized queues not equal') q1.enqueue(1) self.assertTrue(q1 == q1, 'queue not equal to itself') self.assertTrue(q2 == q2, 'queue not equal to itself') q2.enqueue(1) self.assertTrue(q1 == q1, 'queue not equal to itself') self.assertTrue(q2 == q2, 'queue not equal to itself') self.assertTrue(q1 == q2, 'queues not equal after identical enqueue') q1.dequeue() self.assertTrue(q1 == q1, 'queue not equal to itself') self.assertTrue(q2 == q2, 'queue not equal to itself') self.assertFalse(q1 == q2, 'queues equal after one dequeue') q2.dequeue() self.assertTrue(q1 == q2, 'queues not equal after both are dequeued') self.assertTrue(q1 == q1, 'queue not equal to itself') self.assertTrue(q2 == q2, 'queue not equal to itself') def testInit(self): self.assertEqual(len(queue()), 0, 'queue len not 0 after init') q = queue() q.enqueue(1) q.enqueue(2) q.enqueue(3) self.assertEqual(queue((1, 2, 3)),q, 'init not equivalent to enqueues') q = queue((1, 2, 3)) self.assertEqual(q.dequeue(), 1, 'values not returned in proper order') self.assertEqual(q.dequeue(), 2, 'values not returned in proper order') self.assertEqual(q.dequeue(), 3, 'values not returned in proper order') def testRepr(self): q = queue() q.enqueue(1) self.assertEqual(q, eval(repr(q)), 'repr doesn\'t eval to same queue') q.enqueue('foo') self.assertEqual(q, eval(repr(q)), 'repr doesn\'t eval to same queue') q.enqueue(None) self.assertEqual(q, eval(repr(q)), 'repr doesn\'t eval to same queue') q.enqueue(1.0) self.assertEqual(q, eval(repr(q)), 'repr doesn\'t eval to same queue') q.enqueue([]) self.assertEqual(q, eval(repr(q)), 'repr doesn\'t eval to same queue') q.enqueue(()) self.assertEqual(q, eval(repr(q)), 'repr doesn\'t eval to same queue') q.enqueue([1]) self.assertEqual(q, eval(repr(q)), 'repr doesn\'t eval to same queue') q.enqueue((1,)) self.assertEqual(q, eval(repr(q)), 'repr doesn\'t eval to same queue') def testEnqueueDequeue(self): q = queue() self.assertRaises(IndexError, q.dequeue) q.enqueue(1) self.assertEqual(q.dequeue(), 1, 'first dequeue didn\'t return same as first enqueue') q.enqueue(1) q.enqueue(2) q.enqueue(3) self.assertEqual(q.dequeue(), 1) self.assertEqual(q.dequeue(), 2) self.assertEqual(q.dequeue(), 3) def testPeek(self): q = queue() self.assertRaises(IndexError, q.peek) q.enqueue(1) self.assertEqual(q.peek(), 1, 'peek didn\'t return first enqueue') q.enqueue(2) self.assertEqual(q.peek(), 1, 'peek didn\'t return first enqueue') q.dequeue() self.assertEqual(q.peek(), 2, 'peek didn\'t return second enqueue') q.dequeue() self.assertRaises(IndexError, q.peek) def testContains(self): q = queue() self.assertFalse(1 in q, 'empty queue cannot have elements') q.enqueue(1) self.assertTrue(1 in q, 'recent enqueued element not in q') q.enqueue(2) self.assertTrue(1 in q, 'original enqueued element not in q') self.assertTrue(2 in q, 'second enqueued element not in q') q.dequeue() self.assertFalse(1 in q, 'dequeued element in q') self.assertTrue(2 in q, 'not dequeued element not in q') q.dequeue() self.assertFalse(2 in q, 'dequeued element in q') def testIter(self): q1 = queue((1, 2, 3)) q2 = queue() for i in q1: q2.enqueue(i) self.assertEqual(q1, q2, 'iterate didn\'t return all elements') for _ in queue(): self.fail('no elements should be in empty queue') def testPickleCopy(self): q = queue(range(10)) self.assertEqual(q, pickle.loads(pickle.dumps(q))) queue = smallqueue class SmallQueueTest(SupyTestCase): def testReset(self): q = queue() q.enqueue(1) self.assertEqual(len(q), 1) q.reset() self.assertEqual(len(q), 0) def testGetitem(self): q = queue() n = 10 self.assertRaises(IndexError, q.__getitem__, 0) for i in xrange(n): q.enqueue(i) for i in xrange(n): self.assertEqual(q[i], i) for i in xrange(n, 0, -1): self.assertEqual(q[-i], n-i) for i in xrange(len(q)): self.assertEqual(list(q), list(q[:i]) + list(q[i:])) self.assertRaises(IndexError, q.__getitem__, -(n+1)) self.assertRaises(IndexError, q.__getitem__, n) self.assertEqual(q[3:7], queue([3, 4, 5, 6])) def testSetitem(self): q1 = queue() self.assertRaises(IndexError, q1.__setitem__, 0, 0) for i in xrange(10): q1.enqueue(i) q2 = eval(repr(q1)) for (i, elt) in enumerate(q2): q2[i] = elt*2 self.assertEqual([x*2 for x in q1], list(q2)) def testNonzero(self): q = queue() self.assertFalse(q, 'queue not zero after initialization') q.enqueue(1) self.assertTrue(q, 'queue zero after adding element') q.dequeue() self.assertFalse(q, 'queue not zero after dequeue of only element') def testLen(self): q = queue() self.assertEqual(0, len(q), 'queue len not 0 after initialization') q.enqueue(1) self.assertEqual(1, len(q), 'queue len not 1 after enqueue') q.enqueue(2) self.assertEqual(2, len(q), 'queue len not 2 after enqueue') q.dequeue() self.assertEqual(1, len(q), 'queue len not 1 after dequeue') q.dequeue() self.assertEqual(0, len(q), 'queue len not 0 after dequeue') for i in range(10): L = range(i) q = queue(L) self.assertEqual(len(q), i) def testEq(self): q1 = queue() q2 = queue() self.assertTrue(q1 == q1, 'queue not equal to itself') self.assertTrue(q2 == q2, 'queue not equal to itself') self.assertTrue(q1 == q2, 'initialized queues not equal') q1.enqueue(1) self.assertTrue(q1 == q1, 'queue not equal to itself') self.assertTrue(q2 == q2, 'queue not equal to itself') q2.enqueue(1) self.assertTrue(q1 == q1, 'queue not equal to itself') self.assertTrue(q2 == q2, 'queue not equal to itself') self.assertTrue(q1 == q2, 'queues not equal after identical enqueue') q1.dequeue() self.assertTrue(q1 == q1, 'queue not equal to itself') self.assertTrue(q2 == q2, 'queue not equal to itself') self.assertFalse(q1 == q2, 'queues equal after one dequeue') q2.dequeue() self.assertTrue(q1 == q2, 'queues not equal after both are dequeued') self.assertTrue(q1 == q1, 'queue not equal to itself') self.assertTrue(q2 == q2, 'queue not equal to itself') def testInit(self): self.assertEqual(len(queue()), 0, 'queue len not 0 after init') q = queue() q.enqueue(1) q.enqueue(2) q.enqueue(3) self.assertEqual(queue((1, 2, 3)),q, 'init not equivalent to enqueues') q = queue((1, 2, 3)) self.assertEqual(q.dequeue(), 1, 'values not returned in proper order') self.assertEqual(q.dequeue(), 2, 'values not returned in proper order') self.assertEqual(q.dequeue(), 3, 'values not returned in proper order') def testRepr(self): q = queue() q.enqueue(1) self.assertEqual(q, eval(repr(q)), 'repr doesn\'t eval to same queue') q.enqueue('foo') self.assertEqual(q, eval(repr(q)), 'repr doesn\'t eval to same queue') q.enqueue(None) self.assertEqual(q, eval(repr(q)), 'repr doesn\'t eval to same queue') q.enqueue(1.0) self.assertEqual(q, eval(repr(q)), 'repr doesn\'t eval to same queue') q.enqueue([]) self.assertEqual(q, eval(repr(q)), 'repr doesn\'t eval to same queue') q.enqueue(()) self.assertEqual(q, eval(repr(q)), 'repr doesn\'t eval to same queue') q.enqueue([1]) self.assertEqual(q, eval(repr(q)), 'repr doesn\'t eval to same queue') q.enqueue((1,)) self.assertEqual(q, eval(repr(q)), 'repr doesn\'t eval to same queue') def testEnqueueDequeue(self): q = queue() self.assertRaises(IndexError, q.dequeue) q.enqueue(1) self.assertEqual(q.dequeue(), 1, 'first dequeue didn\'t return same as first enqueue') q.enqueue(1) q.enqueue(2) q.enqueue(3) self.assertEqual(q.dequeue(), 1) self.assertEqual(q.dequeue(), 2) self.assertEqual(q.dequeue(), 3) def testPeek(self): q = queue() self.assertRaises(IndexError, q.peek) q.enqueue(1) self.assertEqual(q.peek(), 1, 'peek didn\'t return first enqueue') q.enqueue(2) self.assertEqual(q.peek(), 1, 'peek didn\'t return first enqueue') q.dequeue() self.assertEqual(q.peek(), 2, 'peek didn\'t return second enqueue') q.dequeue() self.assertRaises(IndexError, q.peek) def testContains(self): q = queue() self.assertFalse(1 in q, 'empty queue cannot have elements') q.enqueue(1) self.assertTrue(1 in q, 'recent enqueued element not in q') q.enqueue(2) self.assertTrue(1 in q, 'original enqueued element not in q') self.assertTrue(2 in q, 'second enqueued element not in q') q.dequeue() self.assertFalse(1 in q, 'dequeued element in q') self.assertTrue(2 in q, 'not dequeued element not in q') q.dequeue() self.assertFalse(2 in q, 'dequeued element in q') def testIter(self): q1 = queue((1, 2, 3)) q2 = queue() for i in q1: q2.enqueue(i) self.assertEqual(q1, q2, 'iterate didn\'t return all elements') for _ in queue(): self.fail('no elements should be in empty queue') def testPickleCopy(self): q = queue(range(10)) self.assertEqual(q, pickle.loads(pickle.dumps(q))) class MaxLengthQueueTestCase(SupyTestCase): def testInit(self): q = MaxLengthQueue(3, (1, 2, 3)) self.assertEqual(list(q), [1, 2, 3]) self.assertRaises(TypeError, MaxLengthQueue, 3, 1, 2, 3) def testMaxLength(self): q = MaxLengthQueue(3) q.enqueue(1) self.assertEqual(len(q), 1) q.enqueue(2) self.assertEqual(len(q), 2) q.enqueue(3) self.assertEqual(len(q), 3) q.enqueue(4) self.assertEqual(len(q), 3) self.assertEqual(q.peek(), 2) q.enqueue(5) self.assertEqual(len(q), 3) self.assertEqual(q[0], 3) class TwoWayDictionaryTestCase(SupyTestCase): def testInit(self): d = TwoWayDictionary(foo='bar') self.assertTrue('foo' in d) self.assertTrue('bar' in d) d = TwoWayDictionary({1: 2}) self.assertTrue(1 in d) self.assertTrue(2 in d) def testSetitem(self): d = TwoWayDictionary() d['foo'] = 'bar' self.assertTrue('foo' in d) self.assertTrue('bar' in d) def testDelitem(self): d = TwoWayDictionary(foo='bar') del d['foo'] self.assertFalse('foo' in d) self.assertFalse('bar' in d) d = TwoWayDictionary(foo='bar') del d['bar'] self.assertFalse('bar' in d) self.assertFalse('foo' in d) class TestTimeoutQueue(SupyTestCase): def test(self): q = TimeoutQueue(1) q.enqueue(1) self.assertEqual(len(q), 1) q.enqueue(2) self.assertEqual(len(q), 2) q.enqueue(3) self.assertEqual(len(q), 3) self.assertEqual(sum(q), 6) timeFastForward(1.1) self.assertEqual(len(q), 0) self.assertEqual(sum(q), 0) def testCallableTimeout(self): q = TimeoutQueue(lambda : 1) q.enqueue(1) self.assertEqual(len(q), 1) q.enqueue(2) self.assertEqual(len(q), 2) q.enqueue(3) self.assertEqual(len(q), 3) self.assertEqual(sum(q), 6) timeFastForward(1.1) self.assertEqual(len(q), 0) self.assertEqual(sum(q), 0) def testContains(self): q = TimeoutQueue(1) q.enqueue(1) self.assertTrue(1 in q) self.assertTrue(1 in q) # For some reason, the second one might fail. self.assertFalse(2 in q) timeFastForward(1.1) self.assertFalse(1 in q) def testReset(self): q = TimeoutQueue(10) q.enqueue(1) self.assertTrue(1 in q) q.reset() self.assertFalse(1 in q) class TestCacheDict(SupyTestCase): def testMaxNeverExceeded(self): max = 10 d = CacheDict(10) for i in xrange(max**2): d[i] = i self.assertTrue(len(d) <= max) self.assertTrue(i in d) self.assertTrue(d[i] == i) class TestTruncatableSet(SupyTestCase): def testBasics(self): s = TruncatableSet(['foo', 'bar', 'baz', 'qux']) self.assertEqual(s, set(['foo', 'bar', 'baz', 'qux'])) self.assertTrue('foo' in s) self.assertTrue('bar' in s) self.assertFalse('quux' in s) s.discard('baz') self.assertTrue('foo' in s) self.assertFalse('baz' in s) s.add('quux') self.assertTrue('quux' in s) def testTruncate(self): s = TruncatableSet(['foo', 'bar']) s.add('baz') s.add('qux') s.truncate(3) self.assertEqual(s, set(['bar', 'baz', 'qux'])) def testTruncateUnion(self): s = TruncatableSet(['bar', 'foo']) s |= set(['baz', 'qux']) s.truncate(3) self.assertEqual(s, set(['foo', 'baz', 'qux'])) class UtilsPythonTest(SupyTestCase): def test_dict(self): class Foo: def __hasattr__(self, n): raise Exception(n) def __getattr__(self, n): raise Exception(n) def f(): self = Foo() self.bar = 'baz' raise Exception('f') try: f() except: res = utils.python.collect_extra_debug_data() self.assertTrue(re.search('self.bar.*=.*baz', res), res) def test_slots(self): class Foo: __slots__ = ('bar',) def __hasattr__(self, n): raise Exception(n) def __getattr__(self, n): raise Exception(n) def f(): self = Foo() self.bar = 'baz' raise Exception('f') try: f() except: res = utils.python.collect_extra_debug_data() # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: limnoria-2020.03.17/test/test_yn.py0000644000175000017500000000746613634634533016353 0ustar valval00000000000000### # Copyright (c) 2014, Artur Krysiak # 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 sys import unittest from supybot import questions from supybot.test import SupyTestCase if sys.version_info >= (2, 7, 0): skipif = unittest.skipIf else: skipif = lambda x, y: lambda z:None try: from unittest import mock # Python 3.3+ except ImportError: try: import mock # Everything else, an external 'mock' library except ImportError: mock = None # so complicated construction because I want to # gain the string 'y' instead of the character 'y' # the reason of usage this construction is to prove # that comparing strings by 'is' is wrong # better solution is usage of '==' operator ;) _yes_answer = ''.join(['', 'y']) @skipif(mock is None, 'python-mock is not installed.') class TestYn(SupyTestCase): def test_default_yes_selected(self): questions.expect = mock.Mock(return_value=_yes_answer) answer = questions.yn('up', default='y') self.assertTrue(answer) def test_default_no_selected(self): questions.expect = mock.Mock(return_value='n') answer = questions.yn('up', default='n') self.assertFalse(answer) def test_yes_selected_without_defaults(self): questions.expect = mock.Mock(return_value=_yes_answer) answer = questions.yn('up') self.assertTrue(answer) def test_no_selected_without_defaults(self): questions.expect = mock.Mock(return_value='n') answer = questions.yn('up') self.assertFalse(answer) def test_no_selected_with_default_yes(self): questions.expect = mock.Mock(return_value='n') answer = questions.yn('up', default='y') self.assertFalse(answer) def test_yes_selected_with_default_yes(self): questions.expect = mock.Mock(return_value=_yes_answer) answer = questions.yn('up', default='y') self.assertTrue(answer) def test_yes_selected_with_default_no(self): questions.expect = mock.Mock(return_value=_yes_answer) answer = questions.yn('up', default='n') self.assertTrue(answer) def test_no_selected_with_default_no(self): questions.expect = mock.Mock(return_value='n') answer = questions.yn('up', default='n') self.assertFalse(answer) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: