Skip to content

Plugin Authoring (2.4 and above)

endofline edited this page Aug 8, 2015 · 31 revisions

The "Modern" Plugin Patterns

Bot framework 2.4 (and above) supports 2 kinds of plugin patterns - the function-based modern pattern, and decorator-based modern-alternate pattern.

The modern-alternate pattern is fully compatible with command and handler code from the original hangupsbot.

Developer Guide: Modern Pattern

There is a [separate article on porting/converting pre-2.4 plugins] (Migrating-Pre-2.4-Plugins).

This section addresses things you can do in a modern pattern plugin:

  1. Perform initialisation tasks during plugin startup
  2. Register admin and non-admin commands (i.e. /bot command)
  3. [ ver >= 2.7 ] Optionally register [access control tags] (https://github.com/hangoutsbot/hangoutsbot/wiki/Access-Control-with-Tags) for commands
  4. Register handlers to respond to external user actions and bot events
  5. Share plugin functionality with other plugins

Items 1, 2 and 4 are demonstrated in the [example plugin pattern] (https://gist.github.com/endofline/03a6318aef18160c2420#file-example_modern-py)

Items 3 and 5 are considered advanced functionality and are rarely used in most plugins.

Plugin Initialisation

The function _initialise() (or its alternate spelling _initialize) is called when a plugin is loaded. A reference to the hangupsbot object will be passed allowing for additional actions to be performed using bot functionality, such as reading and writing config.json and memory.json.

Registering /bot Commands

import plugins

plugins.register_user_command(list[str])
plugins.register_admin_command(list[str])

e.g.

import plugins

def _initialise(bot):
    plugins.register_user_command(["helloworld", "goodbyeworld"])

def helloworld(bot, event, *args):
    print("hello world!")

def goodbyeworld(bot, event, *args):
    print("goodbye world!")

Command prototype: custom_command(bot, event, *args) - as shown above

A command registered with both functions will always default to an admin-only command.

Tags Registration (Optional)

[ ver >= 2.7 ] The optional tags parameter is available for both register_user_command and register_admin_command:

plugins.register_user_command([commandlist], tags=[tagslist])
plugins.register_admin_command([commandlist], tags=[tagslist])

Adding tagging support to a plugin gives bot administrators additional control over which user can access and execute commands. For more information, please see the separate article on Access Control with Tags.

Note: You can use named presets for the tagslist in the same way that [plugins.tags.auto-register] (https://github.com/hangoutsbot/hangoutsbot/wiki/Customising-Access-Control#pluginstagsauto-register) uses them to dynamically generate different tags for each command.

Registering Handlers

import plugins

plugins.register_handler(function, type="message", priority=50)

e.g.

import plugins

def _initialise(bot):
    plugins.register_handler(_got_a_message, type="message", priority=50)

def _got_a_message(bot, event, command):
    print("something happened")
Supported Events/Handlers
  • message - fires on a message received in chat (except for bot's own messages)
    • default handler if type not specified
    • handler prototype: on_receive(bot, event, command)
  • rename - fires on chat rename event
    • handler prototype: on_rename(bot, event, command)
  • membership - fires when users join or leave the chat
    • handler prototype: on_membership(bot, event, command)
  • sending - modification of bot messages prior to being sent
    • handler prototype: on_send(bot, broadcast_list, context)
    • broadcast_list is a tuple consisting of (conversation_id, chat_segments)
    • LOW-LEVEL: The functionality provided by this handler is only used by complex plugins.
  • allmessages - fires on all messages (including bot's own)
    • handler prototype: on_receive(bot, event, command)
    • LOW-LEVEL: The functionality provided by this handler is only used by complex plugins.
    • WARNING: Misuse of this handler can result in an infinite message loop in the scenario of a bot continuously responding to its own sent messages.
  • typing - fires on all typing notification (including bot's own)
    • handler prototype: on_typing(bot, event, command)
    • event.from_bot will be True if the bot fired this event
    • event.conv_event has a different data structure, see typing_notification in the [state_update reference] (hangups-state_update-dumps#typing)
  • watermark - fires on all watermark updates (including bot's own)
    • handler prototype: on_watermark(bot, event, command)
    • event.from_bot will be True if the bot fired this event
    • event.conv_event has a different data structure, see watermark_notification in the [state_update reference] (hangups-state_update-dumps#watermark)
  • call - fires on video/voice call starting or ending (including bot's own)
    • handler prototype: on_call(bot, event, command)
    • event.from_bot will be True if the bot fired this event
    • event.conv_event is similar to standard message events, with extra data in hangout_event as indicated in the [state_update reference] (hangups-state_update-dumps#video-hangout)

Handler priority determines the evaluation sequence of each handler per event. The recommended range is 1 to 100 - with 1 being highest priority (evaluated first) and 100 the lowest priority (evaluated last).

Sharing Plugin Functionality

bot.register_shared("<id>", object, forgiving=False)
bot.call_shared("<id>", object)
bot.call_shared("<id>", object, <optional parameter list>)

e.g.

Plugin A: Register callable

def _initialise(bot):
    bot.register_shared("plugin_a._send_alert", _send_alert)

def _send_alert(bot, alert_text)
    print(alert_text)

Plugin B: Call shared function

def function(bot, event, *args):
    bot.call_shared("plugin_a._send_alert", bot, "Hello World")

Registers an object (usually a shared function) for usage by other plugins. It does not matter what the name of <id> is so long as another plugin is not registering another object with the same <id>. If <id> already exists and forgiving=False (default), the bot will raise a RuntimeError exception during registration.

To call/retrieve the shared object, use bot.call_shared("<id>"). This will raise a KeyError exception if <id> does not exist. As a plugin developer, the possibility that the shared function does not exist must be handled.

If the shared object is callable (is a function, class, or similar), call_shared will pass additional supplied parameters (after <id>) as part of the call.

Developer Guide: Modern-Alternate Pattern

This pattern is derived from the way commands and handlers are implemented in the [original hangupsbot] (https://github.com/xmikos/hangupsbot).

Note: Although possible, it is not recommended to mix the two plugin patterns together in a single plugin.

from commands import command

@command.register(admin=True)
@command.register(admin=False)
@command.register

e.g.

from commands import command

@command.register(admin=False)
function helloworld(bot, event, *args):
    print("hello world!")

Command prototype: custom_command(bot, event, *args)

Identical in functionality to plugins.register_user_command and plugins.register_admin_command

Tags Registration via Decorator (Optional)

[ ver >= 2.7 ] The decorators support the optional tags parameter:

@command.register(admin=True, tags=[tagslist])
@command.register(admin=False, tags=[tagslist])

Adding tagging support to a plugin gives bot administrators additional control over which user can access and execute commands. For more information, please see the separate article on Access Control with Tags.

Note: You can use named presets for the tagslist in the same way that [plugins.tags.auto-register] (https://github.com/hangoutsbot/hangoutsbot/wiki/Customising-Access-Control#pluginstagsauto-register) uses them to dynamically generate different tags for each command.

Decorator for Handlers

import hangups
from handlers import handler

@handler.register(priority=5, event=hangups.ChatMessageEvent)

e.g.

import hangups
from handlers import handler

@handler.register(priority=5, event=hangups.ChatMessageEvent)
def _got_a_message(bot, event):
    print("something happened")

Registers a function to handle an event:

The handler function must be of the form function(bot, event) - the 2-parameter signature differs from the 3-parameter signature of functions registered by plugins.register_handler.

Handler priority is handled in the same way as plugins.register_handler except the expected range of the decorator priority is 1-10. For compatibility reasons, the supplied value is multiplied by 10 to scale it up to the internal range of 1-100.

# ## ###

Clone this wiki locally