Your First Farage Module - noglass/FarageBot GitHub Wiki

Introduction

This page will cover creating your very first module for FarageBot using the Farage API.

This module will display chat messages and reactions in the console and add a basic ping/pong command.

Includes

#include "api/farage.h" // The Farage API

There are other header files that use less of the API that can be used individually, but since the module will be compiled with the full library there is little reason not to simply include the whole thing.

Namespaces

Everything in the API uses the base namespace Farage. For simplicity, you can safely use the using keyword:

using namespace Farage;

Module Info

The Farage::Info struct defines a sort of signature for the module. This is required for the module to be loaded. It is also required that it be named Module and is declared extern "C" so the runtime linker can find it.

Layout:

extern "C" Info Module
{
    "Module Name",
    "Module Author",
    "Module Version String",
    "Module URL String",
    FARAGE_API_VERSION
};

It is important to not change the 5th argument. This allows the Farage Engine to determine which version of the API the module was compiled with. Do not use this as a "required API version" or "required engine version". This is strictly used by Farage to determine if the module is compatible with itself when loading. Setting this value to something else may however prove useful if you really do know better than the engine, but you might also need to manually change your engine's API value as well.

Nothing in this struct, aside from the API version, has any functional value. It is only used for displaying info on the module and crediting the author.

#define VERSION "v0.0.1"

extern "C" Info Module
{
    "My First Module",
    "Madison",
    "My very first Farage module ever!",
    VERSION,
    "https://github.com/noglass/FarageBot/wiki/Your-First-Farage-Module",
    FARAGE_API_VERSION
};

Here I used a preprocessor define for the version string, mostly for simplicity since we will be using the same string again further ahead and it is easier to change one value instead of two.

onModuleStart

The onModuleStart event is required for the module to be loaded. It will be automatically called upon successfully loading the module. Within the event, you can register commands, gvars, and other initializer things you may want.

extern "C" int onModuleStart(Handle &handle, Global *global)
{
    recallGlobal(global);

Farage::Handle &handle is this modules handle. Everything about the module is contained here.

The Farage::Global struct contains all the information that gets shared between the Farage Engine and every module. Every module's pointer is stored within it, this means any module has access to any other module as well.
This is also how your module will access the Farage Runtime API. Most of the runtime functions are methods for interacting directly with Discord, like sending messages.

Farage::recallGlobal(global); is absolutely required to be here within your onModuleStart event. Many internal API functions rely on the plugin having stored the Farage::Global* information locally and this is the only place the Farage Engine will send this pointer to the module itself.
This function will store it if one is passed as the first argument. Otherwise it can be used to "recall" the stored pointer, but more information on this will be covered later.

    handle.createGlobVar("first_version",VERSION,"My First Module Version",GVAR_CONSTANT);

The standard practice for Farage modules is to create a gvar corresponding to the version of the module. This makes it easier for users to check the version of your module.
GVAR_CONSTANT means this gvar will be initialized to the value and will be read-only.

    handle.regChatCmd("ping",&pingCmd,NOFLAG,"Pong!");

This will register a new chat command, "ping". The callback for this command is pingCmd, which we will cover next (We haven't actually declared this function yet, but it will be added in the final recap).

Farage::AdminFlag::NOFLAG means no admin flags are required to run this command. For more information about admin flags check out api/admins.h. (Better documentation on this coming soon.)

"Pong!" is a brief description of the command, which can be viewed in the cmdhelp chat command, help chat console command, and modules info console command.

    handle.hookChatPattern("chat",".",nullptr,HOOK_PRINT);

This will create a chat hook on a regex pattern.

"chat" is a brief name for the hook. This name can be used to lookup the hook later and modify or "unhook" it.

"." is the regex pattern to match against chat text. This particular pattern will match every message that contains text, meaning no embeds or uploads unless they also contain text.

nullptr is the callback function. In this case, since we are passing nullptr, no function will be called on matching messages.

HOOK_PRINT is one of several allowed flags. Flags can be OR'd together. For our purposes of simply displaying chat messages, this is all we need. The engine will internally print the message in a somewhat pretty format with guild, channel, and user ID's resolved. The message will also be prefixed with the hook's name, so that's something to keep in mind when naming chat hooks with the HOOK_PRINT flag.

    handle.hookReaction("react",nullptr,HOOK_PRINT);

This works similarly to chat hooks, except it hooks reactions. More options are accepted for react hooks. For our purposes, this is all we need. To see all options check out api/handle.h.

    return 0;
}

The return value of this event can be used to tell the Farage Engine that the module has either loaded successfully or not. Returning non-zero will halt the loading process, and the module will not be loaded.

Command Callbacks

When a command is triggered via chat (or console) the Farage Engine will call the corresponding callback function that the command was registered with. It will always pass the module handle, and C-style argc/argv arguments (except it uses std::string instead of char arrays, and for chat commands the Farage::Message object that triggered the command will also be passed.

int pingCmd(Handle &handle, int argc, const std::string argv[], const Message &message)
{
    messageReply(message,"Pong!");
    return PLUGIN_HANDLED;
}

Farage::messageReply replies to a Farage::Message with a basic text message.

The return value from a command callback determines the Farage Engine's future behavior of processing the triggering message.

By returning PLUGIN_HANDLED the engine understands that processing of this message has reached its completion and no further action is required. If any other plugins, that have lower priority than the current plugin, have also registered a command by the same name, they will not be triggered.

If this is not the desired behavior, you can return PLUGIN_CONTINUE, which will make the engine act as if nothing happened and further processing is required. For console commands, this will make the engine output an Unknown command error, even if the callback executed completely.

Recap

So to recap, we have now created a very basic fully functional module that will output messages and reactions in the console and add a ping command.

Code

#include "api/farage.h" // The Farage API
using namespace Farage;

#define VERSION "v0.0.1"

extern "C" Info Module
{
    "My First Module",
    "Madison",
    "My very first Farage module ever!",
    VERSION,
    "https://github.com/noglass/FarageBot/wiki/Your_First_Farage_Module",
    FARAGE_API_VERSION
};

int pingCmd(Handle&,int,const std::string[],const Message&);

extern "C" int onModuleStart(Handle &handle, Global *global)
{
    recallGlobal(global);
    handle.createGlobVar("first_version",VERSION,"My First Module Version",GVAR_CONSTANT);
    handle.regChatCmd("ping",&pingCmd,NOFLAG,"Pong!");
    handle.hookChatPattern("chat",".",nullptr,HOOK_PRINT);
    handle.hookReaction("react",nullptr,HOOK_PRINT);
    return 0;
}

int pingCmd(Handle &handle, int argc, const std::string argv[], const Message &message)
{
    messageReply(message,"Pong!");
    return PLUGIN_HANDLED;
}

Notice we have added the forward declaration for the pingCmd callback function before the onModuleStart event.

Building your module

In the ./source/modules/ directory, you will find a build.sh script, this script will handle everything for compiling your module in most cases.

This module requires no further action, but if you create a module that requires other source files, you need to add other compile flags, or link libraries, etc... you can create a file in the same directory as your module.cpp file with the same base name as the module, but with the .flags extension and the build script will automatically treat everything in the file as compiler flags. (Argument processing is very difficult to get correct, so avoid using spaces, quotes, or escape sequences within arguments. A space will always split to the next argument. It's annoying and I'm trying to find a fix eventually...)

But I digress.. You can compile with the following command (be sure to be in the ./source/modules/ directory before running the script):

./build.sh module_source.cpp

If you want to install the module automatically after a successful compile, you can pass the --install switch.
Compiled .fso files can be found in ./source/modules/compiled/.

Your Second Module

Stay tuned, it's coming soon!

Your second module will be a bit more in depth covering:

  • Events
  • Detailed chat and console commands
  • Functional gvars
  • More hands on the Runtime API
  • And more!