Writing plugs¶
A Plug
class allows a single external network to be represented in a standard form. Such
a class provides message objects from interactions in the network.
Plugs are expected to implement the following core methods, if needed by the network:
Openable.start()
: Make any initial connections, subscribe to events.Openable.stop()
: Close all connections, perform any general cleanup.Plug.put()
: Push new messages into the network, and return their identifiers.
They are also expected to start their own tasks to collect messages from the network, see below.
Hooks and external code may wish to interact deeper with the network – the following utility methods should also be implemented if possible:
user lookups:
Plug.user_from_id()
Plug.user_from_username()
Plug.channel_for_user()
channel properties:
Plug.channel_is_private()
Plug.channel_title()
Plug.channel_link()
Plug.channel_members()
Plug.channel_admins()
channel actions:
Plug.channel_rename()
Plug.channel_invite()
Plug.channel_remove()
Persistent identifiers¶
Each plug should provide a constant identifier Plug.network_id
that uniquely defines
access to the underlying network. Typically this will include a public identifier for the user
account being used to connect. For disconnected instances within a single network, an identifier
for the instance should also be included to avoid ID clashes.
Similarly, each channel yielded by the network needs a Channel.source
, a unique string
wrapper for underlying channel IDs (e.g. if your network uses integers, you’ll need to handle the
boxing and unboxing of these before exposing them to the rest of the system). If a network has no
concept of channels, use a constant-source channel for all messages.
Message queue¶
Messages enter the system from a queue on the plug instance, the input end of which is accessible
via Plug.queue()
. If the network of a plug is backed by an asyncio client, it may be
possible to register an event handler that just calls the queue method on new messages.
For manual message pickup, for example via websockets or long-polling, you’ll need to create your
own background task during Plug.start()
(it must not block the startup – see
asyncio.ensure_future()
), and cancel it during Plug.stop()
.
Locks during sending¶
In order to ensure correctness of message processing by hooks, calls to Plug.send()
must
return with a message ID before the new message is yielded to the system. This is typically needed
if sending or receiving requires multiple API calls (e.g. to upload/download images), as an
asynchronous call to transfer an image would block one method, potentially returning to the other.
An internal lock applies to Plug.put()
and queue retrieval in order to achieve this.