This site is deprecated; docs have moved to docs.inspircd.org!

Difference between revisions of "Historical:1.1/Writing Modules For InspIRCd"

From the makers of InspIRCd.
Jump to: navigation, search
(Added a notice saying that the instructions only apply for 1.0 (not 1.1))
Line 14: Line 14:
 
There are probably more things to consider, but so long as you're aware of these basics, you will be fine.
 
There are probably more things to consider, but so long as you're aware of these basics, you will be fine.
  
'''This tutorial documents the module API of InspIRCd 1.0.0 and above. If you have a lower version, please upgrade before attempting to write any modules using the information provided here!'''
+
'''This tutorial is intended for InspIRCd 1.0.x. These instructions WILL NOT work with InspIRCd 1.1 or pre-1.0 (beta) due to major API changes.'''
  
 
== Structure of an InspIRCd module ==
 
== Structure of an InspIRCd module ==

Revision as of 13:47, 21 September 2006


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 classfactory 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.

This tutorial is intended for InspIRCd 1.0.x. These instructions WILL NOT work with InspIRCd 1.1 or pre-1.0 (beta) due to major API changes.

Structure of an InspIRCd module

The Classfactory

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:

using namespace std;

#include <stdio.h>
#include <string>
#include <vector>
#include "users.h"
#include "channels.h"
#include "modules.h"
#include "helperfuncs.h"
#include "hashcomp.h"

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

       ~MyModuleFactory()
       {
       }

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

};

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

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.

using namespace std;

#include <stdio.h>
#include <string>
#include <vector>
#include "users.h"
#include "channels.h"
#include "modules.h"
#include "helperfuncs.h"
#include "hashcomp.h"

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

       virtual ~MyModule()
       {
       }

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

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

       ~MyModuleFactory()
       {
       }

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

};

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

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);
       }

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 final 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. You should ALWAYS include this if your module defines user or channel modes.
  • 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 (can be a dependency).
  • VF_COMMON - This module needs to be common on all servers to link -- reserved for future versions.

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 introduce another class, which we can use to talk to the server. This class, surprisingly enough, is called Server. When your module is created, a pointer to our server (via the Server class) is passed to our module factory, then in turn to our module.:

using namespace std;

#include <stdio.h>
#include <string>
#include <vector>
#include "users.h"
#include "channels.h"
#include "modules.h"
#include "helperfuncs.h"
#include "hashcomp.h"

class MyModule : public Module
{
       Server* Srv;

       MyModule(Server* Me)
               : Module::Module(Me)
       {
               // Create an instance of Server

               Srv = Me;
       }

       virtual ~MyModule()
       {
       }

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

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

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

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

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

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

};

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

       ~MyModuleFactory()
       {
       }

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

};

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


Please note that when you should not create objects of type 'Server' 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.

How to build your module

  • Re-run configure with the -update switch:
[[email protected]:inspircd]$ ./configure -update
Configuring default values...
Writing 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.
  • If you are rebuilding an existing module, you may have to remove the .so file from src/modules for the makefile to detect your changes, depending upon which version of InspIRCd you are using.

A working example

Here is a working example of an InspIRCd module. This module is one that actually comes with the IRCd in its tarball, called m_silence, and it demonstrates many interesting features of the InspIRCd API, such as Extendable classes, Command handlers and manipulation of messages.

/*       +------------------------------------+
 *       | Inspire Internet Relay Chat Daemon |
 *       +------------------------------------+
 *
 *  Inspire is copyright (C) 2002-2004 ChatSpike-Dev.
 *                       E-mail:
 *                <[email protected]>
 *                <[email protected]>
 *     
 * Written by Craig Edwards, Craig McLure, and others.
 * This program is free but copyrighted software; see
 *            the file COPYING for details.
 *
 * ---------------------------------------------------
 */

using namespace std;

#include <stdio.h>
#include <string>
#include <vector>
#include "users.h"
#include "channels.h"
#include "modules.h"
#include "helperfuncs.h"
#include "hashcomp.h"

/* $ModDesc: Provides support for the /SILENCE command */


// This typedef holds a silence list. Each user may or may not have a
// silencelist, if a silence list is empty for a user, he/she does not
// have one of these structures associated with their user record.
typedef std::vector<std::string> silencelist;


void handle_silence(char **parameters, int pcnt, userrec *user)
{
       if (!pcnt)
       {
               // no parameters, show the current silence list.
               // Use Extensible::GetExt to fetch the silence list
               silencelist* sl = (silencelist*)user->GetExt("silence_list");
               // if the user has a silence list associated with their user record, show it
               if (sl)
               {
                       for (silencelist::const_iterator c = sl->begin(); c < sl->end(); c++)
                       {
                               WriteServ(user->fd,"271 %s %s %s!*@*",user->nick, user->nick,c->c_str());
                       }
               }
               WriteServ(user->fd,"272 %s :End of Silence List",user->nick);
       }
       else if (pcnt > 0)
       {
               // one or more parameters, add or delete entry from the list (only the first parameter is used)
               char *nick = parameters[0];
               if (nick[0] == '-')
               {
                       // removing an item from the list
                       nick++;
                       // fetch their silence list
                       silencelist* sl = (silencelist*)user->GetExt("silence_list");
                       // does it contain any entries and does it exist?
                       if (sl)
                       {
                               if (sl->size())
                               {
                                       for (silencelist::iterator i = sl->begin(); i != sl->end(); i++)
                                       {
                                               // search through for the item
                                               irc::string listitem = i->c_str();
                                               irc::string target = nick;
                                               if (listitem == target)
                                               {
                                                       sl->erase(i);
                                                       WriteServ(user->fd,"950 %s %s :Removed %s!*@* from silence list",user->nick, user->nick,nick);
                                                       // we have modified the vector from within a loop, we must now bail out
                                                       return;
                                               }
                                       }
                               }
                               if (!sl->size())
                               {
                                       // tidy up -- if a user's list is empty, theres no use having it
                                       // hanging around in the user record.
                                       delete sl;
                                       user->Shrink("silence_list");
                               }
                       }
               }
               else if (nick[0] == '+')
               {
                       nick++;
                       // fetch the user's current silence list
                       silencelist* sl = (silencelist*)user->GetExt("silence_list");
                       // what, they dont have one??? WE'RE ALL GONNA DIE! ...no, we just create an empty one.
                       if (!sl)
                       {
                               sl = new silencelist;
                               user->Extend(std::string("silence_list"),(char*)sl);
                       }
                       // add the nick to it -- silence only takes nicks for some reason even though its list shows masks
                       for (silencelist::iterator n = sl->begin(); n != sl->end();  n++)
                       {
                               irc::string listitem = n->c_str();
                               irc::string target = nick;
                               if (listitem == target)
                               {
                                       WriteServ(user->fd,"952 %s %s :%s is already on your silence list",user->nick, user->nick,nick);
                                       return;
                               }
                       }
                       sl->push_back(std::string(nick));
                       WriteServ(user->fd,"951 %s %s :Added %s!*@* to silence list",user->nick, user->nick,nick);
                       return;
               }
       }
       return;
}


class ModuleSilence : public Module
{
       Server *Srv;
public:

       ModuleSilence(Server* Me)
               : Module::Module(Me)
       {
               Srv = Me;
               Srv->AddCommand("SILENCE",handle_silence,0,0,"m_silence.so");
       }

       virtual void OnUserQuit(userrec* user, std::string reason)
       {
               // when the user quits tidy up any silence list they might have just to keep things tidy
               // and to prevent a HONKING BIG MEMORY LEAK!
               silencelist* sl = (silencelist*)user->GetExt("silence_list");
               if (sl)
               {
                       delete sl;
                       user->Shrink("silence_list");
               }
       }

       virtual void On005Numeric(std::string &output)
       {
               // we don't really have a limit...
               output = output + " SILENCE=999";
       }

       virtual int OnUserPreNotice(userrec* user,void* dest,int target_type, std::string &text)
       {
               // im not sure how unreal's silence operates but ours is sensible. It blocks notices and
               // privmsgs from people on the silence list, directed privately at the user.
               // channel messages are unaffected (ever tried to follow the flow of conversation in
               // a channel when you've set an ignore on the two most talkative people?)
               if (target_type == TYPE_USER)
               {
                       userrec* u = (userrec*)dest;
                       silencelist* sl = (silencelist*)u->GetExt("silence_list");
                       if (sl)
                       {
                               for (silencelist::const_iterator c = sl->begin(); c != sl->end(); c++)
                               {
                                       irc::string listitem = c->c_str();
                                       irc::string target = user->nick;
                                       if (listitem == target)
                                       {
                                               return 1;
                                       }
                               }
                       }
               }
               return 0;
       }

       virtual int OnUserPreMessage(userrec* user,void* dest,int target_type, std::string &text)
       {
               if (target_type == TYPE_USER)
               {
                       userrec* u = (userrec*)dest;
                       silencelist* sl = (silencelist*)u->GetExt("silence_list");
                       if (sl)
                       {
                               for (silencelist::const_iterator c = sl->begin(); c != sl->end(); c++)
                               {
                                       irc::string listitem = c->c_str();
                                       irc::string target = user->nick;
                                       if (listitem == target)
                                       {
                                               return 1;
                                       }
                               }
                       }
               }
               return 0;
       }

       virtual ~ModuleSilence()
       {
       }

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


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

       ~ModuleSilenceFactory()
       {
       }

       virtual Module * CreateModule(Server* Me)
       {
               return new ModuleSilence(Me);
       }

};


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

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.