sopel.plugins.capabilities#

Capability Requests management for plugins.

New in version 8.0.

Important

This is all relatively new. Its usage and documentation is for Sopel core development and advanced developers. It is subject to rapid changes between versions without much (or any) warning.

Do not build your plugin based on what is here, you do not need to.

class sopel.plugins.capabilities.Manager#

Manager of plugins’ capability requests.

Whenever a plugin declares a capability request (through sopel.plugin.capability), the bot will register the request with this manager.

The bot is responsible to call the manager at the appropriate time, and the manager will store requests’ state and handle acknowledgement, by following this workflow:

  1. the bot will register plugins’ requests with the register() method

  2. the bot will request the list of available capabilities with the CAP LS subcommand

  3. upon receiving the list, it’ll use the request_available() method to let the manager send the required CAP REQ messages.

  4. when the server ACK a request, the bot will call the acknowledge() method; when the server NAK a request, the bot will call the deny() method

  5. once all requests are handled (either directly or after calling the resume() method), the bot will send the CAP END message to end capability negotiation

acknowledge(
bot: SopelWrapper,
cap_req: tuple[str, ...],
) list[tuple[bool, CapabilityNegotiation | None]] | None#

Acknowledge a capability request and execute handlers.

Parameters:
  • bot – bot instance to manage the capabilities for

  • cap_req – the capability request from CAP ACK :<cap_req>

Returns:

a list of results and statuses if the capability was requested

This acknowledges a capability request and executes its callbacks from plugins. It returns the result, as a list of 2-value tuples: the first value tells if the callback is done with the processing, and the second is the returned value.

If the capability request was denied before, it is now considered acknowledged instead.

If the capability wasn’t requested, the result will be None.

property acknowledged: frozenset[tuple[str, ...]]#

Set of acknowledged capability requests.

Each element is a capability request as a tuple of capability names:

>>> manager.acknowledged
{('cap1',), ('cap2', 'cap3')}

An acknowledged request is a registered and requested request for which the bot received a CAP ACK message.

Only requested requests can be acknowledged.

property denied: frozenset[tuple[str, ...]]#

Set of denied capability requests.

Each element is a capability request as a tuple of capability names:

>>> manager.denied
{('cap1',), ('cap2', 'cap3')}

A denied request is a registered and requested request for which the bot received a CAP NAK message.

Only requested requests can be denied.

deny(
bot: SopelWrapper,
cap_req: tuple[str, ...],
) list[tuple[bool, CapabilityNegotiation | None]] | None#

Deny a capability request and execute handlers.

Parameters:
  • bot – bot instance to manage the capabilities for

  • cap_req – the capability request from CAP NAK :<cap_req>

This denies a capability request and executes its callbacks from plugins. It returns the result, as a list of 2-value tuples: the first value tells if the callback is done with the processing, and the second is the returned value.

If the capability request was acknowledged before, it is now considered denied instead.

If the capability wasn’t requested, the result will be None.

get(
cap_req: tuple[str, ...],
*,
plugins: list[str] | tuple[str, ...] | set[str] = (),
) Generator[tuple[str, capability], None, None]#

Retrieve the registered request handlers for a capability request.

Parameters:

cap_req – the capability request to retrieve handlers for

Returns:

yield 2-value tuples with (plugin name, capability)

is_acknowledged(request: Iterable[str]) bool#

Tell if a capability request is acknowledged.

Parameters:

request – a set of capabilities that form a capability request together; this can be any iterable

property is_complete: bool#

Tell if the capability negotiation is complete.

When capability negotiation is complete, the bot can send CAP END to notify the server that negotiation is complete.

The capability negotiation is complete when all capability requests have been either acknowledged or denied successfuly (directly or by calling the resume() method).

is_denied(request: Iterable[str]) bool#

Tell if a capability request is denied.

Parameters:

request – a set of capabilities that form a capability request together; this can be any iterable

is_registered(request: Iterable[str]) bool#

Tell if a capability request is registered.

Parameters:

request – a set of capabilities that form a capability request together; this can be any iterable

is_requested(request: Iterable[str]) bool#

Tell if a capability request is requested.

Parameters:

request – a set of capabilities that form a capability request together; this can be any iterable

register(plugin_name: str, request: capability) None#

Register a capability request for plugin_name.

Parameters:

request – the capability request to register for later

Raises:

RuntimeError – when the capability request is too long for a single CAP REQ and CAP * ACK

Once registered, the capability request can be requested by the bot. A registered request appears in registered:

>>> from sopel import plugin
>>> request = plugin.capability('cap1')
>>> manager.register('coretasks', request)
>>> ('cap1',) in manager.registered
True
>>> manager.is_registered(('cap1',))
True

It is not, however, directly requested:

>>> manager.is_requested(('cap1',))
False

See request_available() to automatically request capabilities advertised by the server.

Warning

Sopel cannot accept a request that is too long, because it does not know how to handle a multi-line ACK, and it would not know how to call back the appropriate capability handler.

property registered: frozenset[tuple[str, ...]]#

Set of registered capability requests.

Each element is a capability request as a tuple of capability names:

>>> manager.registered
{('cap1',), ('cap2', 'cap3')}

A registered request is a request wanted by a plugin. The request may or may not be requested, acknowledged, or denied.

request_available(
bot: Sopel,
available_capabilities: Iterable[str],
) None#

Request available capabilities.

Parameters:
  • bot – the bot instance used to send capability requests

  • available_capabilities – available capabilities

This sends CAP REQ commands for requests that can be made, i.e. all the requested capabilities (with or without prefix) must be available for Sopel to send the request.

Requests made are stored as requested; others are ignored:

>>> manager.register('example', 'cap1')
>>> manager.register('example', 'cap2')
>>> manager.request_available(bot, ('cap1', 'cap3'))
>>> manager.is_requested(('cap1',))
True
>>> manager.is_requested(('cap2',))
False
>>> manager.is_requested(('cap3',))
False

Important

The capability request ('cap1', '-cap2') means “enable cap1 and disable cap2”, and the request will be acknowledged or denied at once. If the server doesn’t advertise any of these capabilities, the client should not send a request.

As a result, Sopel will send a request to enable or disable a capability only if it is advertised first. If a request is never made, its callback will never be called, because there won’t be any related ACK/NAK message from the server.

Plugin authors should not use the request’s callback to activate or deactivate features, and instead check the bot’s capabilities every time they need to.

The only exception to that is when the plugin needs to perform an operation while negotiating the capabilities (such as SASL auth).

property requested: frozenset[tuple[str, ...]]#

Set of requested capability requests.

Each element is a capability request as a tuple of capability names:

>>> manager.requested
{('cap1',), ('cap2', 'cap3')}

A requested request is a registered request for which the bot sent a CAP REQ message to the server. The request may or may not be acknowledged or denied.

Only registered requests can be requested.

resume(request: Iterable[str], plugin_name: str) tuple[bool, bool]#

Resume the registered plugin capability request.

Returns:

a 2-value tuple with (was completed, is completed)

The capability request, for that plugin, will be marked as done, and the result will be about the capability negotiation process:

  • was it already completed before the resume?

  • is it completed now?

If the capability request cannot be found for that plugin, the result value remains the same (it stays incomplete or complete)

Important

When a request’s callback returns CONTINUE, this method must be called later (once the plugin has finished its job) or the bot will never send the CAP END command and hang forever.

See also

Plugins can use the method resume_capability_negotiation() from the bot to resume and automatically send CAP END when necessary.