The InspIRCd Project
Home | Developers | Wiki | Forums | Bug Tracker | SVN | Download | Blog | Stats
Personal tools

Writing Modules For InspIRCd

From the makers of InspIRCd.

Jump to: navigation, search


Contents

A 30-Minute Module Tutorial

This tutorial will assume you have at least intermediate knowledge of C++. For example, before attempting to write InspIRCd modules you should know the following, or some of the basic concepts will be difficult to grasp:

  • What a inheritance is in relation to C++ classes
  • What a constructor and destructor are in C++ and how they work
  • What polymorphism is in relation to C++ classes
  • What a class factory is, and how it works
  • The C++ std::string class
  • The STL vector, hash_map and deque classes

There are probably more things to consider, but so long as you're aware of these basics, you will be fine. If you are writing a module which you wish to make available to others, you should probably check it against our STL FAQ, which acts as a checklist to weed out un-scalable code.

This tutorial is intended for InspIRCd 1.1.x. These instructions WILL NOT work with InspIRCd 1.0 or below due to major API changes.

Structure of an InspIRCd module

The Class Factory

An InspIRCd module is a classfactory within a shared object (.so) file. When a module is loaded the core will import the symbol 'init_module' from your module, and attempt to call it (the function has no parameters). Upon being called, the function should return a pointer to a class inherited from class ModuleFactory. The method CreateModule of the class module factory will return a pointer to your actual module class. The creation is done this way to allow for expansibility in the future, for example, if we wanted to place multiple modules into one shared object file. To sum this up, our module currently looks like this:*

#include "users.h"
#include "channels.h"
#include "modules.h"

class MyModuleFactory : public ModuleFactory
{
public:
       MyModuleFactory()
       {
       }

       ~MyModuleFactory()
       {
       }

       virtual Module * CreateModule(InspIRCd* Me)
       {
               return new MyModule(Me);
       }

};

extern "C" void * init_module( void )
{
       return new MyModuleFactory;
}

* Since 1.1.9+svn the factories have been replaced by a single macro that looks like this:

MODULE_INIT(MyModule)

You may still define the full factory class, but it saves typing to simply use the MODULE_INIT macro. This is also portable to future versions (1.2 svn) where the factory class has been removed and MODULE_INIT only defines the entrypoint function.

The Module Class

The more astute amongst you will already know what is going to be added next: We are going to add the definition of the MyModule class to the module file. All modules in InspIRCd inherit from a base class Module that in itself inherits from classbase. Once we have added our definition, our code will look like the one below. Note that every module must have a differently named module class and classfactory*.

#include "users.h"
#include "channels.h"
#include "modules.h"

class MyModule : public Module
{
       MyModule(InspIRCd* Me)
               : Module::Module(Me)
       {
       }

       virtual ~MyModule()
       {
       }

       virtual Version GetVersion()
       {
               return Version(1,0,0,1,0,API_VERSION);
       }
};

class MyModuleFactory : public ModuleFactory
{
public:
       MyModuleFactory()
       {
       }

       ~MyModuleFactory()
       {
       }

       virtual Module * CreateModule(InspIRCd* Me)
       {
               return new MyModule(Me);
       }

};

extern "C" void * init_module( void )
{
       return new MyModuleFactory;
}

* Since 1.1.9+svn the factories have been replaced by a single macro that looks like this:

MODULE_INIT(MyModule)

Now, we have what can be classed a working module. InspIRCd can load this module, and unload this module. A few lines within this code require further clarification:

       virtual Version GetVersion()
       {
               return Version(1,0,0,1,0,API_VERSION);
       }

This section of code informs the core what version number our module is. This will be displayed in the output of /MODULES. The first four digits are the version number (major, minor, build, patch) and the next number is actually a set of flags. You must use the | operator to logically OR the flags together into a bitmask. Available flags are:

  • VF_STATIC - The module is static, cannot be unloaded by /UNLOADMODULE or a rehash. In 1.1 series InspIRCd you should avoid using this unless absolutely neccessary, as setting this flag hinders hot-patching.
  • VF_VENDOR - This flag indicates that the module is part of the original distribution. You should never set this flag.
  • VF_SERVICEPROVIDER - This module provides a service to other modules which are usually accessible via the Event and Request subsystem.
  • VF_COMMON - This module needs to be common on all servers to link - You should set this if your module implements a mode letter.

The final parameter is new to 1.1 series InspIRCd servers, and must always be set to API_VERSION. This is to allow for binary versioning, and if the value does not match that defined in the core, your module will not load.

From examples to working code

Making the module do something

Most of you may have realised that the example module we have just created can't actually DO anything yet! In that case, what we have is useless until we add some code to it so that it can handle events from the core. Your next question therefore is most likely 'So how do we do this?'

Here's how. You must add methods to your Module class, of which a complete list may be found on the developer and module documentation page.

For purpose of this tutorial, we will just add a handler which logs all joins and parts. Note that to do this, we will have to make use of another class, which represents the server itself. This class, surprisingly enough, is called InspIRCd. When your module is created, a pointer to our server (via the InspIRCd class) is passed to our module factory*, then in turn to our module.:

#include "users.h"
#include "channels.h"
#include "modules.h"
#include "inspircd.h"

class MyModule : public Module
{
public:
       MyModule(InspIRCd* Me)
               : Module::Module(Me)
       {
               // Note: Calling the parent constructor copies the 'Me'
               //       pointer into Module::ServerInstance for us.
       }

       virtual ~MyModule()
       {
       }

       virtual void Implements(char* List)
       {
               List[I_OnUserJoin] = List[I_OnUserPart] = 1;
       }

       virtual Version GetVersion()
       {
               return Version(1,0,0,1,0,API_VERSION);
       }

       virtual void OnUserJoin(userrec* user, chanrec* channel, bool &silent)
       {
               // method called when a user joins a channel

               std::string chan = channel->name;
               std::string nick = user->nick;
               ServerInstance->Log(DEBUG,"User " + nick + " joined " + chan);
       }

       virtual void OnUserPart(userrec* user, chanrec* channel, const std::string &partmessage, bool &silent)
       {
               // method called when a user parts a channel

               std::string chan = channel->name;
               std::string nick = user->nick;
               ServerInstance->Log(DEBUG,"User " + nick + " parted " + chan);
       }

};

class MyModuleFactory : public ModuleFactory
{
public:
       MyModuleFactory()
       {
       }

       ~MyModuleFactory()
       {
       }

       virtual Module * CreateModule(InspIRCd* Me)
       {
               return new MyModule(Me);
       }

};

extern "C" void * init_module( void )
{
       return new MyModuleFactory;
}

* Since 1.1.9+svn the factories have been replaced by a single macro that looks like this:

MODULE_INIT(MyModule)


Please note that when you should not create objects of type 'InspIRCd' yourself! This would infer that you are creating a server, which is why one is already passed to you from the core - a server already exists, and your module is getting a pointer to it. If you were to instantiate a class of type 'InspIRCd', you would actually create a whole new instance of the irc server within the process!

The Implements() method

The Implements() method indicates which methods of your module should be 'active' when the module is loaded. This is used by the core to speed up calling multiple modules, and make the module system more efficient.

For each method you wish to activate, you should set its value in the array to 1. For example:

             List[I_OnUserJoin] = List[I_OnUserPart] = 1;

This would set the values of I_OnUserJoin and I_OnUserPart to 1, enabling the methods OnUserJoin and OnUserPart only. If you do not enable a method, it will never be called. This also allows you to temporarily disable methods whilst debugging, without commenting out the entire method or deleting it from the source.

The Special Comments

InspIRCd supports 'special comments'. These are comments which are parsed by ./configure to determine dependencies, include paths and other information from your module's C++ source code. This prevents the need for you to distribute your own configure scripts and/or makefiles with your modules, and makes things simpler for the user.

These special comments must be used exactly in the format shown, all on one line, and spacing must be preserved at all times.

ModDesc
/* $ModDesc: Description here */

Used to describe what your module does.

CompileFlags
/* $CompileFlags: <gcc flags> */

Flags to be added to the gcc build line in the makefile, e.g. include paths.

LinkerFlags
/* $LinkerFlags: <ld flags> */

Flags to be added to the gcc link line in the makefile, e.g. library paths.

ModDep
/* $ModDep: <files> */

Lists one or more files (seperated by spaces) which your module depends upon to be up to date before it can build. For example if you place foobar.h here, any changes to foobar.h also cause your module to be rebuilt. It is usual to only list headers here, not .cpp files.

How to build your module

  • The first time you build the module, re-run configure with the -modupdate switch:
[brain@neuron:inspircd]$ ./configure -modupdate
Configuring default values...
Detecting modules.....................................................................................
Ok, 96 modules detected.
Writing Makefile
Writing src/Makefile
Writing src/modules/Makefile
Complete.
  • Re-run make from the top of the InspIRCd source tree.
  • You may now either load your module into a running InspIRCd with /LOADMODULE or /REHASH, or start up your IRC server with the module configured in its Configuration file.

Where to find more help

You may find more help either by checking our Programmer Documentation, or by asking the devleopers directly on the IRC channel. If you choose to ask directly remember that we are only human (yes, really...) and that we can't be awake 24 hours a day, 7 days a week and that we do have lives, and jobs to go to.


There is also a Forum where you may post and expect a slower, but more in-depth reply to most questions.

Tutorials on specific API sections

The following sections of the wiki document specific classes used in the implementation of modules. For most modules with more than basic functionality, you will need to use one or more of these classes within your module. You should consider this a 'where to go from here' section, which goes into more than basic detail on InspIRCd module development.

Please note that these are wiki duplications of our developer documentation and may be incomplete or out of date. If in doubt, refer to the proper developer documentation pages.

The InspSocket API

Used for asynchronous TCP socket communication within modules

The DNS API

Used for asynchronous DNS lookups within modules

The InspTimer API

Used to create and manipulate one-shot and recurring timers

The ModeHandler API

Used to add user and channel modes to the server

The Command Handler API

Used to add commands to the server

The Event and Request APIs

Used to send messages and classes between modules safely without binary dependencies

The utility classes (stringjoiner, modestacker, tokenstream, etc)

Used for various module and core features to aid in development and debugging

The SQL API

Used for asynchronous communication with SQL databases such as MySQL and PostgreSQL