Development¶
Modules¶
Modules can be added by creating a module inside chitanda/modules
. All
modules inside of chitanda/modules
will be dynamically imported upon bot
startup. The name of the module identifies the module in configuration
options. For example, chitanda/modules/catpics.py
, the identifier will be
catpics
.
Modules can contain setup functions, bot hooks, commands, and database migrations.
If a module contains multiple commands, it can be turned into a package. The
package must have an __init__.py
to be dynamically imported. All python
modules inside the package will be imported, and the name of the package
identifies all python modules inside the package in the configuration file.
Setup¶
If an module imported dynamically by the bot has a setup
function, it will
be called on first import. This occurs after the bot’s __init__
finishes
running. The only argument passed to setup
is bot
. This function should
be synchronous.
During setup, functions can be added to the bot’s message_handler
and
response_handler
hooks and routes can be added to the bot’s
web_application
(for webhook support).
Commands¶
To add a command to the bot, decorate a function with the register
decorator, which takes, as an argument, the command trigger (without the
configurable trigger character). Command functions must be asynchonous, and
should have a message
parameter.
Commands can either be coroutines or async generators; both are supported. The coroutines can return a generator as well.
The return value of a command or the values returned when iterating over it can
be in two formats: a str
or a dict
. If returned as a str
, the
return value will be sent to message.target
. If returned as a dict
, the
dict will be directly passed to the listener.message
coroutine. The
dict
must have the keys target
and message
, with other key support
depending on the listener it is passed to.
For example, a simple command to parrot text would be:
@register('parrot')
async def parrot(message):
return message.contents
<user> .parrot squawk
<chitanda> squawk
When a command is called, it is passed a Message
object as its only
argument. A Message
object has the following attributes:
class Message:
bot # The main bot class.
listener # The listener the message originated from.
target # The channel the message was sent to (same as author in PM).
author # The nickname (IRC) or ID of the message sender.
contents # The contents of the message with the trigger stripped out.
private # If the message was sent via PM or in a channel.
There are several decorators in the chitanda.decorators
package which can
be used to decorate commands. Some of these decorators add new instance
variables to the message
object.
Decorators¶
args¶
chitanda.decorators.args
A decorator factory that takes re.Pattern
objects and/or str
regexes as
arguments. When a command is called, message.contents
will be matched with
the regexes in the order that they were passed into args
. If there is a
match, the return value of re.Match.groups()
will be assigned to
message.args
. If there isn’t a match, a BotError
will be raised.
Example:
import re
from chitanda.decorators import args, register
REUSED_REGEX = re.compile(r'a regex')
@register('generic_command')
@args(r'([^ ]+)', REUSED_REGEX)
async def call(message):
print(message.args)
admin_only¶
chitanda.decorators.admin_only
Compares the sender of the command against the admin list. If the sender is an
admin, the command will be called. Otherwise, a BotError
will be raised.
auth_only¶
chitanda.decorators.auth_only
Check’s a user’s authorization before calling the command. This is primarily
geared towards IRC, where it mandates NickServ identification. In services that
require accounts to use, the command will always be called. If the user is
found to be authorized, their account name/username will be assigned to
message.username
. If they are not authorized, a BotError
will be raised.
channel_only¶
chitanda.decorators.channel_only
Requires that the message be sent in a channel, otherwise a BotError
will
be raised.
private_message_only¶
chitanda.decorators.private_message_only
Requires that the command be sent via PM, otherwise a BotError
will be
raised.
allowed_listeners¶
chitanda.decorators.allowed_listeners
A decorator factory that restricts the command to certain listeners. Each
allowed listener type should be passed in as a separate argument. If the
command is called on a disallowed listener, a BotError
will be raised.
Commands that are not allowed on a listener will not be shown in that
listener’s help
command.
Example:
from chitanda.decorators import register, allowed_listeners
from chitanda.listeners import IRCListener
@register('quit')
@allowed_listeners(IRCListener)
async def call(message):
await listener.raw('QUIT\r\n')
Decorating Async Generators¶
When decorating an async generator, the admin_only
and auth_only
decorators must visually come last, i.e. decorate the function first. this is
because they have different behaviors for async generators vs regular
coroutines and detection of a decorated async generator isn’t accurate
from chitanda.decorators import args, auth_only, register
# Good
@register('pics cats')
@args(r'$')
@auth_only
async def call(message):
for cat in _get_cat_pics():
yield cat
# Bad
@register('pics cats')
@auth_only
@args(r'$')
async def call(message):
for cat in _get_cat_pics():
yield cat
Hooks¶
Hooks enable modules to process messages before the command is called and responses before they are sent to the recipient.
Pre-command hooks must be coroutines or async generators and take a message
parameter, which is the Message
object. If a value is returned from the
hook, it will be handled the same way a return value from a command call would
be handled. To add a pre-command hook, append the hook function to the
bot.message_handlers
list.
Pre-response hooks must be coroutines and take four parameters:
bot, listener, target, response
. Their return value is discarded.
The response
argument will always be a dictionary with target
and
message
keys.
Database Migrations¶
Modules with database migrations must be python packages. Inside the package,
the existence of a directory named migrations
indicates that the module has
database migrations to run. Migrations should be numerically named .sql
files in the format 0001.sql
, 0002.sql
. They will be ran in the order
of their ascending numerical identifiers.
The migrations that have been ran will be recorded in the database as to not re-run them.