@@ -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. | |||
@@ -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. | |||
@@ -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*. | |||
@@ -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: | |||
@@ -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() | |||