mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-12-12 05:09:45 +01:00
Compare commits
14 Commits
225c249ec2
...
1952dae6f7
Author | SHA1 | Date | |
---|---|---|---|
1952dae6f7 | |||
|
a7216d290f | ||
|
649048443e | ||
|
65ab65cbb1 | ||
|
a7c4c9bd78 | ||
|
936d7ebfea | ||
|
d919e2133d | ||
|
3b25a94b46 | ||
|
2293d1c129 | ||
|
67a39a3adb | ||
|
4b82934131 | ||
|
c8053dad54 | ||
|
69c948bd5f | ||
|
8a52902727 |
@ -1,276 +0,0 @@
|
|||||||
# Spanish translation for limnoria
|
|
||||||
# Copyright (c) 2015 Limnoria Contributors 2015
|
|
||||||
# This file is distributed under the same license as the Limnoria package.
|
|
||||||
# Aaron Farias <timido@ubuntu.com>, 2015.
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: limnoria\n"
|
|
||||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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 <Unknown>\n"
|
|
||||||
"Language-Team: Spanish <es@li.org>\n"
|
|
||||||
"Language: es_ES\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 ""
|
|
||||||
"<channel> [<key>]\n"
|
|
||||||
"\n"
|
|
||||||
" Tell the bot to join the given channel. If <key> is given, it is "
|
|
||||||
"used\n"
|
|
||||||
" when attempting to join the channel.\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"<Canal> [<key>]\n"
|
|
||||||
"\n"
|
|
||||||
"Dígale al bot para unirse al canal dado. Si <key> 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 ""
|
|
||||||
"[<nick>] [<network>]\n"
|
|
||||||
"\n"
|
|
||||||
" Changes the bot's nick to <nick>. If no nick is given, returns the\n"
|
|
||||||
" bot's current nick.\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"[<nick>] [<red>]\n"
|
|
||||||
"\n"
|
|
||||||
"Cambia el nick del bot a <nick>. Si no se da nick, devuelve el\n"
|
|
||||||
"actual nick del bot.\n"
|
|
||||||
" "
|
|
||||||
|
|
||||||
#: plugin.py:237
|
|
||||||
#, docstring
|
|
||||||
msgid ""
|
|
||||||
"[<channel>] [<reason>]\n"
|
|
||||||
"\n"
|
|
||||||
" Tells the bot to part the list of channels you give it. <channel> "
|
|
||||||
"is\n"
|
|
||||||
" only necessary if you want the bot to part a channel other than the\n"
|
|
||||||
" current channel. If <reason> is specified, use it as the part\n"
|
|
||||||
" message.\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"[<canal>] [<razón>]\n"
|
|
||||||
"\n"
|
|
||||||
"Le dice al bot a parte de la lista de canales que le des. <canal> es\n"
|
|
||||||
"sólo es necesario si desea que el bot a desprenderse de un canal que no sea "
|
|
||||||
"el\n"
|
|
||||||
"canal actual. Si <razón> 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 ""
|
|
||||||
"<name|hostmask> <capability>\n"
|
|
||||||
"\n"
|
|
||||||
" Gives the user specified by <name> (or the user to whom "
|
|
||||||
"<hostmask>\n"
|
|
||||||
" currently maps) the specified capability <capability>\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"<nombre | hostmask> <capacidad>\n"
|
|
||||||
"\n"
|
|
||||||
"Le da al usuario especificado por <nombre> (o el usuario a quien <hostmask>\n"
|
|
||||||
"Actualmente los mapas) la capacidad especificada <capacidad>\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 ""
|
|
||||||
"<name|hostmask> <capability>\n"
|
|
||||||
"\n"
|
|
||||||
" Takes from the user specified by <name> (or the user to whom\n"
|
|
||||||
" <hostmask> currently maps) the specified capability "
|
|
||||||
"<capability>\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"<Nombre | hostmask> <capacidad>\n"
|
|
||||||
"\n"
|
|
||||||
"Toma del usuario especificado por <nombre> (o el usuario a quien\n"
|
|
||||||
"<Hostmask> Actualmente los mapas) la capacidad especificada <capacidad>\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 ""
|
|
||||||
"<hostmask|nick> [<expires>]\n"
|
|
||||||
"\n"
|
|
||||||
" This will set a persistent ignore on <hostmask> or the hostmask\n"
|
|
||||||
" currently associated with <nick>. <expires> 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 ""
|
|
||||||
"<Hostmask | nick> [<expira>]\n"
|
|
||||||
"\n"
|
|
||||||
"Esto establecerá una persistente ignoran en <hostmask> o la hostmask\n"
|
|
||||||
"actualmente asociados con <nick>. <Expira> 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 ""
|
|
||||||
"<hostmask|nick>\n"
|
|
||||||
"\n"
|
|
||||||
" This will remove the persistent ignore on <hostmask> or the\n"
|
|
||||||
" hostmask currently associated with <nick>.\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"<Hostmask | nick>\n"
|
|
||||||
"\n"
|
|
||||||
"Esto eliminará la persistente ignoran en <hostmask> o la\n"
|
|
||||||
"hostmask actualmente asociada con <nick>.\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"
|
|
||||||
" "
|
|
@ -1,151 +0,0 @@
|
|||||||
# Spanish translation for limnoria
|
|
||||||
# Copyright (c) 2015 Limnoria contributors 2015
|
|
||||||
# This file is distributed under the same license as the Limnoria package.
|
|
||||||
# Aaron Farias <timido@ubuntu.com>, 2015.
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: limnoria\n"
|
|
||||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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 <Unknown>\n"
|
|
||||||
"Language-Team: Spanish <es@li.org>\n"
|
|
||||||
"Language: es_ES\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: a<number of escaped chars>a(<index>d)+<word without dots>."
|
|
||||||
msgstr ""
|
|
||||||
"Codifica [a-z0-9.] + En [az] [a-z0-9].\n"
|
|
||||||
"Formato: un <número de caracteres escapados> a (<índice> d) + <palabra sin "
|
|
||||||
"puntos>."
|
|
||||||
|
|
||||||
#: plugin.py:221
|
|
||||||
msgid " at least"
|
|
||||||
msgstr " al menos"
|
|
||||||
|
|
||||||
#: plugin.py:223 plugin.py:228
|
|
||||||
msgid ""
|
|
||||||
"<an alias,%s %n>\n"
|
|
||||||
"\n"
|
|
||||||
"Alias for %q."
|
|
||||||
msgstr ""
|
|
||||||
"<Un alias,%s %n>\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 ""
|
|
||||||
"<alias>\n"
|
|
||||||
"\n"
|
|
||||||
" Locks an alias so that no one else can change it.\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"<Alias>\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 ""
|
|
||||||
"<alias>\n"
|
|
||||||
"\n"
|
|
||||||
" Unlocks an alias so that people can define new aliases over it.\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"<Alias>\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 ""
|
|
||||||
"<name> <command>\n"
|
|
||||||
"\n"
|
|
||||||
" Defines an alias <name> that executes <command>. The <command>\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 ""
|
|
||||||
"<Nombre> <comando>\n"
|
|
||||||
"\n"
|
|
||||||
"Define un alias <nombre> que ejecuta <command>. El <comando>\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 ""
|
|
||||||
"<name>\n"
|
|
||||||
"\n"
|
|
||||||
" Removes the given alias, if unlocked.\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"<Nombre>\n"
|
|
||||||
"\n"
|
|
||||||
"Elimina los alias dados, si desbloqueado.\n"
|
|
||||||
" "
|
|
@ -1,156 +0,0 @@
|
|||||||
# Spanish translation for limnoria
|
|
||||||
# Copyright (c) 2015 Limnoria contributors 2015
|
|
||||||
# This file is distributed under the same license as the Limnoria package.
|
|
||||||
# Aaron Farias <timido@ubuntu.com>, 2015.
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: limnoria\n"
|
|
||||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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 <Unknown>\n"
|
|
||||||
"Language-Team: Spanish <es@li.org>\n"
|
|
||||||
"Language: es_ES\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 ""
|
|
||||||
"<channel> <text>\n"
|
|
||||||
"\n"
|
|
||||||
" Sends <text> to <channel>.\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"<Canal> <texto>\n"
|
|
||||||
"\n"
|
|
||||||
"Envía <texto> al <canal>.\n"
|
|
||||||
" "
|
|
||||||
|
|
||||||
#: plugin.py:92
|
|
||||||
#, docstring
|
|
||||||
msgid ""
|
|
||||||
"<nick> <text>\n"
|
|
||||||
"\n"
|
|
||||||
" Sends <text> to <nick>. Can only be used if\n"
|
|
||||||
" supybot.plugins.Anonymous.allowPrivateTarget is True.\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"<Nick> <texto>\n"
|
|
||||||
"\n"
|
|
||||||
"Envía <texto> a <nick>. Sólo puede utilizarse si\n"
|
|
||||||
"supybot.plugins.Anonymous.allowPrivateTarget es True.\n"
|
|
||||||
" "
|
|
||||||
|
|
||||||
#: plugin.py:106
|
|
||||||
#, docstring
|
|
||||||
msgid ""
|
|
||||||
"<channel> <action>\n"
|
|
||||||
"\n"
|
|
||||||
" Performs <action> in <channel>.\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"<Canal> <acción>\n"
|
|
||||||
"\n"
|
|
||||||
"Realiza <action> en <channel>.\n"
|
|
||||||
" "
|
|
@ -1,137 +0,0 @@
|
|||||||
# Spanish translation for Limnoria
|
|
||||||
# Copyright (c) 2015 Limnoria 2015
|
|
||||||
# This file is distributed under the same license as the Limnoria package.
|
|
||||||
# Aaron Farias <timido@ubuntu.com>, 2015.
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: limnoria\n"
|
|
||||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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 <Unknown>\n"
|
|
||||||
"Language-Team: Spanish <es@li.org>\n"
|
|
||||||
"Language: es_ES\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 <channel>,op capability when they join the channel.\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"Determina si el bot automáticamente\n"
|
|
||||||
"dar op con el <channel>, capacidad de op cuando se unen al canal.\n"
|
|
||||||
" "
|
|
||||||
|
|
||||||
#: config.py:64
|
|
||||||
msgid ""
|
|
||||||
"Determines whether the bot will automatically\n"
|
|
||||||
" halfop people with the <channel>,halfop capability when they join the\n"
|
|
||||||
" channel."
|
|
||||||
msgstr ""
|
|
||||||
"Determina si el bot automáticamente\n"
|
|
||||||
"personas HALFOP con el <channel>, capacidad halfop cuando se unen a la\n"
|
|
||||||
"canal."
|
|
||||||
|
|
||||||
#: config.py:68
|
|
||||||
msgid ""
|
|
||||||
"Determines whether the bot will automatically\n"
|
|
||||||
" voice people with the <channel>,voice capability when they join the\n"
|
|
||||||
" channel."
|
|
||||||
msgstr ""
|
|
||||||
"Determina si el bot automáticamente\n"
|
|
||||||
"gente de voz con el <channel>, 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."
|
|
@ -121,8 +121,9 @@ class Autocomplete(callbacks.Plugin):
|
|||||||
"""Provides command completion for IRC clients that support it."""
|
"""Provides command completion for IRC clients that support it."""
|
||||||
|
|
||||||
def _enabled(self, irc, msg):
|
def _enabled(self, irc, msg):
|
||||||
return conf.supybot.protocols.irc.experimentalExtensions() and self.registryValue(
|
return (
|
||||||
"enabled", msg.channel, irc.network
|
conf.supybot.protocols.irc.experimentalExtensions()
|
||||||
|
and self.registryValue("enabled", msg.channel, irc.network)
|
||||||
)
|
)
|
||||||
|
|
||||||
def doTagmsg(self, irc, msg):
|
def doTagmsg(self, irc, msg):
|
||||||
|
@ -1,199 +0,0 @@
|
|||||||
# Spanish translation for Limnoria
|
|
||||||
# Copyright (c) 2015 Limnoria Contributors 2015
|
|
||||||
# This file is distributed under the same license as the Limnoria package.
|
|
||||||
# Aaron Farias <timido@ubuntu.com>, 2015.
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: limnoria\n"
|
|
||||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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 <Unknown>\n"
|
|
||||||
"Language-Team: Spanish <es@li.org>\n"
|
|
||||||
"Language: es_ES\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 ""
|
|
||||||
"<word> [<word> ...]\n"
|
|
||||||
"\n"
|
|
||||||
" Adds all <word>s to the list of words being censored.\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"<Palabra> [<palabra> ...]\n"
|
|
||||||
"\n"
|
|
||||||
"Añade todos <palabra> s a la lista de palabras que se censuró.\n"
|
|
||||||
" "
|
|
||||||
|
|
||||||
#: plugin.py:142
|
|
||||||
#, docstring
|
|
||||||
msgid ""
|
|
||||||
"<word> [<word> ...]\n"
|
|
||||||
"\n"
|
|
||||||
" Removes <word>s from the list of words being censored.\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"<Palabra> [<palabra> ...]\n"
|
|
||||||
"\n"
|
|
||||||
"Elimina <palabra> s de la lista de palabras que se censuró.\n"
|
|
||||||
" "
|
|
@ -18,6 +18,8 @@ Basically, it replaces the standard 'Error: <x> is not a valid command.'
|
|||||||
messages with messages kept in a database, able to give more personable
|
messages with messages kept in a database, able to give more personable
|
||||||
responses.
|
responses.
|
||||||
|
|
||||||
|
``$command`` in the message will be replaced by the command's name.
|
||||||
|
|
||||||
.. _commands-Dunno:
|
.. _commands-Dunno:
|
||||||
|
|
||||||
Commands
|
Commands
|
||||||
|
@ -1,288 +0,0 @@
|
|||||||
# Spanish translation for Limnoria
|
|
||||||
# Copyright (c) 2015 Limnoria 2015
|
|
||||||
# This file is distributed under the same license as the Limnoria package.
|
|
||||||
# Aaron Farias <timido@ubuntu.com>, 2015.
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: limnoria\n"
|
|
||||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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 <Unknown>\n"
|
|
||||||
"Language-Team: Spanish <es@li.org>\n"
|
|
||||||
"Language: es_ES\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] <search>\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] <Búsqueda>\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 ""
|
|
||||||
"<search> [--{filter,language} <value>]\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 ""
|
|
||||||
"<Búsqueda> [- {filtro, idioma} <valor>]\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 ""
|
|
||||||
"<url>\n"
|
|
||||||
"\n"
|
|
||||||
" Returns a link to the cached version of <url> if it is available.\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"<url>\n"
|
|
||||||
"\n"
|
|
||||||
"Devuelve un enlace a la versión en caché de <url> 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 ""
|
|
||||||
"<search string> <search string> [<search string> ...]\n"
|
|
||||||
"\n"
|
|
||||||
" Returns the results of each search, in order, from greatest number\n"
|
|
||||||
" of results to least.\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"<Concepto de búsqueda> <Concepto de búsqueda> [<cadena de búsqueda> ...]\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 ""
|
|
||||||
"<source language> [to] <target language> <text>\n"
|
|
||||||
"\n"
|
|
||||||
" Returns <text> translated from <source language> into <target\n"
|
|
||||||
" language>.\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"<idioma fuente> [a] <idioma de destino> <texto>\n"
|
|
||||||
"\n"
|
|
||||||
"Devuelve <texto> traducidos de <idioma fuente> a <destino\n"
|
|
||||||
"idioma>.\n"
|
|
||||||
" "
|
|
||||||
|
|
||||||
#: plugin.py:285
|
|
||||||
#, docstring
|
|
||||||
msgid "^google\\s+(.*)$"
|
|
||||||
msgstr "^google\\s+(.*)$"
|
|
||||||
|
|
||||||
#: plugin.py:306
|
|
||||||
#, docstring
|
|
||||||
msgid ""
|
|
||||||
"<expression>\n"
|
|
||||||
"\n"
|
|
||||||
" Uses Google's calculator to calculate the value of <expression>.\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"<expresión>\n"
|
|
||||||
"\n"
|
|
||||||
"Utiliza la calculadora de Google para calcular el valor de <expresión>.\n"
|
|
||||||
" "
|
|
||||||
|
|
||||||
#: plugin.py:341
|
|
||||||
#, docstring
|
|
||||||
msgid ""
|
|
||||||
"<phone number>\n"
|
|
||||||
"\n"
|
|
||||||
" Looks <phone number> up on Google.\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
"<número de teléfono>\n"
|
|
||||||
"\n"
|
|
||||||
"Mirar <número de teléfono> 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."
|
|
91
plugins/Poll/README.rst
Normal file
91
plugins/Poll/README.rst
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
.. _plugin-Poll:
|
||||||
|
|
||||||
|
Documentation for the Poll plugin for Supybot
|
||||||
|
=============================================
|
||||||
|
|
||||||
|
Purpose
|
||||||
|
-------
|
||||||
|
Poll: Provides a simple way to vote on answers to a question
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
Provides a simple way to vote on answers to a question
|
||||||
|
|
||||||
|
For example, this creates a poll::
|
||||||
|
|
||||||
|
<admin> @poll add "Is this a test?" "Yes" "No" "Maybe"
|
||||||
|
<bot> The operation succeeded. Poll # 42 created.
|
||||||
|
|
||||||
|
Creates a poll that can be voted on in this way::
|
||||||
|
|
||||||
|
<citizen1> @vote 42 Yes
|
||||||
|
<citizen2> @vote 42 No
|
||||||
|
<citizen3> @vote 42 No
|
||||||
|
|
||||||
|
And results::
|
||||||
|
|
||||||
|
<admin> @poll results
|
||||||
|
<bot> 2 votes for No, 1 vote for Yes, and 0 votes for Maybe",
|
||||||
|
|
||||||
|
Longer answers are possible, and voters only need to use the first
|
||||||
|
word of each answer to vote. For example, this creates a poll that
|
||||||
|
can be voted on in the same way::
|
||||||
|
|
||||||
|
<admin> @poll add "Is this a test?" "Yes totally" "No no no" "Maybe"
|
||||||
|
<bot> The operation succeeded. Poll # 43 created.
|
||||||
|
|
||||||
|
You can also add a number or letter at the beginning of each question to
|
||||||
|
make it easier::
|
||||||
|
|
||||||
|
<admin> @poll add "Who is the best captain?" "1 James T Kirk" "2 Jean-Luc Picard" "3 Benjamin Sisko" "4 Kathryn Janeway"
|
||||||
|
<bot> The operation succeeded. Poll # 44 created.
|
||||||
|
|
||||||
|
<trekkie1> @vote 42 1
|
||||||
|
<trekkie2> @vote 42 4
|
||||||
|
<trekkie3> @vote 42 4
|
||||||
|
|
||||||
|
.. _commands-Poll:
|
||||||
|
|
||||||
|
Commands
|
||||||
|
--------
|
||||||
|
.. _command-poll-add:
|
||||||
|
|
||||||
|
add [<channel>] <question> <answer1> [<answer2> [<answer3> [...]]]
|
||||||
|
Creates a new poll with the specified <question> and answers on the <channel>. The first word of each answer is used as its id to vote, so each answer should start with a different word. <channel> is only necessary if this command is run in private, and defaults to the current channel otherwise.
|
||||||
|
|
||||||
|
.. _command-poll-close:
|
||||||
|
|
||||||
|
close [<channel>] <poll_id>
|
||||||
|
Closes the specified poll.
|
||||||
|
|
||||||
|
.. _command-poll-results:
|
||||||
|
|
||||||
|
results [<channel>] <poll_id>
|
||||||
|
Returns the results of the specified poll.
|
||||||
|
|
||||||
|
.. _command-poll-vote:
|
||||||
|
|
||||||
|
vote [<channel>] <poll_id> <answer_id>
|
||||||
|
Registers your vote on the poll <poll_id> as being the answer identified by <answer_id> (which is the first word of each possible answer).
|
||||||
|
|
||||||
|
.. _conf-Poll:
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. _conf-supybot.plugins.Poll.public:
|
||||||
|
|
||||||
|
|
||||||
|
supybot.plugins.Poll.public
|
||||||
|
This config variable defaults to "True", is not network-specific, and is not channel-specific.
|
||||||
|
|
||||||
|
Determines whether this plugin is publicly visible.
|
||||||
|
|
||||||
|
.. _conf-supybot.plugins.Poll.requireManageCapability:
|
||||||
|
|
||||||
|
|
||||||
|
supybot.plugins.Poll.requireManageCapability
|
||||||
|
This config variable defaults to "channel,op; channel,halfop", is network-specific, and is channel-specific.
|
||||||
|
|
||||||
|
Determines the capabilities required (if any) to open and close polls. Use 'channel,capab' for channel-level capabilities. Note that absence of an explicit anticapability means user has capability.
|
||||||
|
|
72
plugins/Poll/__init__.py
Normal file
72
plugins/Poll/__init__.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
###
|
||||||
|
# Copyright (c) 2021, 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.
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
"""
|
||||||
|
Poll: Provides a simple way to vote on answers to a question
|
||||||
|
"""
|
||||||
|
|
||||||
|
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:
|
72
plugins/Poll/config.py
Normal file
72
plugins/Poll/config.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
###
|
||||||
|
# Copyright (c) 2021, 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 import conf, registry
|
||||||
|
|
||||||
|
try:
|
||||||
|
from supybot.i18n import PluginInternationalization
|
||||||
|
|
||||||
|
_ = PluginInternationalization("Poll")
|
||||||
|
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("Poll", True)
|
||||||
|
|
||||||
|
|
||||||
|
Poll = conf.registerPlugin("Poll")
|
||||||
|
# This is where your configuration variables (if any) should go. For example:
|
||||||
|
# conf.registerGlobalValue(Poll, 'someConfigVariableName',
|
||||||
|
# registry.Boolean(False, _("""Help for someConfigVariableName.""")))
|
||||||
|
|
||||||
|
conf.registerChannelValue(
|
||||||
|
Poll,
|
||||||
|
"requireManageCapability",
|
||||||
|
registry.String(
|
||||||
|
"channel,op; channel,halfop",
|
||||||
|
_(
|
||||||
|
"""Determines the capabilities required (if any) to open and
|
||||||
|
close polls.
|
||||||
|
Use 'channel,capab' for channel-level capabilities.
|
||||||
|
Note that absence of an explicit anticapability means user has
|
||||||
|
capability.
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
1
plugins/Poll/local/__init__.py
Normal file
1
plugins/Poll/local/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Stub so local is a module, used for third-party modules
|
225
plugins/Poll/plugin.py
Normal file
225
plugins/Poll/plugin.py
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
###
|
||||||
|
# Copyright (c) 2021, 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 collections
|
||||||
|
import re
|
||||||
|
|
||||||
|
from supybot import utils, plugins, ircdb, ircutils, callbacks
|
||||||
|
from supybot.commands import *
|
||||||
|
|
||||||
|
try:
|
||||||
|
from supybot.i18n import PluginInternationalization
|
||||||
|
|
||||||
|
_ = PluginInternationalization("Poll")
|
||||||
|
except ImportError:
|
||||||
|
# Placeholder that allows to run the plugin on a bot
|
||||||
|
# without the i18n module
|
||||||
|
_ = lambda x: x
|
||||||
|
|
||||||
|
|
||||||
|
Poll = collections.namedtuple("Poll", "question answers votes open")
|
||||||
|
|
||||||
|
|
||||||
|
class Poll_(callbacks.Plugin):
|
||||||
|
"""Provides a simple way to vote on answers to a question
|
||||||
|
|
||||||
|
For example, this creates a poll::
|
||||||
|
|
||||||
|
<admin> @poll add "Is this a test?" "Yes" "No" "Maybe"
|
||||||
|
<bot> The operation succeeded. Poll # 42 created.
|
||||||
|
|
||||||
|
Creates a poll that can be voted on in this way::
|
||||||
|
|
||||||
|
<citizen1> @vote 42 Yes
|
||||||
|
<citizen2> @vote 42 No
|
||||||
|
<citizen3> @vote 42 No
|
||||||
|
|
||||||
|
And results::
|
||||||
|
|
||||||
|
<admin> @poll results
|
||||||
|
<bot> 2 votes for No, 1 vote for Yes, and 0 votes for Maybe",
|
||||||
|
|
||||||
|
Longer answers are possible, and voters only need to use the first
|
||||||
|
word of each answer to vote. For example, this creates a poll that
|
||||||
|
can be voted on in the same way::
|
||||||
|
|
||||||
|
<admin> @poll add "Is this a test?" "Yes totally" "No no no" "Maybe"
|
||||||
|
<bot> The operation succeeded. Poll # 43 created.
|
||||||
|
|
||||||
|
You can also add a number or letter at the beginning of each question to
|
||||||
|
make it easier::
|
||||||
|
|
||||||
|
<admin> @poll add "Who is the best captain?" "1 James T Kirk" "2 Jean-Luc Picard" "3 Benjamin Sisko" "4 Kathryn Janeway"
|
||||||
|
<bot> The operation succeeded. Poll # 44 created.
|
||||||
|
|
||||||
|
<trekkie1> @vote 42 1
|
||||||
|
<trekkie2> @vote 42 4
|
||||||
|
<trekkie3> @vote 42 4
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, irc):
|
||||||
|
super().__init__(irc)
|
||||||
|
|
||||||
|
# {(network, channel): {id: Poll}}
|
||||||
|
self._polls = collections.defaultdict(dict)
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
return "Poll"
|
||||||
|
|
||||||
|
def _checkManageCapability(self, irc, msg, channel):
|
||||||
|
# Copy-pasted from Topic
|
||||||
|
capabilities = self.registryValue(
|
||||||
|
"requireManageCapability", channel, irc.network
|
||||||
|
)
|
||||||
|
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
|
||||||
|
irc.errorNoCapability(capabilities, Raise=True)
|
||||||
|
|
||||||
|
def _getPoll(self, irc, channel, poll_id):
|
||||||
|
poll = self._polls[(irc.network, channel)].get(poll_id)
|
||||||
|
if poll is None:
|
||||||
|
irc.error(
|
||||||
|
_("A poll with this ID does not exist in this channel."),
|
||||||
|
Raise=True,
|
||||||
|
)
|
||||||
|
return poll
|
||||||
|
|
||||||
|
@wrap(["channel", "something", many("something")])
|
||||||
|
def add(self, irc, msg, args, channel, question, answers):
|
||||||
|
"""[<channel>] <question> <answer1> [<answer2> [<answer3> [...]]]
|
||||||
|
|
||||||
|
Creates a new poll with the specified <question> and answers
|
||||||
|
on the <channel>.
|
||||||
|
The first word of each answer is used as its id to vote,
|
||||||
|
so each answer should start with a different word.
|
||||||
|
|
||||||
|
<channel> is only necessary if this command is run in private,
|
||||||
|
and defaults to the current channel otherwise."""
|
||||||
|
self._checkManageCapability(irc, msg, channel)
|
||||||
|
|
||||||
|
poll_id = max(self._polls[(irc.network, channel)], default=0) + 1
|
||||||
|
|
||||||
|
answers = [(answer.split()[0], answer) for answer in answers]
|
||||||
|
|
||||||
|
answer_id_counts = collections.Counter(id_ for (id_, _) in answers).items()
|
||||||
|
duplicate_answer_ids = [
|
||||||
|
answer_id for (answer_id, count) in answer_id_counts if count > 1
|
||||||
|
]
|
||||||
|
if duplicate_answer_ids:
|
||||||
|
irc.error(
|
||||||
|
format(
|
||||||
|
_("Duplicate answer identifier(s): %L"), duplicate_answer_ids
|
||||||
|
),
|
||||||
|
Raise=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._polls[(irc.network, channel)][poll_id] = Poll(
|
||||||
|
question=question, answers=dict(answers), votes={}, open=True
|
||||||
|
)
|
||||||
|
|
||||||
|
irc.replySuccess(_("Poll # %d created.") % poll_id)
|
||||||
|
|
||||||
|
@wrap(["channel", "positiveInt"])
|
||||||
|
def close(self, irc, msg, args, channel, poll_id):
|
||||||
|
"""[<channel>] <poll_id>
|
||||||
|
|
||||||
|
Closes the specified poll."""
|
||||||
|
self._checkManageCapability(irc, msg, channel)
|
||||||
|
|
||||||
|
poll = self._getPoll(irc, channel, poll_id)
|
||||||
|
|
||||||
|
if not poll.open:
|
||||||
|
irc.error(_("This poll was already closed."), Raise=True)
|
||||||
|
|
||||||
|
poll = Poll(
|
||||||
|
question=poll.question,
|
||||||
|
answers=poll.answers,
|
||||||
|
votes=poll.votes,
|
||||||
|
open=False,
|
||||||
|
)
|
||||||
|
self._polls[(irc.network, channel)][poll_id] = poll
|
||||||
|
irc.replySuccess()
|
||||||
|
|
||||||
|
@wrap(["channel", "positiveInt", "somethingWithoutSpaces"])
|
||||||
|
def vote(self, irc, msg, args, channel, poll_id, answer_id):
|
||||||
|
"""[<channel>] <poll_id> <answer_id>
|
||||||
|
|
||||||
|
Registers your vote on the poll <poll_id> as being the answer
|
||||||
|
identified by <answer_id> (which is the first word of each possible
|
||||||
|
answer)."""
|
||||||
|
|
||||||
|
poll = self._getPoll(irc, channel, poll_id)
|
||||||
|
|
||||||
|
if not poll.open:
|
||||||
|
irc.error(_("This poll is closed."), Raise=True)
|
||||||
|
|
||||||
|
if msg.nick in poll.votes:
|
||||||
|
irc.error(_("You already voted on this poll."), Raise=True)
|
||||||
|
|
||||||
|
if answer_id not in poll.answers:
|
||||||
|
irc.error(
|
||||||
|
format(
|
||||||
|
_("Invalid answer ID. Valid answers are: %L"),
|
||||||
|
poll.answers,
|
||||||
|
),
|
||||||
|
Raise=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
poll.votes[msg.nick] = answer_id
|
||||||
|
|
||||||
|
irc.replySuccess()
|
||||||
|
|
||||||
|
@wrap(["channel", "positiveInt"])
|
||||||
|
def results(self, irc, msg, args, channel, poll_id):
|
||||||
|
"""[<channel>] <poll_id>
|
||||||
|
|
||||||
|
Returns the results of the specified poll."""
|
||||||
|
|
||||||
|
poll = self._getPoll(irc, channel, poll_id)
|
||||||
|
|
||||||
|
counts = collections.Counter(poll.votes.values())
|
||||||
|
|
||||||
|
# Add answers with 0 votes
|
||||||
|
counts.update({answer_id: 0 for answer_id in poll.answers})
|
||||||
|
|
||||||
|
results = [
|
||||||
|
format(_("%n for %s"), (v, "vote"), k)
|
||||||
|
for (k, v) in counts.most_common()
|
||||||
|
]
|
||||||
|
|
||||||
|
irc.replies(results)
|
||||||
|
|
||||||
|
|
||||||
|
Class = Poll_
|
124
plugins/Poll/test.py
Normal file
124
plugins/Poll/test.py
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
###
|
||||||
|
# Copyright (c) 2021, 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 *
|
||||||
|
|
||||||
|
|
||||||
|
class PollTestCase(ChannelPluginTestCase):
|
||||||
|
plugins = ("Poll",)
|
||||||
|
|
||||||
|
def testBasics(self):
|
||||||
|
self.assertResponse(
|
||||||
|
'poll add "Is this a test?" "Yes" "No" "Maybe"',
|
||||||
|
"The operation succeeded. Poll # 1 created.",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertNotError("vote 1 Yes", frm="voter1!foo@bar")
|
||||||
|
self.assertNotError("vote 1 No", frm="voter2!foo@bar")
|
||||||
|
self.assertNotError("vote 1 No", frm="voter3!foo@bar")
|
||||||
|
|
||||||
|
self.assertResponse(
|
||||||
|
"results 1",
|
||||||
|
"2 votes for No, 1 vote for Yes, and 0 votes for Maybe",
|
||||||
|
)
|
||||||
|
|
||||||
|
def testDoubleVoting(self):
|
||||||
|
self.assertResponse(
|
||||||
|
'poll add "Is this a test?" "Yes" "No" "Maybe"',
|
||||||
|
"The operation succeeded. Poll # 1 created.",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertNotError("vote 1 Yes", frm="voter1!foo@bar")
|
||||||
|
self.assertNotError("vote 1 No", frm="voter2!foo@bar")
|
||||||
|
self.assertResponse(
|
||||||
|
"vote 1 Yes",
|
||||||
|
"voter1: Error: You already voted on this poll.",
|
||||||
|
frm="voter1!foo@bar",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRegexp(
|
||||||
|
"results 1",
|
||||||
|
"1 vote for (Yes|No), 1 vote for (Yes|No), and 0 votes for Maybe",
|
||||||
|
)
|
||||||
|
|
||||||
|
def testClosed(self):
|
||||||
|
self.assertResponse(
|
||||||
|
'poll add "Is this a test?" "Yes" "No" "Maybe"',
|
||||||
|
"The operation succeeded. Poll # 1 created.",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertNotError("vote 1 Yes", frm="voter1!foo@bar")
|
||||||
|
self.assertNotError("vote 1 No", frm="voter2!foo@bar")
|
||||||
|
self.assertNotError("close 1")
|
||||||
|
self.assertResponse(
|
||||||
|
"vote 1 Yes",
|
||||||
|
"voter3: Error: This poll is closed.",
|
||||||
|
frm="voter3!foo@bar",
|
||||||
|
)
|
||||||
|
self.assertRegexp("close 1", "already closed")
|
||||||
|
|
||||||
|
self.assertRegexp(
|
||||||
|
"results 1",
|
||||||
|
"1 vote for (Yes|No), 1 vote for (Yes|No), and 0 votes for Maybe",
|
||||||
|
)
|
||||||
|
|
||||||
|
def testNonExisting(self):
|
||||||
|
self.assertResponse(
|
||||||
|
'poll add "Is this a test?" "Yes" "No" "Maybe"',
|
||||||
|
"The operation succeeded. Poll # 1 created.",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRegexp("vote 2 Yes", "does not exist")
|
||||||
|
|
||||||
|
def testLongAnswers(self):
|
||||||
|
self.assertResponse(
|
||||||
|
'poll add "Is this a test?" "Yes totally" "No no no" "Maybe"',
|
||||||
|
"The operation succeeded. Poll # 1 created.",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertNotError("vote 1 Yes", frm="voter1!foo@bar")
|
||||||
|
self.assertNotError("vote 1 No", frm="voter2!foo@bar")
|
||||||
|
self.assertNotError("vote 1 No", frm="voter3!foo@bar")
|
||||||
|
|
||||||
|
self.assertResponse(
|
||||||
|
"results 1",
|
||||||
|
"2 votes for No, 1 vote for Yes, and 0 votes for Maybe",
|
||||||
|
)
|
||||||
|
|
||||||
|
def testDuplicateId(self):
|
||||||
|
self.assertResponse(
|
||||||
|
'poll add "Is this a test?" "Yes" "Yes" "Maybe"',
|
||||||
|
"Error: Duplicate answer identifier(s): Yes",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertResponse(
|
||||||
|
'poll add "Is this a test?" "Yes totally" "Yes and no" "Maybe"',
|
||||||
|
"Error: Duplicate answer identifier(s): Yes",
|
||||||
|
)
|
@ -23,6 +23,11 @@ necessary if the bot is properly configured.
|
|||||||
|
|
||||||
Commands
|
Commands
|
||||||
--------
|
--------
|
||||||
|
.. _command-services-chanserv:
|
||||||
|
|
||||||
|
chanserv <text>
|
||||||
|
Sends the <text> to ChanServ. For example, to register a channel on Atheme, use: @chanserv REGISTER <#channel>.
|
||||||
|
|
||||||
.. _command-services-ghost:
|
.. _command-services-ghost:
|
||||||
|
|
||||||
ghost [<nick>]
|
ghost [<nick>]
|
||||||
@ -43,6 +48,11 @@ invite [<channel>]
|
|||||||
nicks takes no arguments
|
nicks takes no arguments
|
||||||
Returns the nicks that this plugin is configured to identify and ghost with.
|
Returns the nicks that this plugin is configured to identify and ghost with.
|
||||||
|
|
||||||
|
.. _command-services-nickserv:
|
||||||
|
|
||||||
|
nickserv <text>
|
||||||
|
Sends the <text> to NickServ. For example, to register to NickServ on Atheme, use: @nickserv REGISTER <password> <email-address>.
|
||||||
|
|
||||||
.. _command-services-op:
|
.. _command-services-op:
|
||||||
|
|
||||||
op [<channel>]
|
op [<channel>]
|
||||||
|
@ -558,6 +558,35 @@ class Services(callbacks.Plugin):
|
|||||||
'I\'m able to ghost a nick.'))
|
'I\'m able to ghost a nick.'))
|
||||||
ghost = wrap(ghost, [('checkCapability', 'admin'), additional('nick')])
|
ghost = wrap(ghost, [('checkCapability', 'admin'), additional('nick')])
|
||||||
|
|
||||||
|
def nickserv(self, irc, msg, args, text):
|
||||||
|
"""<text>
|
||||||
|
|
||||||
|
Sends the <text> to NickServ. For example, to register to NickServ
|
||||||
|
on Atheme, use: @nickserv REGISTER <password> <email-address>."""
|
||||||
|
nickserv = self.registryValue('NickServ', network=irc.network)
|
||||||
|
if nickserv:
|
||||||
|
irc.replySuccess()
|
||||||
|
irc.queueMsg(ircmsgs.privmsg(nickserv, text))
|
||||||
|
else:
|
||||||
|
irc.error(_('You must set supybot.plugins.Services.NickServ before '
|
||||||
|
'I\'m able to message NickServ'))
|
||||||
|
nickserv = wrap(nickserv, ['owner', 'text'])
|
||||||
|
|
||||||
|
def chanserv(self, irc, msg, args, text):
|
||||||
|
"""<text>
|
||||||
|
|
||||||
|
Sends the <text> to ChanServ. For example, to register a channel
|
||||||
|
on Atheme, use: @chanserv REGISTER <#channel>."""
|
||||||
|
chanserv = self.registryValue('ChanServ', network=irc.network)
|
||||||
|
if chanserv:
|
||||||
|
irc.replySuccess()
|
||||||
|
irc.queueMsg(ircmsgs.privmsg(chanserv, text))
|
||||||
|
else:
|
||||||
|
irc.error(_('You must set supybot.plugins.Services.ChanServ before '
|
||||||
|
'I\'m able to message ChanServ'))
|
||||||
|
chanserv = wrap(chanserv, ['owner', 'text'])
|
||||||
|
|
||||||
|
|
||||||
@internationalizeDocstring
|
@internationalizeDocstring
|
||||||
def password(self, irc, msg, args, nick, password):
|
def password(self, irc, msg, args, nick, password):
|
||||||
"""<nick> [<password>]
|
"""<nick> [<password>]
|
||||||
@ -606,9 +635,9 @@ class Services(callbacks.Plugin):
|
|||||||
Raise=True
|
Raise=True
|
||||||
)
|
)
|
||||||
|
|
||||||
if "draft/register" not in otherIrc.state.capabilities_ls:
|
if "draft/account-registration" not in otherIrc.state.capabilities_ls:
|
||||||
irc.error(
|
irc.error(
|
||||||
_("This network does not support draft/register."),
|
_("This network does not support draft/account-registration."),
|
||||||
Raise=True
|
Raise=True
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -636,7 +665,7 @@ class Services(callbacks.Plugin):
|
|||||||
# https://gist.github.com/edk0/bf3b50fc219fd1bed1aa15d98bfb6495
|
# https://gist.github.com/edk0/bf3b50fc219fd1bed1aa15d98bfb6495
|
||||||
self._checkCanRegister(irc, otherIrc)
|
self._checkCanRegister(irc, otherIrc)
|
||||||
|
|
||||||
cap_values = (otherIrc.state.capabilities_ls["draft/register"] or "").split(",")
|
cap_values = (otherIrc.state.capabilities_ls["draft/account-registration"] or "").split(",")
|
||||||
if "email-required" in cap_values and email is None:
|
if "email-required" in cap_values and email is None:
|
||||||
irc.error(
|
irc.error(
|
||||||
_("This network requires an email address to register."),
|
_("This network requires an email address to register."),
|
||||||
@ -648,7 +677,7 @@ class Services(callbacks.Plugin):
|
|||||||
otherIrc.queueMsg(ircmsgs.IrcMsg(
|
otherIrc.queueMsg(ircmsgs.IrcMsg(
|
||||||
server_tags={"label": label},
|
server_tags={"label": label},
|
||||||
command="REGISTER",
|
command="REGISTER",
|
||||||
args=[email or "*", password],
|
args=["*", email or "*", password],
|
||||||
))
|
))
|
||||||
register = wrap(register, ["owner", "private", "networkIrc", "something", optional("email")])
|
register = wrap(register, ["owner", "private", "networkIrc", "something", optional("email")])
|
||||||
|
|
||||||
|
@ -88,6 +88,18 @@ class ServicesTestCase(PluginTestCase):
|
|||||||
finally:
|
finally:
|
||||||
self.assertNotError('services password %s ""' % self.nick)
|
self.assertNotError('services password %s ""' % self.nick)
|
||||||
|
|
||||||
|
def testNickserv(self):
|
||||||
|
self.assertNotError('nickserv foo bar')
|
||||||
|
m = self.irc.takeMsg()
|
||||||
|
self.assertEqual(m.command, 'PRIVMSG', m)
|
||||||
|
self.assertEqual(m.args, ('NickServ', 'foo bar'), m)
|
||||||
|
|
||||||
|
def testChanserv(self):
|
||||||
|
self.assertNotError('chanserv foo bar')
|
||||||
|
m = self.irc.takeMsg()
|
||||||
|
self.assertEqual(m.command, 'PRIVMSG', m)
|
||||||
|
self.assertEqual(m.args, ('ChanServ', 'foo bar'), m)
|
||||||
|
|
||||||
def testRegisterNoExperimentalExtensions(self):
|
def testRegisterNoExperimentalExtensions(self):
|
||||||
self.assertRegexp(
|
self.assertRegexp(
|
||||||
"register p4ssw0rd", "error: Experimental IRC extensions")
|
"register p4ssw0rd", "error: Experimental IRC extensions")
|
||||||
@ -180,7 +192,7 @@ class ExperimentalServicesTestCase(PluginTestCase):
|
|||||||
super().setUp()
|
super().setUp()
|
||||||
conf.supybot.protocols.irc.experimentalExtensions.setValue(True)
|
conf.supybot.protocols.irc.experimentalExtensions.setValue(True)
|
||||||
self._initialCaps = self.irc.state.capabilities_ls.copy()
|
self._initialCaps = self.irc.state.capabilities_ls.copy()
|
||||||
self.irc.state.capabilities_ls["draft/register"] = None
|
self.irc.state.capabilities_ls["draft/account-registration"] = None
|
||||||
self.irc.state.capabilities_ls["labeled-response"] = None
|
self.irc.state.capabilities_ls["labeled-response"] = None
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
@ -196,17 +208,17 @@ class ExperimentalServicesTestCase(PluginTestCase):
|
|||||||
"register p4ssw0rd",
|
"register p4ssw0rd",
|
||||||
"error: This network does not support labeled-response.")
|
"error: This network does not support labeled-response.")
|
||||||
|
|
||||||
del self.irc.state.capabilities_ls["draft/register"]
|
del self.irc.state.capabilities_ls["draft/account-registration"]
|
||||||
self.assertRegexp(
|
self.assertRegexp(
|
||||||
"register p4ssw0rd",
|
"register p4ssw0rd",
|
||||||
"error: This network does not support draft/register.")
|
"error: This network does not support draft/account-registration.")
|
||||||
finally:
|
finally:
|
||||||
self.irc.state.capabilities_ls = old_caps
|
self.irc.state.capabilities_ls = old_caps
|
||||||
|
|
||||||
def testRegisterRequireEmail(self):
|
def testRegisterRequireEmail(self):
|
||||||
old_caps = self.irc.state.capabilities_ls.copy()
|
old_caps = self.irc.state.capabilities_ls.copy()
|
||||||
try:
|
try:
|
||||||
self.irc.state.capabilities_ls["draft/register"] = "email-required"
|
self.irc.state.capabilities_ls["draft/account-registration"] = "email-required"
|
||||||
self.assertRegexp(
|
self.assertRegexp(
|
||||||
"register p4ssw0rd",
|
"register p4ssw0rd",
|
||||||
"error: This network requires an email address to register.")
|
"error: This network requires an email address to register.")
|
||||||
@ -216,7 +228,7 @@ class ExperimentalServicesTestCase(PluginTestCase):
|
|||||||
def testRegisterSuccess(self):
|
def testRegisterSuccess(self):
|
||||||
m = self.getMsg("register p4ssw0rd")
|
m = self.getMsg("register p4ssw0rd")
|
||||||
label = m.server_tags.pop("label")
|
label = m.server_tags.pop("label")
|
||||||
self.assertEqual(m, IrcMsg(command="REGISTER", args=["*", "p4ssw0rd"]))
|
self.assertEqual(m, IrcMsg(command="REGISTER", args=["*", "*", "p4ssw0rd"]))
|
||||||
self.irc.feedMsg(IrcMsg(
|
self.irc.feedMsg(IrcMsg(
|
||||||
server_tags={"label": label},
|
server_tags={"label": label},
|
||||||
command="REGISTER",
|
command="REGISTER",
|
||||||
@ -230,7 +242,7 @@ class ExperimentalServicesTestCase(PluginTestCase):
|
|||||||
# oragono replies with a batch
|
# oragono replies with a batch
|
||||||
m = self.getMsg("register p4ssw0rd")
|
m = self.getMsg("register p4ssw0rd")
|
||||||
label = m.server_tags.pop("label")
|
label = m.server_tags.pop("label")
|
||||||
self.assertEqual(m, IrcMsg(command="REGISTER", args=["*", "p4ssw0rd"]))
|
self.assertEqual(m, IrcMsg(command="REGISTER", args=["*", "*", "p4ssw0rd"]))
|
||||||
|
|
||||||
batch_name = "Services_testRegisterSuccessBatch"
|
batch_name = "Services_testRegisterSuccessBatch"
|
||||||
self.irc.feedMsg(IrcMsg(
|
self.irc.feedMsg(IrcMsg(
|
||||||
@ -261,7 +273,7 @@ class ExperimentalServicesTestCase(PluginTestCase):
|
|||||||
m = self.getMsg("register p4ssw0rd foo@example.org")
|
m = self.getMsg("register p4ssw0rd foo@example.org")
|
||||||
label = m.server_tags.pop("label")
|
label = m.server_tags.pop("label")
|
||||||
self.assertEqual(m, IrcMsg(
|
self.assertEqual(m, IrcMsg(
|
||||||
command="REGISTER", args=["foo@example.org", "p4ssw0rd"]))
|
command="REGISTER", args=["*", "foo@example.org", "p4ssw0rd"]))
|
||||||
self.irc.feedMsg(IrcMsg(
|
self.irc.feedMsg(IrcMsg(
|
||||||
server_tags={"label": label},
|
server_tags={"label": label},
|
||||||
command="REGISTER",
|
command="REGISTER",
|
||||||
@ -274,7 +286,7 @@ class ExperimentalServicesTestCase(PluginTestCase):
|
|||||||
def testRegisterVerify(self):
|
def testRegisterVerify(self):
|
||||||
m = self.getMsg("register p4ssw0rd")
|
m = self.getMsg("register p4ssw0rd")
|
||||||
label = m.server_tags.pop("label")
|
label = m.server_tags.pop("label")
|
||||||
self.assertEqual(m, IrcMsg(command="REGISTER", args=["*", "p4ssw0rd"]))
|
self.assertEqual(m, IrcMsg(command="REGISTER", args=["*", "*", "p4ssw0rd"]))
|
||||||
self.irc.feedMsg(IrcMsg(
|
self.irc.feedMsg(IrcMsg(
|
||||||
server_tags={"label": label},
|
server_tags={"label": label},
|
||||||
command="REGISTER",
|
command="REGISTER",
|
||||||
@ -301,7 +313,7 @@ class ExperimentalServicesTestCase(PluginTestCase):
|
|||||||
def testRegisterVerifyBatch(self):
|
def testRegisterVerifyBatch(self):
|
||||||
m = self.getMsg("register p4ssw0rd")
|
m = self.getMsg("register p4ssw0rd")
|
||||||
label = m.server_tags.pop("label")
|
label = m.server_tags.pop("label")
|
||||||
self.assertEqual(m, IrcMsg(command="REGISTER", args=["*", "p4ssw0rd"]))
|
self.assertEqual(m, IrcMsg(command="REGISTER", args=["*", "*", "p4ssw0rd"]))
|
||||||
self.irc.feedMsg(IrcMsg(
|
self.irc.feedMsg(IrcMsg(
|
||||||
server_tags={"label": label},
|
server_tags={"label": label},
|
||||||
command="REGISTER",
|
command="REGISTER",
|
||||||
|
@ -60,12 +60,12 @@ list [--capability=<capability>] [<glob>]
|
|||||||
.. _command-user-register:
|
.. _command-user-register:
|
||||||
|
|
||||||
register <name> <password>
|
register <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.
|
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. Use "!" instead of <password> to disable password authentication.
|
||||||
|
|
||||||
.. _command-user-set.password:
|
.. _command-user-set.password:
|
||||||
|
|
||||||
set password [<name>] <old password> <new password>
|
set password [<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.
|
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. If the <new password> is "!", password login will be disabled.
|
||||||
|
|
||||||
.. _command-user-set.secure:
|
.. _command-user-set.secure:
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 79
|
line-length = 79
|
||||||
|
|
||||||
include = 'plugins/(Autocomplete|Fediverse)/.*\.pyi?$'
|
include = 'plugins/(Autocomplete|Fediverse|Poll)/.*\.pyi?$'
|
||||||
|
@ -84,7 +84,7 @@ class PluginDoc(object):
|
|||||||
def __init__(self, mod, titleTemplate):
|
def __init__(self, mod, titleTemplate):
|
||||||
self.mod = mod
|
self.mod = mod
|
||||||
self.inst = self.mod.Class(None)
|
self.inst = self.mod.Class(None)
|
||||||
self.name = self.mod.Class.__name__
|
self.name = self.inst.name()
|
||||||
self.appendExtraBlankLine = False
|
self.appendExtraBlankLine = False
|
||||||
self.titleTemplate = string.Template(titleTemplate)
|
self.titleTemplate = string.Template(titleTemplate)
|
||||||
self.lines = []
|
self.lines = []
|
||||||
|
@ -511,7 +511,9 @@ class RichReplyMethods(object):
|
|||||||
# change the state of this Irc object.
|
# change the state of this Irc object.
|
||||||
if to is not None:
|
if to is not None:
|
||||||
self.to = self.to or to
|
self.to = self.to or to
|
||||||
if self.private or self.msg.channel is None:
|
if self.private:
|
||||||
|
target = to or self.msg.nick
|
||||||
|
elif self.msg.channel is None:
|
||||||
target = self.msg.nick
|
target = self.msg.nick
|
||||||
else:
|
else:
|
||||||
target = self.to or self.msg.args[0]
|
target = self.to or self.msg.args[0]
|
||||||
@ -721,21 +723,26 @@ class ReplyIrcProxy(RichReplyMethods):
|
|||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return getattr(self.irc, attr)
|
return getattr(self.irc, attr)
|
||||||
|
|
||||||
def _replyOverhead(self, target, targetNick, prefixNick):
|
def _replyOverhead(self, msg, **kwargs):
|
||||||
"""Returns the number of bytes added to a PRIVMSG payload, either by
|
"""Returns the number of bytes added to a PRIVMSG payload, either by
|
||||||
Limnoria itself or by the server.
|
Limnoria itself or by the server.
|
||||||
Ignores tag bytes, as they are accounted for separatly."""
|
Ignores tag bytes, as they are accounted for separately."""
|
||||||
overhead = (
|
# FIXME: big hack.
|
||||||
len(':')
|
# _makeReply does a lot of internal state computation, especially
|
||||||
+ len(self.irc.prefix.encode())
|
# related to the final target to use.
|
||||||
+ len(' PRIVMSG ')
|
# I tried to get them out of _makeReply but it's a clusterfuck, so I
|
||||||
+ len(target.encode())
|
# gave up. Instead, we call _makeReply with a dummy payload to guess
|
||||||
+ len(' :')
|
# what overhead it will add.
|
||||||
+ len('\r\n')
|
payload = 'foo'
|
||||||
)
|
channel = msg.channel
|
||||||
if prefixNick and targetNick is not None:
|
msg = copy.deepcopy(msg) # because _makeReply calls .tag('repliedTo')
|
||||||
overhead += len(targetNick) + len(': ')
|
msg.channel = channel # ugh... copy.deepcopy uses msg.__reduce__
|
||||||
return overhead
|
reply_msg = _makeReply(self, msg, payload, **kwargs)
|
||||||
|
|
||||||
|
# strip tags, add prefix
|
||||||
|
reply_msg = ircmsgs.IrcMsg(
|
||||||
|
msg=reply_msg, server_tags={}, prefix=self.prefix)
|
||||||
|
return len(str(reply_msg)) - len(payload)
|
||||||
|
|
||||||
def _sendReply(self, s, target, msg, sendImmediately=False,
|
def _sendReply(self, s, target, msg, sendImmediately=False,
|
||||||
noLengthCheck=False, **kwargs):
|
noLengthCheck=False, **kwargs):
|
||||||
@ -760,8 +767,7 @@ class ReplyIrcProxy(RichReplyMethods):
|
|||||||
allowedLength = conf.get(conf.supybot.reply.mores.length,
|
allowedLength = conf.get(conf.supybot.reply.mores.length,
|
||||||
channel=target, network=self.irc.network)
|
channel=target, network=self.irc.network)
|
||||||
if not allowedLength: # 0 indicates this.
|
if not allowedLength: # 0 indicates this.
|
||||||
allowedLength = 512 - self._replyOverhead(
|
allowedLength = 512 - self._replyOverhead(msg, **kwargs)
|
||||||
target, msg.nick, prefixNick=kwargs['prefixNick'])
|
|
||||||
maximumMores = conf.get(conf.supybot.reply.mores.maximum,
|
maximumMores = conf.get(conf.supybot.reply.mores.maximum,
|
||||||
channel=target, network=self.irc.network)
|
channel=target, network=self.irc.network)
|
||||||
maximumLength = allowedLength * maximumMores
|
maximumLength = allowedLength * maximumMores
|
||||||
@ -901,12 +907,12 @@ class ReplyIrcProxy(RichReplyMethods):
|
|||||||
assert conf.supybot.protocols.irc.experimentalExtensions()
|
assert conf.supybot.protocols.irc.experimentalExtensions()
|
||||||
assert 'draft/multiline' in self.state.capabilities_ack
|
assert 'draft/multiline' in self.state.capabilities_ack
|
||||||
|
|
||||||
if not allowedLength: # 0 indicates this.
|
if allowedLength: # 0 indicates this.
|
||||||
# We're only interested in the overhead outside the payload,
|
largest_msg_size = allowedLength
|
||||||
# regardless of the entire payload (nick prefix included),
|
else:
|
||||||
# so prefixNick=False
|
# Used as upper bound of each message's size to decide how many
|
||||||
allowedLength = 512 - self._replyOverhead(
|
# messages to put in each batch.
|
||||||
target, targetNick, prefixNick=False)
|
largest_msg_size = max(len(msg.args[1]) for msg in msgs)
|
||||||
|
|
||||||
multiline_cap_values = ircutils.parseCapabilityKeyValue(
|
multiline_cap_values = ircutils.parseCapabilityKeyValue(
|
||||||
self.state.capabilities_ls['draft/multiline'])
|
self.state.capabilities_ls['draft/multiline'])
|
||||||
@ -919,7 +925,7 @@ class ReplyIrcProxy(RichReplyMethods):
|
|||||||
# encode messages again here just to have their length, so
|
# encode messages again here just to have their length, so
|
||||||
# let's assume they all have the maximum length.
|
# let's assume they all have the maximum length.
|
||||||
# It's not optimal, but close enough and simplifies the code.
|
# It's not optimal, but close enough and simplifies the code.
|
||||||
messages_per_batch = max_bytes_per_batch // allowedLength
|
messages_per_batch = max_bytes_per_batch // largest_msg_size
|
||||||
|
|
||||||
# "Clients MUST NOT send tags other than draft/multiline-concat and
|
# "Clients MUST NOT send tags other than draft/multiline-concat and
|
||||||
# batch on messages within the batch. In particular, all client-only
|
# batch on messages within the batch. In particular, all client-only
|
||||||
@ -1139,7 +1145,7 @@ class NestedCommandsIrcProxy(ReplyIrcProxy):
|
|||||||
# evaluated our own list of arguments.
|
# evaluated our own list of arguments.
|
||||||
assert not self.finalEvaled, 'finalEval called twice.'
|
assert not self.finalEvaled, 'finalEval called twice.'
|
||||||
self.finalEvaled = True
|
self.finalEvaled = True
|
||||||
# Now, the way we call a command is we iterate over the loaded pluings,
|
# Now, the way we call a command is we iterate over the loaded plugins,
|
||||||
# asking each one if the list of args we have interests it. The
|
# asking each one if the list of args we have interests it. The
|
||||||
# way we do that is by calling getCommand on the plugin.
|
# way we do that is by calling getCommand on the plugin.
|
||||||
# The plugin will return a list of args which it considers to be
|
# The plugin will return a list of args which it considers to be
|
||||||
|
11
src/conf.py
11
src/conf.py
@ -339,10 +339,19 @@ class SpaceSeparatedSetOfChannels(registry.SpaceSeparatedListOf):
|
|||||||
channels.append(channel)
|
channels.append(channel)
|
||||||
msg = ircmsgs.joins(channels_with_key + channels, keys)
|
msg = ircmsgs.joins(channels_with_key + channels, keys)
|
||||||
if len(str(msg)) > 512:
|
if len(str(msg)) > 512:
|
||||||
|
# Use previous short enough join message
|
||||||
msgs.append(old)
|
msgs.append(old)
|
||||||
|
# Reset and construct a new join message using the current
|
||||||
|
# channel.
|
||||||
keys = []
|
keys = []
|
||||||
channels_with_key = []
|
channels_with_key = []
|
||||||
channels = []
|
channels = []
|
||||||
|
if key:
|
||||||
|
keys.append(key)
|
||||||
|
channels_with_key.append(channel)
|
||||||
|
else:
|
||||||
|
channels.append(channel)
|
||||||
|
msg = ircmsgs.joins(channels_with_key + channels, keys)
|
||||||
old = msg
|
old = msg
|
||||||
if msg:
|
if msg:
|
||||||
msgs.append(msg)
|
msgs.append(msg)
|
||||||
@ -554,7 +563,7 @@ registerChannelValue(supybot.reply.error, 'withNotice',
|
|||||||
messages to users via NOTICE instead of PRIVMSG. You might want to do this
|
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
|
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
|
messages; or you might want to use it in combination with
|
||||||
supybot.reply.errorInPrivate so private errors don't open a query window
|
supybot.reply.error.inPrivate so private errors don't open a query window
|
||||||
in most IRC clients.""")))
|
in most IRC clients.""")))
|
||||||
registerChannelValue(supybot.reply.error, 'noCapability',
|
registerChannelValue(supybot.reply.error, 'noCapability',
|
||||||
registry.Boolean(False, _("""Determines whether the bot will *not* provide
|
registry.Boolean(False, _("""Determines whether the bot will *not* provide
|
||||||
|
@ -318,7 +318,7 @@ class SocketDriver(drivers.IrcDriver, drivers.ServersMixin):
|
|||||||
# address is a hostname, eg. because we're using a SOCKS
|
# address is a hostname, eg. because we're using a SOCKS
|
||||||
# proxy
|
# proxy
|
||||||
is_loopback = False
|
is_loopback = False
|
||||||
if not is_loopback:
|
if not is_loopback and not address.endswith('.onion'):
|
||||||
drivers.log.warning(('Connection to network %s '
|
drivers.log.warning(('Connection to network %s '
|
||||||
'does not use SSL/TLS, which makes it vulnerable to '
|
'does not use SSL/TLS, which makes it vulnerable to '
|
||||||
'man-in-the-middle attacks and passive eavesdropping. '
|
'man-in-the-middle attacks and passive eavesdropping. '
|
||||||
|
@ -61,7 +61,7 @@ class IrcDriver(object):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def die(self):
|
def die(self):
|
||||||
# The end of any overrided die method should be
|
# The end of any overridden die method should be
|
||||||
# "super(Class, self).die()", in order to make
|
# "super(Class, self).die()", in order to make
|
||||||
# sure this (and anything else later added) is done.
|
# sure this (and anything else later added) is done.
|
||||||
remove(self.name())
|
remove(self.name())
|
||||||
|
@ -708,8 +708,11 @@ class IrcState(IrcCommandDispatcher, log.Firewalled):
|
|||||||
self.channels = channels
|
self.channels = channels
|
||||||
self.nicksToHostmasks = nicksToHostmasks
|
self.nicksToHostmasks = nicksToHostmasks
|
||||||
|
|
||||||
# Batches should always finish and be way shorter than 3600s, but
|
# Batches usually finish and are way shorter than 3600s, but
|
||||||
# let's just make sure to avoid leaking memory.
|
# we need to:
|
||||||
|
# * keep them in case the connection breaks (and reset() can't
|
||||||
|
# clear the list itself)
|
||||||
|
# * make sure to avoid leaking memory in general
|
||||||
self.batches = ExpiringDict(timeout=3600)
|
self.batches = ExpiringDict(timeout=3600)
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
@ -721,12 +724,24 @@ class IrcState(IrcCommandDispatcher, log.Firewalled):
|
|||||||
self.channels.clear()
|
self.channels.clear()
|
||||||
self.supported.clear()
|
self.supported.clear()
|
||||||
self.nicksToHostmasks.clear()
|
self.nicksToHostmasks.clear()
|
||||||
self.batches.clear()
|
|
||||||
self.capabilities_req = set()
|
self.capabilities_req = set()
|
||||||
self.capabilities_ack = set()
|
self.capabilities_ack = set()
|
||||||
self.capabilities_nak = set()
|
self.capabilities_nak = set()
|
||||||
self.capabilities_ls = {}
|
self.capabilities_ls = {}
|
||||||
|
|
||||||
|
# Don't clear batches right now. reset() is called on ERROR messages,
|
||||||
|
# which may be part of a BATCH so we need to remember that batch.
|
||||||
|
# At worst, the batch will expire in the near future, as self.batches
|
||||||
|
# is an instance of ExpiringDict.
|
||||||
|
# If we did clear the batch, then this would happen:
|
||||||
|
# 1. IrcState.addMsg() would crash on the ERROR, because its batch
|
||||||
|
# server-tag references an unknown batch, so it would not set the
|
||||||
|
# 'batch' supybot-tag
|
||||||
|
# 2. Irc.doBatch would crash on the closing BATCH, for the same reason
|
||||||
|
# 3. Owner.doBatch would crash because it expects the batch
|
||||||
|
# supybot-tag to be set, but it wasn't because of 1
|
||||||
|
#self.batches.clear()
|
||||||
|
|
||||||
def __reduce__(self):
|
def __reduce__(self):
|
||||||
return (self.__class__, (self.history, self.supported,
|
return (self.__class__, (self.history, self.supported,
|
||||||
self.nicksToHostmasks, self.channels))
|
self.nicksToHostmasks, self.channels))
|
||||||
|
@ -272,7 +272,10 @@ class IrcMsg(object):
|
|||||||
else:
|
else:
|
||||||
self.reply_env = None
|
self.reply_env = None
|
||||||
self.tags = msg.tags.copy()
|
self.tags = msg.tags.copy()
|
||||||
self.server_tags = msg.server_tags
|
if server_tags is None:
|
||||||
|
self.server_tags = msg.server_tags.copy()
|
||||||
|
else:
|
||||||
|
self.server_tags = server_tags
|
||||||
self.time = msg.time
|
self.time = msg.time
|
||||||
else:
|
else:
|
||||||
self.prefix = prefix
|
self.prefix = prefix
|
||||||
|
@ -445,7 +445,7 @@ class Value(Group):
|
|||||||
# The complicated case. We want a net+chan specific value,
|
# The complicated case. We want a net+chan specific value,
|
||||||
# which may come in three different ways:
|
# which may come in three different ways:
|
||||||
#
|
#
|
||||||
# 1. it was set explicitely net+chan
|
# 1. it was set explicitly net+chan
|
||||||
# 2. it's inherited from a net specific value (which may itself be
|
# 2. it's inherited from a net specific value (which may itself be
|
||||||
# inherited from the base value)
|
# inherited from the base value)
|
||||||
# 3. it's inherited from the chan specific value (which is not a
|
# 3. it's inherited from the chan specific value (which is not a
|
||||||
@ -453,7 +453,7 @@ class Value(Group):
|
|||||||
# load configuration from old bots).
|
# load configuration from old bots).
|
||||||
#
|
#
|
||||||
# The choice between 2 and 3 is done by checking which of the
|
# The choice between 2 and 3 is done by checking which of the
|
||||||
# net-specific and chan-specific values was set explicitely by
|
# net-specific and chan-specific values was set explicitly by
|
||||||
# a user/admin. In case both were, the net-specific value is used
|
# 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
|
# (there is no particular reason for this, I just think it makes
|
||||||
# more sense).
|
# more sense).
|
||||||
|
@ -561,6 +561,14 @@ class PrivmsgTestCase(ChannelPluginTestCase):
|
|||||||
" " + "foo " * 79 + "'")
|
" " + "foo " * 79 + "'")
|
||||||
self.assertNoResponse(" ", timeout=0.1)
|
self.assertNoResponse(" ", timeout=0.1)
|
||||||
|
|
||||||
|
def testReplyPrivate(self):
|
||||||
|
# Send from a very long nick, which should be taken into account when
|
||||||
|
# computing the reply overhead.
|
||||||
|
self.assertResponse(
|
||||||
|
"eval irc.reply('foo '*300, private=True)",
|
||||||
|
"foo " * 39 + "\x02(7 more messages)\x02",
|
||||||
|
frm='foo'*100 + '!bar@baz')
|
||||||
|
|
||||||
def testClientTagReply(self):
|
def testClientTagReply(self):
|
||||||
self.irc.addCallback(self.First(self.irc))
|
self.irc.addCallback(self.First(self.irc))
|
||||||
|
|
||||||
|
59
test/test_conf.py
Normal file
59
test/test_conf.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
##
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions, and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions, and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the author of this software nor the name of
|
||||||
|
# contributors to this software may be used to endorse or promote products
|
||||||
|
# derived from this software without specific prior written consent.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT (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.registry as registry
|
||||||
|
import supybot.ircutils as ircutils
|
||||||
|
|
||||||
|
|
||||||
|
class SupyConfTestCase(SupyTestCase):
|
||||||
|
def testJoinToOneChannel(self):
|
||||||
|
orig = conf.supybot.networks.test.channels()
|
||||||
|
channels = ircutils.IrcSet()
|
||||||
|
channels.add("#bar")
|
||||||
|
conf.supybot.networks.test.channels.setValue(channels)
|
||||||
|
msgs = conf.supybot.networks.test.channels.joins()
|
||||||
|
self.assertEqual(msgs[0].args, ("#bar",))
|
||||||
|
conf.supybot.networks.test.channels.setValue(orig)
|
||||||
|
|
||||||
|
def testJoinToManyChannels(self):
|
||||||
|
orig = conf.supybot.networks.test.channels()
|
||||||
|
channels = ircutils.IrcSet()
|
||||||
|
input_list = []
|
||||||
|
for x in range(1, 30):
|
||||||
|
name = "#verylongchannelname" + str(x)
|
||||||
|
channels.add(name)
|
||||||
|
input_list.append(name)
|
||||||
|
conf.supybot.networks.test.channels.setValue(channels)
|
||||||
|
msgs = conf.supybot.networks.test.channels.joins()
|
||||||
|
# Double check we split the messages
|
||||||
|
self.assertEqual(len(msgs), 2)
|
||||||
|
# Ensure all channel names are present
|
||||||
|
chan_list = (msgs[0].args[0] + ',' + msgs[1].args[0]).split(',')
|
||||||
|
self.assertCountEqual(input_list, chan_list)
|
||||||
|
conf.supybot.networks.test.channels.setValue(orig)
|
@ -1042,8 +1042,6 @@ class IrcTestCase(SupyTestCase):
|
|||||||
repr(c.batch)
|
repr(c.batch)
|
||||||
)
|
)
|
||||||
|
|
||||||
maxDiff = None
|
|
||||||
|
|
||||||
def testBatchNested(self):
|
def testBatchNested(self):
|
||||||
self.irc.reset()
|
self.irc.reset()
|
||||||
logs = textwrap.dedent('''
|
logs = textwrap.dedent('''
|
||||||
@ -1107,6 +1105,42 @@ class IrcTestCase(SupyTestCase):
|
|||||||
self.assertEqual(msg5.tagged('batch'), outer)
|
self.assertEqual(msg5.tagged('batch'), outer)
|
||||||
self.assertEqual(self.irc.state.getParentBatches(msg5), [outer])
|
self.assertEqual(self.irc.state.getParentBatches(msg5), [outer])
|
||||||
|
|
||||||
|
def testBatchError(self):
|
||||||
|
# Checks getting an ERROR message in a batch does not cause issues
|
||||||
|
# due to deinitializing the connection at the same time.
|
||||||
|
self.irc.reset()
|
||||||
|
m1 = ircmsgs.IrcMsg(':host BATCH +name batchtype')
|
||||||
|
self.irc.feedMsg(m1)
|
||||||
|
m2 = ircmsgs.IrcMsg('@batch=name :someuser2 NOTICE * :oh no')
|
||||||
|
self.irc.feedMsg(m2)
|
||||||
|
m3 = ircmsgs.IrcMsg('@batch=name ERROR :bye')
|
||||||
|
self.irc.feedMsg(m3)
|
||||||
|
class Callback(irclib.IrcCallback):
|
||||||
|
batch = None
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
return super().__call__(*args, **kwargs)
|
||||||
|
def name(self):
|
||||||
|
return 'testcallback'
|
||||||
|
def doBatch(self2, irc, msg):
|
||||||
|
self2.batch = msg.tagged('batch')
|
||||||
|
|
||||||
|
# would usually be called by the driver upon reconnect() trigged
|
||||||
|
# by the ERROR:
|
||||||
|
self.irc.reset()
|
||||||
|
|
||||||
|
c = Callback()
|
||||||
|
self.irc.addCallback(c)
|
||||||
|
try:
|
||||||
|
m4 = ircmsgs.IrcMsg(':host BATCH -name')
|
||||||
|
self.irc.feedMsg(m4)
|
||||||
|
finally:
|
||||||
|
self.irc.removeCallback(c.name())
|
||||||
|
self.assertEqual(
|
||||||
|
c.batch,
|
||||||
|
irclib.Batch('name', 'batchtype', (), [m1, m2, m3, m4], None),
|
||||||
|
repr(c.batch)
|
||||||
|
)
|
||||||
|
|
||||||
def testTruncate(self):
|
def testTruncate(self):
|
||||||
self.irc = irclib.Irc('test')
|
self.irc = irclib.Irc('test')
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user