Developing Prosody modules

Introduction

Creating modules for Prosody is a piece of cake - making module development as easy as possible was a Prosody goal from the very beginning.

If you are new to Lua then take a look at the online Programming in Lua book for an introduction to the language, and the Lua manual for later reference. A list of useful Lua links can be found on Matthew's site. If you are already familiar with programming then Lua will hardly need learning at all.

A Prosody module consists of a single Lua script, which will get loaded by the server whenever necessary. When loaded into the server, there will be available a global object 'module' through which you can interact with the module manager and the rest of the server. For more information see the module API reference.

Lots of existing modules to use as inspiration can be found in our community module repository, as well of course as the default Prosody distribution itself.

Hello world

Here is a minimal module that logs "Hello world!" to the Prosody log when it loads:

    -- mod_hello.lua
    module:log("info", "Hello world!");

Put the file in your modules directory and enable it in the config file (see Installing modules). Load the module using one of our admin interfaces (telnet, ad-hoc) or restart Prosody to see your log message appear.

The module is a bit boring so far. Let's have it do something XMPP-related. Let's try an echo responder (we'll assume you're at least a little familiar with XMPP, if not see here).

Thinking about the steps we need to take:

  1. Receive an incoming message stanza
  2. Swap the 'to' and 'from' attributes
  3. Ask Prosody to send the new stanza

We should also be careful not to respond to responses. Echo chambers are fun, but we don't want to accidentally cause a loop where an error causes an error reply, which causes an error reply which causes…

Anyway. The first thing we need to do is receive the message stanza. For that we need our module to have an address. The easiest way to do that in Prosody/XMPP is to make it a component. This has to be done in the config file. Remove "hello" from your modules_enabled, and add a new line to the end of your config file like:

    -- prosody.cfg.lua
    Component "echo.example.com" "hello"

Any stanzas addressed to echo.example.com or anything@echo.example.com will now be routed to our module.

But how do we actually receive these stanzas in the code? Well, we need to use Events. Events are how Prosody communicates lots of notifications, and incoming stanzas are no exception. The events documentation describes the different events generated for every kind of stanza, however you can read that later, I'll tell you that for now we'll handle just one: message/bare.

    -- mod_hello.lua
    function on_message(event)
        module:log("info", "Received a message! Look: %s", tostring(event.stanza));
    end
 
    module:hook("message/bare", on_message);

Reload the module, restart Prosody, or whatever makes you happy. Log into Prosody with your XMPP client, and send a message to "hello@echo.example.com". You need a client that supports sending messages to JIDs that are not on your contact list, such as Pidgin or Gajim. Our module isn't smart enough to deal with such things yet.

Check that you saw the message printed in the log file. If you didn't, don't worry. Check that your code matches the example above - if it does, then it's likely a configuration issue. If you are stuck don't be afraid to come and ask us for help!

If you did, congratulations! We're halfway there already. Our next step is to… create our reply stanza. Since we want to echo what we received, we'll use Prosody's stanza library to clone the incoming stanza. Then we can swap the 'to' and 'from' attributes around. Finally, let's send it and then signal that we handled the event!

    -- mod_hello.lua
    local st = require "util.stanza"; -- Import Prosody's stanza API into 'st'
 
    function on_message(event)
        module:log("info", "Received a message! Look: %s", tostring(event.stanza));
        -- Check the type of the incoming stanza to avoid loops:
        if event.stanza.attr.type == "error" then
            return; -- We do not want to reply to these, so leave.
        end
        -- Create a clone of the received stanza to use for our reply:
        local reply_stanza = st.clone(event.stanza);
        -- This is a neat trick in Lua to swap two variables, without needing a third:
        reply_stanza.attr.to, reply_stanza.attr.from = reply_stanza.attr.from, reply_stanza.attr.to;
        -- Now send!
        module:send(reply_stanza);
        -- We're done! Now, let's halt event propagation
        return true
    end
 
    module:hook("message/bare", on_message);

Reload, send another message, and enjoy! There are lots of improvements and features we could add, but for now we'll leave that to your imagination. Congratulations on your first module, and we hope to see around in the Prosody community. Do join our mailing lists and chatroom to join the discussion 😄

You can now proceed to our module API reference, and the other pages in our developer documentation.