pyircbot/docs/module_guide/_module_guide.rst

216 lines
7.5 KiB
ReStructuredText
Raw Normal View History

2015-08-08 16:29:23 -07:00
************************
Module Developer's Guide
2015-08-08 16:29:23 -07:00
************************
2015-08-08 16:29:23 -07:00
Modules consist of a single python file, named for the module. For example, EchoExample.py
Getting Started
===============
2017-03-28 16:48:31 -07:00
All modules should inherit from the base class
:doc:`ModuleBase </api/modulebase>`, and should be named matching their python
2015-08-08 16:29:23 -07:00
file's name.
.. code-block:: python
2017-07-02 15:24:14 -07:00
from pyircbot.modulebase import ModuleBase, hook, command
2015-08-08 16:29:23 -07:00
class EchoExample(ModuleBase):
The class's ``__init__`` method accepts 2 args - a reference to the bot's API
and what the bot has decided to name this module. These are passed to
2017-03-28 16:48:31 -07:00
ModuleBase. Module's init method should be as quick as possible. The bot loads
2017-05-14 12:44:14 -07:00
modules sequentially on startup and you should avoid long operations here as it
will undesirably slow bot startup time.
2015-08-08 16:29:23 -07:00
.. code-block:: python
def __init__(self, bot, moduleName):
2015-08-08 16:50:26 -07:00
ModuleBase.__init__(self, bot, moduleName)
2015-08-08 16:29:23 -07:00
2017-03-28 16:48:31 -07:00
If your module has a config file - EchoExample.json - it will be automatically
loaded on module startup. It can be manually reloaded by
2015-08-08 16:29:23 -07:00
calling :py:meth:`pyircbot.modulebase.ModuleBase.loadConfig`:
.. code-block:: python
print(self.config)
2017-05-14 12:44:14 -07:00
self.loadConfig() # Manually reload config
2015-08-08 16:29:23 -07:00
2017-05-14 12:44:14 -07:00
Any other setup code should be placed in ``__init__``. Note that ``__init__``
will be executed when the bot is starting up and before the irc connection is
opened.
2015-08-08 16:29:23 -07:00
2017-05-14 12:44:14 -07:00
Adding interaction
------------------
2015-08-08 16:29:23 -07:00
2017-05-14 12:44:14 -07:00
In order to make your module respond to various IRC commands, pyircbot uses a
system of "hooks", which can be defined using decorators or programmatically.
Note: in this case, "commands" refers to IRC protocol commands such as PRIVMSG,
KICK, JOIN, etc. Pyircbot also provides some meta-events that are accessed in
2017-07-02 15:24:14 -07:00
the same way. The complete list of supported hooks can be seen in the source
2017-05-14 12:44:14 -07:00
of :py:meth:`pyircbot.irccore.IRCCore.initHooks`.
2015-08-08 16:29:23 -07:00
2017-05-14 12:44:14 -07:00
The easiest method is to use the ``hook`` decorator on any function your want
called when an IRC command is received.
2015-08-08 16:29:23 -07:00
.. code-block:: python
2017-05-14 12:44:14 -07:00
@hook("PRIVMSG")
2015-08-08 22:50:04 -07:00
def echo(self, event):
2015-08-08 16:29:23 -07:00
2017-03-28 16:48:31 -07:00
The handler is passed and IRCEvent object containing the data sent by the irc
2017-07-02 15:24:14 -07:00
server. The values of these are can vary, but the format is always the same.
2015-08-08 22:50:04 -07:00
2017-03-28 16:48:31 -07:00
``event.args`` is the list of arguments the IRC server sent. ``event.prefix``
is the sender, parsed. ``trailing`` is arbitrary data associated
with the event. In the case of PRIVMSG: args has one entry - the channel name
2015-08-08 22:50:04 -07:00
or nick the message was in/from.
2017-03-28 16:48:31 -07:00
Prefix is an ``UserPrefix`` object with the properties ``event.prefix.nick``,
2015-08-08 22:50:04 -07:00
``event.prefix.username``, ``event.prefix.hostname``, and the original unparsed
prefix, ``event.prefix.str``.
2017-03-28 16:48:31 -07:00
Prefix may also be a ``ServerPrefix`` object, if the hook is for an IRC method
that interacts with the server directly, such as PING. It would have the
2015-08-08 22:50:04 -07:00
properties ``event.prefix.hostname`` and ``event.prefix.str``.
2017-11-27 18:58:20 -08:00
There are more hook-like decorators. See @regex and @command.
2015-08-08 22:50:04 -07:00
Since the module described above echos messages, let's do that:
2015-08-08 16:29:23 -07:00
.. code-block:: python
2015-08-08 22:50:04 -07:00
self.bot.act_PRIVMSG(event.args[0], event.trailing)
2015-08-08 16:29:23 -07:00
2017-03-28 16:48:31 -07:00
This sends a PRIVMSG to the originating channel or nick, with the same msg
content that was received.
2015-08-08 16:29:23 -07:00
2017-07-02 15:24:14 -07:00
Alternatively, if your module needs to respond to chat-based commands, a
similar decorator :py:class:`pyircbot.modulebase.command`. can be used:
.. code-block:: python
@command("echo")
def echo2(self, cmd, msg):
# If the message was ".echo bob asdf", cmd.args would look like:
# ["bob", "asdf"]
self.bot.act_PRIVMSG(msg.args[0], msg.trailing)
2015-08-08 16:29:23 -07:00
Beyond this, a module's class can import or do anything python can to deliver
2017-03-28 16:48:31 -07:00
responses. For modules that use threads or connect to external services, a
shutdown handler is needed to ensure a clean shutdown.
2015-08-08 16:29:23 -07:00
.. code-block:: python
def ondisable(self):
"""Called when the module should be disabled. Your module should do any sort
of clean-up operations here like ending child threads or saving data files.
"""
pass
2015-08-08 16:50:26 -07:00
EchoExample module
------------------
2017-05-14 12:44:14 -07:00
This is the snippets above combined into a usable module.
2015-08-08 16:50:26 -07:00
.. code-block:: python
2017-05-14 12:44:14 -07:00
from pyircbot.modulebase import ModuleBase, hook
2017-03-28 16:48:31 -07:00
2015-08-08 16:50:26 -07:00
class EchoExample(ModuleBase):
def __init__(self, bot, moduleName):
ModuleBase.__init__(self, bot, moduleName)
print(self.config)
2017-03-28 16:48:31 -07:00
2017-05-14 12:44:14 -07:00
@hook("PRIVMSG")
2015-08-08 22:50:04 -07:00
def echo(self, event):
self.bot.act_PRIVMSG(event.args[0], event.trailing)
2017-03-28 16:48:31 -07:00
2015-08-08 16:50:26 -07:00
def ondisable(self):
print("I'm getting unloaded!")
In usage:
.. code-block:: text
4:40:17 PM <Beefpile> test
4:40:17 PM <derpbot420> test
2015-08-08 22:50:04 -07:00
2015-08-08 16:29:23 -07:00
Advanced Usage
==============
Check out the helper methods that :doc:`ModuleBase </api/modulebase>` offers.
Refer to existing modules for helper methods from elsewhere in PyIRCBot.
:doc:`PyIRCBot </api/pyircbot>` has some useful methods:
- :py:meth:`pyircbot.pyircbot.PyIRCBot.messageHasCommand`
- :py:meth:`pyircbot.pyircbot.PyIRCBot.getDataPath`
- :py:meth:`pyircbot.pyircbot.PyIRCBot.getmodulebyname`
2017-03-28 16:48:31 -07:00
:doc:`GameBase </api/modules/gamebase>` is a good example of the basic code
structure a IRC game could follow, designed so different channels would have
2015-08-11 15:24:02 -07:00
separate game instances.
2015-08-08 16:29:23 -07:00
Inter-module Communication
--------------------------
In the list above, :py:meth:`pyircbot.pyircbot.PyIRCBot.getmodulebyname` can be
2017-03-28 16:48:31 -07:00
used to retrieve a reference to another loaded module. This is simply the
2015-08-08 16:29:23 -07:00
instance of the other module's class.
But what if you wanted a module to find another by type? For example, a module
2017-03-28 16:48:31 -07:00
providing a cache API could provide a service called "cache". Modules that use
2015-08-08 16:29:23 -07:00
a cache API to function could find this module - or another that's functionally
equivalent.
Modules providing a service state so like:
.. code-block:: python
def __init__(self, bot, moduleName):
2015-08-08 16:50:26 -07:00
ModuleBase.__init__(self, bot, moduleName)
2015-08-08 16:29:23 -07:00
self.services=["cache"]
2017-03-28 16:48:31 -07:00
Then, another module can find this one by using either
2015-08-08 16:29:23 -07:00
:py:meth:`pyircbot.pyircbot.PyIRCBot.getmodulesbyservice` or
2017-03-28 16:48:31 -07:00
:py:meth:`pyircbot.pyircbot.PyIRCBot.getBestModuleForService` and passing the
name "cache". The first returns a list of all modules offering the "cache"
service, the second returns an arbitrary module returning cache if more that
2015-08-08 16:29:23 -07:00
one is found.
**PyIRCBot does NOT automatically handle inter-module communication. Meaning,
2017-03-28 16:48:31 -07:00
modules providing a service should be loaded before modules requiring the
2015-08-08 16:29:23 -07:00
service. Modules using a service MUST BE unloaded before the service module
is unloaded.**
2017-12-03 21:09:31 -08:00
Testing
-------
Good modules should be fully tested. PyIRCBot's test suite provides fixtures
for testing any module:
- ``fakebot``: a slimmed-down bot backend for light module testing
- ``livebot``: a fully fledged instance of the bot connected to a real irc server
Except for the most extreme cases, most modules can be tested with ``fakebot``.
It allows feeding lines and checking internal state. In the test suite,
``tests/modules/test_calc.py`` is a good example of use.
However, ``livebot`` exists mainly for testing network-level things, like
reconnecting after a connection drop, etc. If your module deals with this level
of operations, ``livebot`` might be right. Otherwise. no.
Tests (for modules) should be placed in ``./tests/modules/`` and be named like
``test_modulename.py``.
To run the test suite, call the helper script in the root of the repository:
- ``./run-tests.sh``
Or an individual test:
- ``PYTHONPATH=. py.test tests/modules/test_mymodule.py -s -k test_function_name``