Procházet zdrojové kódy

Command.setup() like Task.setup(); config.commands like config.tasks

tags/v0.1^2
Ben Kurtovic před 12 roky
rodič
revize
cfdfc49d78
5 změnil soubory, kde provedl 92 přidání a 35 odebrání
  1. +18
    -5
      README.rst
  2. +23
    -4
      docs/customizing.rst
  3. +13
    -3
      earwigbot/commands/__init__.py
  4. +13
    -6
      earwigbot/commands/geolocate.py
  5. +25
    -17
      earwigbot/config.py

+ 18
- 5
README.rst Zobrazit soubor

@@ -138,9 +138,9 @@ The most useful attributes are:

`earwigbot.config.BotConfig`_ stores configuration information for the bot. Its
docstring explains what each attribute is used for, but essentially each "node"
(one of ``config.components``, ``wiki``, ``tasks``, ``irc``, and ``metadata``)
maps to a section of the bot's ``config.yml`` file. For example, if
``config.yml`` includes something like::
(one of ``config.components``, ``wiki``, ``irc``, ``commands``, ``tasks``, and
``metadata``) maps to a section of the bot's ``config.yml`` file. For example,
if ``config.yml`` includes something like::

irc:
frontend:
@@ -158,7 +158,8 @@ Custom IRC commands
~~~~~~~~~~~~~~~~~~~

Custom commands are subclasses of `earwigbot.commands.BaseCommand`_ that
override ``BaseCommand``'s ``process()`` (and optionally ``check()``) methods.
override ``BaseCommand``'s ``process()`` (and optionally ``check()`` or
``setup()``) methods.

``BaseCommand``'s docstrings should explain what each attribute and method is
for and what they should be overridden with, but these are the basics:
@@ -171,6 +172,12 @@ for and what they should be overridden with, but these are the basics:
messages only), and ``"join"`` (for when a user joins a channel). See the
afc_status_ plugin for a command that responds to other hook types.

- Method ``setup()`` is called *once* with no arguments immediately after the
command is first loaded. Does nothing by default; treat it like an
``__init__()`` if you want (``__init__()`` does things by default and a
dedicated setup method is often easier than overriding ``__init__()`` and
using ``super``).

- Method ``check()`` is passed a ``Data`` [2]_ object, and should return
``True`` if you want to respond to this message, or ``False`` otherwise. The
default behavior is to return ``True`` only if ``data.is_command`` is
@@ -191,6 +198,12 @@ for and what they should be overridden with, but these are the basics:
``self.action(chan_or_user, msg)``, ``self.notice(chan_or_user, msg)``,
``self.join(chan)``, and ``self.part(chan)``.

Commands have access to ``config.commands[command_name]`` for config
information, which is a node in ``config.yml`` like every other attribute of
``bot.config``. This can be used to store, for example, API keys or SQL
connection info, so that these can be easily changed without modifying the
command itself.

It's important to name the command class ``Command`` within the file, or else
the bot might not recognize it as a command. The name of the file doesn't
really matter and need not match the command's name, but this is recommended
@@ -243,7 +256,7 @@ and what they should be overridden with, but these are the basics:

Tasks have access to ``config.tasks[task_name]`` for config information, which
is a node in ``config.yml`` like every other attribute of ``bot.config``. This
can be used to store, for example, edit summaries, or templates to append to
can be used to store, for example, edit summaries or templates to append to
user talk pages, so that these can be easily changed without modifying the task
itself.



+ 23
- 4
docs/customizing.rst Zobrazit soubor

@@ -58,8 +58,13 @@ The most useful attributes are:

:py:class:`earwigbot.config.BotConfig` stores configuration information for the
bot. Its docstrings explains what each attribute is used for, but essentially
each "node" (one of :py:attr:`config.components`, :py:attr:`wiki`,
:py:attr:`tasks`, :py:attr:`tasks`, or :py:attr:`metadata`) maps to a section
each "node" (one of :py:attr:`config.components
<earwigbot.config.BotConfig.components>`,
:py:attr:`~earwigbot.config.BotConfig.wiki`,
:py:attr:`~earwigbot.config.BotConfig.irc`,
:py:attr:`~earwigbot.config.BotConfig.commands`,
:py:attr:`~earwigbot.config.BotConfig.tasks`, or
:py:attr:`~earwigbot.config.BotConfig.metadata`) maps to a section
of the bot's :file:`config.yml` file. For example, if :file:`config.yml`
includes something like::

@@ -81,7 +86,8 @@ Custom IRC commands
Custom commands are subclasses of :py:class:`earwigbot.commands.BaseCommand`
that override :py:class:`~earwigbot.commands.BaseCommand`'s
:py:meth:`~earwigbot.commands.BaseCommand.process` (and optionally
:py:meth:`~earwigbot.commands.BaseCommand.check`) methods.
:py:meth:`~earwigbot.commands.BaseCommand.check` or
:py:meth:`~earwigbot.commands.BaseCommand.setup`) methods.

:py:class:`~earwigbot.commands.BaseCommand`'s docstrings should explain what
each attribute and method is for and what they should be overridden with, but
@@ -97,6 +103,13 @@ these are the basics:
a user joins a channel). See the afc_status_ plugin for a command that
responds to other hook types.

- Method :py:meth:`~earwigbot.commands.BaseCommand.setup` is called *once* with
no arguments immediately after the command is first loaded. Does nothing by
default; treat it like an :py:meth:`__init__` if you want
(:py:meth:`~earwigbot.tasks.BaseCommand.__init__` does things by default and
a dedicated setup method is often easier than overriding
:py:meth:`~earwigbot.tasks.BaseCommand.__init__` and using :py:obj:`super`).

- Method :py:meth:`~earwigbot.commands.BaseCommand.check` is passed a
:py:class:`~earwigbot.irc.data.Data` [1]_ object, and should return ``True``
if you want to respond to this message, or ``False`` otherwise. The default
@@ -128,6 +141,12 @@ these are the basics:
<earwigbot.irc.connection.IRCConnection.join>`, and
:py:meth:`part(chan) <earwigbot.irc.connection.IRCConnection.part>`.

Commands have access to :py:attr:`config.commands[command_name]` for config
information, which is a node in :file:`config.yml` like every other attribute
of :py:attr:`bot.config`. This can be used to store, for example, API keys or
SQL connection info, so that these can be easily changed without modifying the
command itself.

It's important to name the command class :py:class:`Command` within the file,
or else the bot might not recognize it as a command. The name of the file
doesn't really matter and need not match the command's name, but this is
@@ -190,7 +209,7 @@ are the basics:

Tasks have access to :py:attr:`config.tasks[task_name]` for config information,
which is a node in :file:`config.yml` like every other attribute of
:py:attr:`bot.config`. This can be used to store, for example, edit summaries,
:py:attr:`bot.config`. This can be used to store, for example, edit summaries
or templates to append to user talk pages, so that these can be easily changed
without modifying the task itself.



+ 13
- 3
earwigbot/commands/__init__.py Zobrazit soubor

@@ -49,9 +49,10 @@ class BaseCommand(object):

This is called once when the command is loaded (from
:py:meth:`commands.load() <earwigbot.managers._ResourceManager.load>`).
*bot* is out base :py:class:`~earwigbot.bot.Bot` object. Generally you
shouldn't need to override this; if you do, call
``super(Command, self).__init__()`` first.
*bot* is out base :py:class:`~earwigbot.bot.Bot` object. Don't override
this directly; if you do, remember to place
``super(Command, self).__init()`` first. Use :py:meth:`setup` for
typical command-init/setup needs.
"""
self.bot = bot
self.config = bot.config
@@ -67,6 +68,15 @@ class BaseCommand(object):
self.mode = lambda t, level, msg: self.bot.frontend.mode(t, level, msg)
self.pong = lambda target: self.bot.frontend.pong(target)

self.setup()

def setup(self):
"""Hook called immediately after the command is loaded.

Does nothing by default; feel free to override.
"""
pass

def check(self, data):
"""Return whether this command should be called in response to *data*.



+ 13
- 6
earwigbot/commands/geolocate.py Zobrazit soubor

@@ -29,6 +29,15 @@ class Command(BaseCommand):
"""Geolocate an IP address (via http://ipinfodb.com/)."""
name = "geolocate"

def setup(self):
self.config.decrypt(self.config.commands, (self.name, "apiKey"))
try:
self.key = self.config.commands[self.name]["apiKey"]
except KeyError:
self.key = None
log = 'Cannot use without an API key for http://ipinfodb.com/ stored as config.commands["{0}"]["apiKey"]'
self.logger.warn(log.format(self.name))

def check(self, data):
commands = ["geolocate", "locate", "geo", "ip"]
return data.is_command and data.command in commands
@@ -38,18 +47,16 @@ class Command(BaseCommand):
self.reply(data, "please specify an IP to lookup.")
return

try:
key = config.tasks[self.name]["apiKey"]
except KeyError:
msg = 'I need an API key for http://ipinfodb.com/ stored as \x0303config.tasks["{0}"]["apiKey"]\x0301.'
log = 'Need an API key for http://ipinfodb.com/ stored as config.tasks["{0}"]["apiKey"]'
if not self.key:
msg = 'I need an API key for http://ipinfodb.com/ stored as \x0303config.commands["{0}"]["apiKey"]\x0301.'
log = 'Need an API key for http://ipinfodb.com/ stored as config.commands["{0}"]["apiKey"]'
self.reply(data, msg.format(self.name) + ".")
self.logger.error(log.format(self.name))
return

address = data.args[0]
url = "http://api.ipinfodb.com/v3/ip-city/?key={0}&ip={1}&format=json"
query = urllib2.urlopen(url.format(key, address)).read()
query = urllib2.urlopen(url.format(self.key, address)).read()
res = json.loads(query)

try:


+ 25
- 17
earwigbot/config.py Zobrazit soubor

@@ -48,8 +48,9 @@ class BotConfig(object):
- :py:attr:`path`: path to the bot's config file
- :py:attr:`components`: enabled components
- :py:attr:`wiki`: information about wiki-editing
- :py:attr:`tasks`: information for bot tasks
- :py:attr:`irc`: information about IRC
- :py:attr:`commands`: information about IRC commands
- :py:attr:`tasks`: information for bot tasks
- :py:attr:`metadata`: miscellaneous information
- :py:meth:`schedule`: tasks scheduled to run at a given time

@@ -69,12 +70,13 @@ class BotConfig(object):

self._components = _ConfigNode()
self._wiki = _ConfigNode()
self._tasks = _ConfigNode()
self._irc = _ConfigNode()
self._commands = _ConfigNode()
self._tasks = _ConfigNode()
self._metadata = _ConfigNode()

self._nodes = [self._components, self._wiki, self._tasks, self._irc,
self._metadata]
self._nodes = [self._components, self._wiki, self._irc, self._commands,
self._tasks, self._metadata]

self._decryptable_nodes = [ # Default nodes to decrypt
(self._wiki, ("password",)),
@@ -196,16 +198,21 @@ class BotConfig(object):
return self._wiki

@property
def tasks(self):
"""A dict of information for bot tasks."""
return self._tasks

@property
def irc(self):
"""A dict of information about IRC."""
return self._irc

@property
def commands(self):
"""A dict of information for IRC commands."""
return self._commands

@property
def tasks(self):
"""A dict of information for bot tasks."""
return self._tasks

@property
def metadata(self):
"""A dict of miscellaneous information."""
return self._metadata
@@ -225,14 +232,14 @@ class BotConfig(object):
user. If there is no config file at all, offer to make one, otherwise
exit.

Data from the config file is stored in five
Data from the config file is stored in six
:py:class:`~earwigbot.config._ConfigNode`\ s (:py:attr:`components`,
:py:attr:`wiki`, :py:attr:`tasks`, :py:attr:`irc`, :py:attr:`metadata`)
for easy access (as well as the lower-level :py:attr:`data` attribute).
If passwords are encrypted, we'll use :py:func:`~getpass.getpass` for
the key and then decrypt them. If the config is being reloaded,
encrypted items will be automatically decrypted if they were decrypted
earlier.
:py:attr:`wiki`, :py:attr:`irc`, :py:attr:`commands`, :py:attr:`tasks`,
:py:attr:`metadata`) for easy access (as well as the lower-level
:py:attr:`data` attribute). If passwords are encrypted, we'll use
:py:func:`~getpass.getpass` for the key and then decrypt them. If the
config is being reloaded, encrypted items will be automatically
decrypted if they were decrypted earlier.
"""
if not path.exists(self._config_path):
print "Config file not found:", self._config_path
@@ -246,8 +253,9 @@ class BotConfig(object):
data = self._data
self.components._load(data.get("components", {}))
self.wiki._load(data.get("wiki", {}))
self.tasks._load(data.get("tasks", {}))
self.irc._load(data.get("irc", {}))
self.commands._load(data.get("commands", {}))
self.tasks._load(data.get("tasks", {}))
self.metadata._load(data.get("metadata", {}))

self._setup_logging()


Načítá se…
Zrušit
Uložit