Loadable Modules - Extending the mesibo Platform

mesibo On-Premise's loadable module architecture lets you extend the platform with your own code — intercepting messages, adding custom processing logic, and integrating with any external service or backend. You can build profanity filters, message translators, AI-powered chatbots connected to LLMs or services like Dialogflow, vector database lookups, IoT and robotics control systems, analytics pipelines, and more. All of this runs inside your own infrastructure with full control over your data.

Modules are built as C/C++ shared libraries (.so files) and loaded into mesibo at runtime — no recompilation of mesibo itself is required.

Prerequisites

What is a mesibo Module?

A mesibo module is a message processor that intercepts each message passing through the server and decides what to do with it:

  • Pass — forward the message to the recipient unchanged
  • Consume — drop the message, preventing it from reaching the recipient
  • Process — modify, analyze, or respond to the message before forwarding or consuming it

For example:

  • A profanity filter module inspects each message and consumes those containing objectionable content
  • A translation module translates each message before forwarding it to the recipient
  • A chatbot module sends incoming messages to an AI service and replies to the sender with the response

The logic inside each module is entirely yours — mesibo provides the hooks, your code provides the behavior.

Modules are built as shared libraries (.so files) and loaded by the mesibo On-Premise server at runtime.

Module Architecture

How Modules Work

When mesibo receives a message, it invokes the callback functions defined in each loaded module, in the order the modules were loaded. Each module can inspect the message and return a result indicating whether to pass or consume it.

Module Flowchart

Multiple modules can be loaded simultaneously, each with its own configuration. The same module can also be loaded more than once with different configurations. Load order and configuration for all modules are specified in /etc/mesibo/mesibo.conf.

Module Chain

Anatomy of a mesibo Module

A mesibo module has three components:

  1. An initialization function called once by mesibo after loading the module
  2. A set of callback functions defined by the module and called by mesibo on events (incoming messages, calls, logins, etc.)
  3. A module definition structure that declares the callback functions and module metadata
Anatomy of Module

Module Definition

A module is described by the mesibo_module_t structure:

typedef struct mesibo_module_s {
    mesibo_uint_t    version;        /* mesibo module API version */
    mesibo_uint_t    flags;          /* module flags */

    const char      *name;           /* module name */
    const char      *description;    /* module description */
    void            *ctx;            /* module context */
    module_config_t *config;         /* module configuration */

    mesibo_int_t    (*on_cleanup)(mesibo_module_t *mod);

    mesibo_int_t    (*on_message)(mesibo_module_t *mod, mesibo_message_params_t *params,
                                  char *message, mesibo_uint_t len);
    mesibo_int_t    (*on_message_status)(mesibo_module_t *mod, mesibo_message_params_t *params,
                                         mesibo_uint_t status);

    mesibo_int_t    (*on_call)(mesibo_module_t *mod);
    mesibo_int_t    (*on_call_status)(mesibo_module_t *mod);

    mesibo_int_t    (*on_login)(mesibo_module_t *mod, mesibo_user_t *user);

    mesibo_uint_t    signature;      /* module signature */

    // initialized by mesibo
    mesibo_int_t    (*invoke)(mesibo_int_t id, mesibo_module_t *mod, ...);
} mesibo_module_t;

Setting up the Development Environment

The mesibo Docker image includes a complete development environment — compilers, debuggers, and source code for several ready-to-use example modules (chatbot, translation filter, etc.). No external toolchain setup is required.

Setting up your working directory

Copy the source code from the container to your host machine so that edits persist across container restarts. In this example, the working directory on the host is /home/mesibo/working-dir. Create a bin subdirectory for compiled modules:

$ mkdir -p /home/mesibo/working-dir/bin

Start mesibo with the working directory and module output directory mounted:

$ sudo docker run -v /etc/letsencrypt/archive/mesibo.example.com:/certs \
     -v /home/mesibo/working-dir:/mesibo/working \
     -v /home/mesibo/working-dir/bin:/usr/lib64/mesibo/ \
     -v /etc/mesibo:/etc/mesibo \
     --net=host --ipc=host --name mesibo --rm -d mesibo/mesibo <APP_TOKEN>

Entering the development shell

Once the container is running, open a shell inside it:

$ sudo docker exec -it mesibo /bin/bash

You will see a shell prompt:

[root@mesibo /]#

Navigate to your mounted working directory and copy the module source files from the container:

[root@mesibo /]# cd /mesibo/working
[root@mesibo working]# cp -a /mesibo/src .
[root@mesibo working]# cd src
[root@mesibo src]# ls
README.md  chatbot  filter  include  make.inc  skeleton  translate

Compiling modules

To compile any module, run make from its directory:

[root@mesibo src]# cd skeleton
[root@mesibo skeleton]# make

To create a new module, copy the skeleton directory, rename it, and update the MODULE field in its Makefile to match your module name. For a detailed example, refer to the Building a Chatbot section below.

mesibo Configuration File

Modules are loaded by specifying them in /etc/mesibo/mesibo.conf. To load a module without configuration:

module <module_name>

To load a module with configuration key-value pairs:

module <module_name> {
    <config_name> = <config_value>
    <config_name> = <config_value>
}

For example:

module skeleton {
    file_name = xyz
    auth_key = abc
}

To load multiple modules (mesibo processes them in the order listed):

module module_one {
    /* configuration for module_one */
}

module module_two {
    /* configuration for module_two */
}

Configuration key-value pairs are passed to each module's initialization function as a list of module_config_item_t name-value pairs.

Module Initialization

mesibo initializes each module by calling a function named mesibo_module_<module_name>_init. For example, if your module name is chatbot, the function must be named mesibo_module_chatbot_init. If this function is not defined, mesibo will fail to load the module.

The prototype is defined in module.hopen_in_new:

int mesibo_module_<module_name>_init(mesibo_int_t version, mesibo_module_t *mod, mesibo_uint_t len);

Parameters:

  1. version — mesibo module API version. Verify it matches MESIBO_MODULE_VERSION.
  2. mod — pointer to the module structure allocated by mesibo. The module must populate this with callback function pointers and a description.
  3. len — size of the module definition structure. Verify it matches sizeof(mesibo_module_t).

Use the MESIBO_MODULE_SANITY_CHECK macro as the first line of your initialization function. It performs all the above checks automatically.

Configuration specified in mesibo.conf is available in the mod->config structure as a list of module_config_item_t name-value pairs.

Example initialization for the skeleton module configured with file_name and auth_key:

int mesibo_module_skeleton_init(mesibo_int_t version, mesibo_module_t *m, mesibo_uint_t len) {

    MESIBO_MODULE_SANITY_CHECK(m, version, len);

    if(m->config) {
        mesibo_log(m, 1, "Configuration items passed to the module:\n");
        for(int i = 0; i < m->config->count; i++) {
            mesibo_log(m, 0, "  %s = %s\n",
                m->config->items[i].name,
                m->config->items[i].value);
        }
    }

    m->flags = 0;
    m->description = strdup("Sample Module");

    m->on_cleanup = skeleton_on_cleanup;
    m->on_message = skeleton_on_message;
    m->on_message_status = skeleton_on_message_status;
    m->on_login = skeleton_on_login;

    return MESIBO_RESULT_OK;
}

This will log:

Configuration items passed to the module:
  file_name = xyz
  auth_key = abc

Module Callback Functions

Callback functions are called by mesibo on specific events. The following events are supported:

  • Incoming message from a user
  • Status update for a message sent by the module
  • Incoming or outgoing call
  • User login or logout
  • Module unload (for cleanup)

Not all callbacks need to be defined — implement only those relevant to your module. All prototypes are in module.hopen_in_new.

Important: Callback functions must not block. If your processing is time-consuming or involves network calls, copy the data and process it in a separate thread.

on_message

Called when a user sends a message.

mesibo_int_t (*on_message)(mesibo_module_t *mod,
        mesibo_message_params_t *params,
        char *message, mesibo_uint_t len);

Parameters:

  1. mod — pointer to the module structure
  2. params — pointer to message parameters (id, from, to, etc.). See Module Data Structures.
  3. message — buffer containing the message bytes
  4. len — length of the message in bytes

Returns:

  • MESIBO_RESULT_PASS — pass the message to the next module or the recipient unchanged
  • MESIBO_RESULT_CONSUMED — consume the message; it will not be forwarded

on_message_status

Called when there is a delivery status update for a message sent by the module.

mesibo_int_t (*on_message_status)(mesibo_module_t *mod,
        mesibo_message_params_t *params,
        mesibo_uint_t status);

Parameters:

  1. mod — pointer to the module structure
  2. params — pointer to message parameters
  3. status — status code such as MSGSTATUS_SENT, MSGSTATUS_DELIVERED, MSGSTATUS_READ

Returns: MESIBO_RESULT_OK

on_call

Called when a user initiates or receives a call.

mesibo_int_t (*on_call)(mesibo_module_t *mod);

Returns: MESIBO_RESULT_OK

on_call_status

Called when there is a status update for a call (ringing, answered, ended, etc.).

mesibo_int_t (*on_call_status)(mesibo_module_t *mod);

Returns: MESIBO_RESULT_OK

on_login

Called when a user logs in or logs out.

mesibo_int_t (*on_login)(mesibo_module_t *mod, mesibo_user_t *user);

Parameters:

  1. mod — pointer to the module structure
  2. user — pointer to the user structure containing the user's address and online status. See Module Data Structures.

Returns: MESIBO_RESULT_OK

on_cleanup

Called when the module is unloaded. Use this to free memory and release any resources acquired during initialization.

mesibo_int_t (*on_cleanup)(mesibo_module_t *mod);

Module Helper Functions

mesibo_message

Sends a message from the module to a user or group.

mesibo_int_t mesibo_message(mesibo_module_t *mod, mesibo_message_params_t *params,
                             const char *message, mesibo_uint_t len);

Parameters:

  1. mod — pointer to the module structure
  2. params — message parameters including sender, recipient, and message ID
  3. message — buffer containing the message bytes
  4. len — length of the message in bytes

Returns: 0 on success, -1 on failure

Example:

mesibo_message_params_t p;
memset(&p, 0, sizeof(p));

p.to = "user_destination";
p.from = "user_source";
p.id = rand();

const char *message = "Hello from Module";
mesibo_message(mod, &p, message, strlen(message));

Utility Functions

mesibo provides utility functions for asynchronous operations, HTTP requests, socket I/O, and logging.

HTTP

mesibo_util_http

Makes an asynchronous HTTP request. Useful for integrating with external services such as LLM APIs, Dialogflow, translation services, or any REST endpoint.

mesibo_int_t mesibo_util_http(mesibo_http_t *req, void *cbdata);

Parameters:

  1. req — pointer to the HTTP request structure. See HTTP Request Structure.
  2. cbdata — pointer to arbitrary user-defined data passed through to the response callbacks.

Returns: 0 on success, -1 on failure

Example:

mesibo_http_t req;
memset(&req, 0, sizeof(req));
req.url = "https://example.com/api";
req.post = "op=query&token=xxxxxxxxx&uid=456";
req.on_data = mesibo_http_on_data_callback;
mesibo_util_http(&req, NULL);

Socket

mesibo provides socket I/O functions for connecting to external services over TCP or WebSocket.

mesibo_util_socket_connect

Opens a connection to a socket.

mesibo_int_t mesibo_util_socket_connect(mesibo_socket_t *sock, void *cbdata);

Parameters:

  1. sock — pointer to the socket structure. See Socket Structure.
  2. cbdata — pointer to arbitrary callback data

Returns: 0 on success, -1 on failure

mesibo_util_socket_write

Writes data to a connected socket.

mesibo_int_t mesibo_util_socket_write(mesibo_int_t sock, const char *data, mesibo_int_t len);

Parameters:

  1. sock — socket handle
  2. data — raw data bytes
  3. len — number of bytes to write

Returns: 0 on success, -1 on failure

mesibo_util_socket_close

Closes the socket connection.

void mesibo_util_socket_close(mesibo_int_t sock);

Logging

mesibo_log

Prints output to the mesibo container logs.

mesibo_int_t mesibo_log(mesibo_module_t *mod, mesibo_uint_t level, const char *format, ...);

Parameters:

  1. mod — pointer to the module structure
  2. level — log level. Level 0 always prints. For level > 0, logging must be enabled in the configuration file.
  3. format — printf-style format string followed by arguments

Returns: 0 on success, -1 on failure

Example:

mesibo_log(mod, 0, "%s\n", "Hello from mesibo Module!");

mesibo_vlog

Same as mesibo_log but accepts a va_list argument list.

mesibo_int_t mesibo_vlog(mesibo_module_t *mod, mesibo_uint_t level, const char *format, va_list args);

mesibo_util_getconfig

Retrieves a configuration value by name from the module configuration passed in mesibo.conf.

char *mesibo_util_getconfig(mesibo_module_t *mod, const char *item_name);

Parameters:

  1. mod — pointer to the module structure
  2. item_name — name of the configuration key

Returns: the configuration value as a string

Example — given this configuration:

module skeleton {
    file = abc
    auth_token = xyz
}
char *item_val = mesibo_util_getconfig(mod, "file");
// item_val contains "abc"

mesibo_util_json_extract

Extracts the value of a key from a JSON string.

char *mesibo_util_json_extract(char *src, const char *key, char **next);

Parameters:

  1. src — JSON source string
  2. key — key to look up
  3. next — set to a pointer to the byte immediately after the matched value, or NULL if not needed

Returns: the value as a string

Example:

char *json = strdup("{ \"name\":\"apple\", \"game\":\"ball\" }");
char *value = mesibo_util_json_extract(json, "name", NULL);
// value contains "apple"

Module Data Structures

Message Parameters Structure

mesibo_message_params_t defines the parameters of an incoming or outgoing message. Used as an argument to on_message, on_message_status, mesibo_message, and related functions.

typedef struct mesibo_message_params_s {
    mesibo_uint_t aid;
    mesibo_uint_t id;
    mesibo_uint_t refid;
    mesibo_uint_t groupid;
    mesibo_uint_t flags;
    mesibo_uint_t type;
    mesibo_uint_t expiry;
    mesibo_uint_t to_online;

    char *to, *from;
} mesibo_message_params_t;
  • aid — Application ID
  • id — Message ID. For outgoing messages, specify this in mesibo_message.
  • refid — Reference message ID; links this message to another (e.g. a reply)
  • groupid — Group ID for group messages; 0 for one-to-one messages
  • flags — Message flags
  • type — Arbitrary user-defined message type
  • expiry — Time-to-live for outgoing messages, in seconds
  • to_online — If set, deliver only if the recipient is currently online

User Structure

typedef struct mesibo_user_s {
    mesibo_uint_t flags;
    char *address;
    mesibo_int_t online;
} mesibo_user_t;
  • flags — User flags
  • address — User address (any character sequence that uniquely identifies the user)
  • online — Online status: 1 if online, 0 if offline

HTTP Request Structure

Used with mesibo_util_http().

typedef struct _mesibo_http_t {
    const char *url;
    const char *post;

    const char *content_type;    // body content type
    const char *extra_header;
    const char *user_agent;
    const char *referrer;
    const char *origin;
    const char *cookie;
    const char *encoding;        // gzip, deflate, identity, br
    const char *cache_control;
    const char *accept;
    const char *etag;
    mesibo_uint_t ims;           // If-Modified-Since, GMT timestamp

    mesibo_uint_t conn_timeout, header_timeout, body_timeout, total_timeout;

    mesibo_http_ondata_t   on_data;
    mesibo_http_onstatus_t on_status;
    mesibo_http_onclose_t  on_close;
} mesibo_http_t;

Properties:

  • url — HTTP or HTTPS URL. Authentication credentials can be embedded: https://user:pass@api.example.com
  • post — Raw POST body string, e.g. "authtoken=xyz&user=abc"
  • content_type — Content-Type header, e.g. "application/json"
  • extra_header — Additional request headers, e.g. "Authorization: Bearer <token>"
  • user_agent — User-Agent string (default: mesibo/x.x)
  • referrer — HTTP Referer header
  • origin — HTTP Origin header
  • cookie — HTTP Cookie header
  • encoding — Content encoding (gzip, deflate, identity, br)
  • cache_control — Cache-Control header
  • accept — Accept header
  • etag — ETag header
  • ims — If-Modified-Since timestamp
  • conn_timeout, header_timeout, body_timeout, total_timeout — per-phase timeouts

Callbacks:

  • on_data — receives the response body, potentially in multiple chunks
typedef mesibo_int_t (*mesibo_http_ondata_t)(void *cbdata, mesibo_int_t state,
        mesibo_int_t progress, const char *buffer, mesibo_int_t size);

The state parameter reflects the current HTTP phase:

typedef enum {
    MODULE_HTTP_STATE_REQUEST,
    MODULE_HTTP_STATE_REQBODY,
    MODULE_HTTP_STATE_RESPHEADER,
    MODULE_HTTP_STATE_RESPBODY,
    MODULE_HTTP_STATE_DONE
} module_http_state_t;

progress ranges from 0 to 100. A negative value indicates an error.

Example callback:

static int mesibo_http_callback(void *cbdata, mesibo_int_t state,
        mesibo_int_t progress, const char *buffer, mesibo_int_t size) {

    tMessageContext *b = (tMessageContext *)cbdata;
    mesibo_module_t *mod = b->mod;

    if (progress < 0) {
        mesibo_log(mod, 0, "HTTP error in callback\n");
        return -1;
    }

    if (state != MODULE_HTTP_STATE_RESPBODY) {
        return 0;
    }

    memcpy(b->buffer + b->datalen, buffer, size);
    b->datalen += size;

    if (progress == 100) {
        // response complete — process b->buffer here
    }

    return 0;
}
  • on_status — called with the HTTP response status code and content type
typedef mesibo_int_t (*mesibo_http_onstatus_t)(void *cbdata, mesibo_int_t status,
        const char *response_type);
  • on_close — called when the connection closes. result is MESIBO_RESULT_FAIL if the connection closed with an error.
typedef void (*mesibo_http_onclose_t)(void *cbdata, mesibo_int_t result);

Socket Structure

Used with mesibo_util_socket_connect().

typedef struct mesibo_socket_s {
    const char *url;
    const char *ws_protocol;   // WebSocket only — valid only if url is NULL

    mesibo_int_t keepalive;
    mesibo_int_t verify_host;
    void *ssl_ctx;             // use if provided; auto-generated if enable_ssl=1

    mesibo_socket_onconnect_t on_connect;
    mesibo_socket_onwrite_t   on_write;
    mesibo_socket_ondata_t    on_data;
    mesibo_socket_onclose_t   on_close;
} mesibo_socket_t;

Properties:

  • url — HTTP or HTTPS host URL
  • ws_protocol — WebSocket URL (ws:// or wss://); used only when url is NULL
  • keepalive — enable persistent keep-alive connection
  • verify_host — verify host validity; disable on private networks
  • ssl_ctx — SSL context for HTTPS/WSS connections

Callbacks:

  • on_connect — called on successful connection; asock holds the socket handle
typedef void (*mesibo_socket_onconnect_t)(void *cbdata, mesibo_int_t asock, mesibo_int_t fd);
  • on_write — called when a write operation completes
typedef void (*mesibo_socket_onwrite_t)(void *cbdata);
  • on_data — called when data is received
typedef mesibo_int_t (*mesibo_socket_ondata_t)(void *cbdata, const char *data, mesibo_int_t len);
  • on_close — called when the connection closes
typedef void (*mesibo_socket_onclose_t)(void *cbdata, mesibo_int_t type);

Module Configuration Structure

Configuration key-value pairs from mesibo.conf are passed to the module as:

typedef struct module_config_item_s {
    char *name;
    char *value;
} module_config_item_t;

typedef struct module_configs_s {
    int count;
    module_config_item_t items[0];
} module_config_t;

count holds the number of items; items is the array of name-value pairs.

Memory Management

Module callbacks can be invoked millions of times under load. Avoid dynamic memory allocation inside callbacks — allocate and prepare resources during initialization and reuse them throughout the module's lifetime.

  • Module definition structure — allocated by mesibo; do not free it.
  • Config item structures — allocated by mesibo and can be freed if needed. The key and value strings inside each item are statically allocated — use those pointers freely but do not free them.

Building a Chatbot

The following example builds a working chatbot module that forwards messages to Dialogflow and replies to users with the response. Dialogflow is used here as a concrete REST API integration example — the same pattern applies to any HTTP-based AI or LLM service.

Module Chatbot Sample

The chatbot works as follows:

  • A dedicated user address is configured as the chatbot destination
  • When a user sends a message to that address, the on_message callback receives it
  • The module forwards the message text to Dialogflow via HTTP
  • When Dialogflow responds, the module sends the reply back to the original sender using mesibo_message
  • Messages addressed to any other destination are passed through unchanged

The complete source is available at github.com/mesibo/onpremise-loadable-modules/tree/master/chatbotopen_in_new.

About Dialogflow

Dialogflow is a Google service for building conversational interfaces. Once trained on your data (FAQs, emails, etc.), it responds to user queries in natural language. It is accessed via a REST API.

For full documentation, refer to Dialogflow Documentationopen_in_new.

Getting Dialogflow API credentials

You will need a GCP project ID and an access token.

  1. Set up a GCP Consoleopen_in_new project:

    • Create or select a project and note the project ID
    • Create a service account
    • Download the service account key as a JSON file
  2. Set the credentials environment variable:

export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account-file.json"
  1. Install and initialize the Cloud SDKopen_in_new.

  2. Print your access token:

echo $(gcloud auth application-default print-access-token)

Save the token for use in the module configuration.

Invoking the Dialogflow API

Send a POST request to:

https://dialogflow.googleapis.com/v2/projects/<PROJECT_ID>/agent/sessions/<SESSION_ID>

SESSION_ID can be any unique identifier — using the mesibo message ID works well.

With headers:

Authorization: Bearer <YOUR_ACCESS_TOKEN>
Content-Type: application/json

And POST body:

{
    "queryInput": {
        "text": {
            "text": "<message text>",
            "languageCode": "<language code>"
        }
    }
}

Step 1 - Create the Source File

Create chatbot.c and include the module header:

#include "module.h"

Step 2 - Define the Module Configuration

Define the configuration block for the chatbot in /etc/mesibo/mesibo.conf:

module chatbot {
    project       = <GCP Project ID>
    endpoint      = https://dialogflow.googleapis.com/v2
    access_token  = <Service Account access token>
    language      = en
    address       = my_chatbot
    log           = 1
}

Step 3 - Initialize the Module

The initialization function reads configuration, stores it in the module context, and registers the on_message callback:

int mesibo_module_chatbot_init(mesibo_int_t version, mesibo_module_t *m, mesibo_uint_t len) {

    MESIBO_MODULE_SANITY_CHECK(m, version, len);

    m->flags = 0;
    m->description = strdup("Chatbot Module");
    m->on_message = chatbot_on_message;

    if(m->config) {
        chatbot_config_t *cbc = get_config_dialogflow(m);
        if(cbc == NULL) {
            mesibo_log(m, MODULE_LOG_LEVEL_0VERRIDE, "%s: missing configuration\n", m->name);
            return MESIBO_RESULT_FAIL;
        }
        m->ctx = (void *)cbc;

        if(chatbot_init_dialogflow(m) != MESIBO_RESULT_OK) {
            mesibo_log(m, MODULE_LOG_LEVEL_0VERRIDE, "%s: invalid configuration\n", m->name);
            return MESIBO_RESULT_FAIL;
        }
    } else {
        return MESIBO_RESULT_FAIL;
    }

    return MESIBO_RESULT_OK;
}

The configuration is stored in chatbot_config_t and saved in m->ctx so it is accessible from all callbacks:

typedef struct chatbot_config_s {
    /* from mesibo.conf */
    const char *project;
    const char *endpoint;
    const char *access_token;
    const char *address;
    const char *language;
    int log;

    /* constructed during init */
    char *post_url;
    char *auth_bearer;
    module_http_option_t *chatbot_http_opt;
} chatbot_config_t;

Use mesibo_util_getconfig to read each value:

static chatbot_config_t *get_config_dialogflow(mesibo_module_t *mod) {
    chatbot_config_t *cbc = (chatbot_config_t *)calloc(1, sizeof(chatbot_config_t));
    cbc->project      = mesibo_util_getconfig(mod, "project");
    cbc->endpoint     = mesibo_util_getconfig(mod, "endpoint");
    cbc->access_token = mesibo_util_getconfig(mod, "access_token");
    cbc->address      = mesibo_util_getconfig(mod, "address");
    cbc->language     = mesibo_util_getconfig(mod, "language");
    cbc->log          = atoi(mesibo_util_getconfig(mod, "log"));
    return cbc;
}

Construct the Dialogflow URL and authorization header once during init rather than on every request:

static int chatbot_init_dialogflow(mesibo_module_t *mod) {
    chatbot_config_t *cbc = (chatbot_config_t *)mod->ctx;

    asprintf(&cbc->post_url, "%s/projects/%s/agent/sessions",
             cbc->endpoint, cbc->project);
    asprintf(&cbc->auth_bearer, "Authorization: Bearer %s", cbc->access_token);

    cbc->chatbot_http_opt = mesibo_chatbot_get_http_opt(cbc);

    return MESIBO_RESULT_OK;
}

Step 4 - Handle Incoming Messages

The on_message callback intercepts each message. Messages addressed to the chatbot are forwarded to Dialogflow and consumed; all other messages are passed through unchanged:

static mesibo_int_t chatbot_on_message(mesibo_module_t *mod, mesibo_message_params_t *p,
        char *message, mesibo_uint_t len) {

    chatbot_config_t *cbc = (chatbot_config_t *)mod->ctx;

    if(0 == strcmp(p->to, cbc->address)) {
        // Copy params — do not modify the original as other modules may use it
        mesibo_message_params_t *np = (mesibo_message_params_t *)calloc(1, sizeof(mesibo_message_params_t));
        memcpy(np, p, sizeof(mesibo_message_params_t));
        chatbot_process_message(mod, np, message, len);
        return MESIBO_RESULT_CONSUMED;
    }

    return MESIBO_RESULT_PASS;
}

Step 5 - Send the Message to Dialogflow

Build the HTTP request and send it asynchronously. The current message context is passed as callback data so it is available when the response arrives:

typedef struct http_context_s {
    mesibo_module_t *mod;
    mesibo_message_params_t *params;
    char *from;
    char *to;
    char *post_data;
    mesibo_int_t status;
    char response_type[HTTP_RESPONSE_TYPE_LEN];
    char buffer[HTTP_BUFFER_LEN];
    int datalen;
} http_context_t;

static mesibo_int_t chatbot_process_message(mesibo_module_t *mod, mesibo_message_params_t *p,
        const char *message, mesibo_uint_t len) {

    chatbot_config_t *cbc = (chatbot_config_t *)mod->ctx;

    char post_url[HTTP_POST_URL_LEN_MAX];
    sprintf(post_url, "%s/%lu:detectIntent", cbc->post_url, p->id);

    char *raw_post_data;
    asprintf(&raw_post_data,
             "{\"queryInput\":{\"text\":{\"text\":\"%.*s\",\"languageCode\":\"%s\"}}}",
             (int)len, message, cbc->language);

    http_context_t *http_context = (http_context_t *)calloc(1, sizeof(http_context_t));
    http_context->mod       = mod;
    http_context->params    = p;
    http_context->from      = strdup(p->from);
    http_context->to        = strdup(p->to);
    http_context->post_data = raw_post_data;

    cbc->chatbot_http_req->url      = post_url;
    cbc->chatbot_http_req->post     = raw_post_data;
    cbc->chatbot_http_req->on_data  = chatbot_http_on_data_callback;
    cbc->chatbot_http_req->on_status = chatbot_http_on_status_callback;
    cbc->chatbot_http_req->on_close = chatbot_http_on_close_callback;

    mesibo_util_http(cbc->chatbot_http_req, (void *)http_context);

    return MESIBO_RESULT_OK;
}

Step 6 - Handle the Dialogflow Response

The response arrives in chunks via on_data. Accumulate chunks into a buffer until progress == 100, then extract the response text and send it back to the user:

static mesibo_int_t chatbot_http_on_data_callback(void *cbdata, mesibo_int_t state,
        mesibo_int_t progress, const char *buffer, mesibo_int_t size) {

    http_context_t *b = (http_context_t *)cbdata;
    mesibo_module_t *mod = b->mod;

    if (progress < 0) {
        mesibo_log(mod, MODULE_LOG_LEVEL_0VERRIDE, "HTTP error in chatbot callback\n");
        mesibo_chatbot_destroy_http_context(b);
        return MESIBO_RESULT_FAIL;
    }

    if (MODULE_HTTP_STATE_RESPBODY != state) {
        return MESIBO_RESULT_OK;
    }

    if (buffer != NULL && size != 0) {
        if (HTTP_BUFFER_LEN < (b->datalen + size)) {
            mesibo_log(mod, MODULE_LOG_LEVEL_0VERRIDE, "HTTP callback: buffer overflow\n");
            return MESIBO_RESULT_FAIL;
        }
        memcpy(b->buffer + b->datalen, buffer, size);
        b->datalen += size;
    }

    return MESIBO_RESULT_OK;
}

mesibo_int_t chatbot_http_on_status_callback(void *cbdata, mesibo_int_t status,
        const char *response_type) {

    http_context_t *b = (http_context_t *)cbdata;
    if(!b) return MESIBO_RESULT_FAIL;

    mesibo_module_t *mod = b->mod;
    if(!mod) return MESIBO_RESULT_FAIL;

    chatbot_config_t *cbc = (chatbot_config_t *)mod->ctx;

    b->status = status;
    if(response_type != NULL) {
        memcpy(b->response_type, response_type, strlen(response_type));
        mesibo_log(mod, cbc->log, "status: %d, response_type: %s\n", (int)status, response_type);
    }

    return MESIBO_RESULT_OK;
}

void chatbot_http_on_close_callback(void *cbdata, mesibo_int_t result) {

    http_context_t *b = (http_context_t *)cbdata;
    mesibo_module_t *mod = b->mod;

    if(MESIBO_RESULT_FAIL == result) {
        mesibo_log(mod, MODULE_LOG_LEVEL_0VERRIDE, "Invalid HTTP response\n");
        return;
    }

    // Extract the response text from the Dialogflow JSON response
    char *extracted_response = mesibo_util_json_extract(b->buffer, "fulfillmentText", NULL);

    // Send the response back to the user who sent the original query
    mesibo_message_params_t p;
    memset(&p, 0, sizeof(mesibo_message_params_t));
    p.id     = rand();
    p.refid  = b->params->id;  // link response to the original query message
    p.aid    = b->params->aid;
    p.from   = b->to;
    p.to     = b->from;
    p.expiry = 3600;

    mesibo_message(mod, &p, extracted_response, strlen(extracted_response));
    mesibo_chatbot_destroy_http_context(b);
}

Step 7 - Compile the Module

Open the sample Makefile and set the MODULE variable to chatbot:

MODULE = chatbot

Then run:

$ sudo make

Verify the output library was created:

/usr/lib64/mesibo/mesibo_mod_chatbot.so

Step 8 - Load the Module

Add the chatbot configuration to /etc/mesibo/mesibo.conf (copy from sample.conf and update the project and access_token values).

Then start mesibo with the module library directory and configuration directory mounted:

$ sudo docker run -v /etc/letsencrypt/archive/mesibo.example.com:/certs \
     -v /usr/lib64/mesibo/:/usr/lib64/mesibo/ \
     -v /etc/mesibo:/etc/mesibo \
     --net=host --ipc=host --name mesibo --rm -d mesibo/mesibo <APP_TOKEN>