Dev:Channel Mode API

= Introduction = Using the "Extended Channel Modes" API, you can create your own channel modes. We support 2 types: with parameter (eg: ), and without parameter - paramless (eg:  ). The current API does not allow you to create list modes or prefixes. If you want to have some list mode then you can often use the Dev:Extended Bans API instead.

The API is relatively simple for modes without a parameter (paramless). It is more complex for channel modes with parameters. But we still do our best to make it easy.

The channel mode API only provides setting/unsetting the channel mode. To make your channel mode actually do something useful you need to add Hooks or other objects.

= Example = We highly recommend to read the section, BUT it may also be a good idea to have the source of an actual channel mode on your (other) screen while reading this manual.

All UnrealIRCd channel modes that use the extended channel modes API are in src/modules/chanmodes. Good examples are:
 * For a simple paramless channel mode, see src/modules/chanmodes/regonly.c
 * Channel modes with parameters are also in src/modules/chanmodes: such as history.c, link.c and floodprot.c. Note, however, that these are quite complex!

= Creating a channel mode =

The first step, in, is to declare the   struct and its members. Then call the  function like this: Cmode_t EXTCMODE_DUMMY = 0L;

MOD_INIT {   CmodeInfo cmodereq;

memset(&cmodereq, 0, sizeof(cmodereq)); cmodereq.letter = 'X'; cmodereq.paracount = 0;

/* for the is_ok function, choose 1 of 2 below: */ cmodereq.is_ok = extcmode_default_requirechop; /* for paramless modes that simply require +o/+a/+q */ // cmodereq.is_ok = modeX_is_ok; /* or.. your own function */

CmodeAdd(modinfo->handle, cmodereq, &EXTCMODE_DUMMY);

So basically you pass: 1) the module handle, 2) a CmodeInfo struct, and 3) a pointer to a long int that will be set to the integer value of the channel mode.

The CmodeInfo struct contains two regular variables which you may set:
 * letter specifies the channel mode letter.
 * local specifies if the channel mode is local (i.e. remote servers will never know this channel mode is set or unset). This is rarely used.

Other than that, the CmodeInfo struct contains many function pointers. In the example of above we only set the is_ok function. There are 3 categories of functions:
 * Required functions for both paramless and parameter modes
 * Optional functions
 * Required functions for parameter modes

They are explained below:

is_ok
Access and parameter checking.

SHORTCUT: If you are writing a paramless channel mode and you simply want to require +o/+a/+q for setting and unsetting the mode, then you can save yourself from writing your own is_ok function. In that case simply use extcmode_default_requirechop as commented out in the example in previous section.

Declaration of the is_ok function: int        (*is_ok)(Client *client, Channel *channel, char mode, char *para, int checkt, int what);

The is_ok function is called for a number of cases:
 * If checkt is EXCHK_ACCESS then the function is called to check if the user may set/unset the mode. You must only check access and NOT send any error message(s). If access is granted you should return EX_ALLOW. If you want to disallow a set/unset then return EX_DENY. Normally IRC Operators with override capabilities may still override a deny, if you also want to prevent IRCOp's from (un)setting then you can return EX_ALWAYS_DENY.
 * When checkt is EXCHK_ACCESS_ERR it means your function is called (again) to check access but this time you may send an error message to the user (in fact, you are expected to), indicating why setting or unsetting is not permitted.
 * Finally, when checkt is EXCHK_PARAM then you are supposed to check the correctness of the parameter being passed. If it's not good (eg: you expect a value between 1 and 20 and you recieve 'lalala' or '30') then you return EX_DENY. Otherwise you return EX_ALLOW. You should also send an error message to the user telling them what's wrong.

Example: int modeX_is_ok(Client *client, Channel *channel, char mode, char *para, int checkt, int what) {

if ((checkt == EXCHK_ACCESS) || (checkt == EXCHK_ACCESS_ERR)) {       if (!is_chan_op(client, channel)) {           if (checkt == EXCHK_ACCESS_ERR) sendnumeric(client, ERR_CHANOPRIVSNEEDED, channel->chname); return EX_DENY; }       return EX_ALLOW; }   else if (checkt == EXCHK_PARAM) {       int v = atoi(para); if ((v < 1) || (v > 20)) {           sendnotice(client, "ChanMode +X: ERROR: Expect a value between 1 and 20"); return EX_DENY; }       return EX_ALLOW; }

return EX_ALLOW; /* Falltrough... normally never reached */ }

conv_param
Convert input parameter to 'correct' parameter. For example +X a123 could be converted to +X 123.

Generally is_ok should be used for checking and simply reject invalid user input there. However in some cases you may want to transform user input. That's what this function is for: transform the string if necessary. You SHOULD NOT send any errors here.

If you don't need this, then simply don't register the function.

Declaration: char *     (*conv_param)(char *para, Client *client);

Example: /* Instead of rejecting 20 values in +X, the author of this module chose to transform these to 1 and 20 respectively. */ char *modeX_conv_param(char *param, Client *client) { static char convbuf[32]; int t;

t = atoi(param); if (t < 1) t=1; if (t > 20) t=20; ircsnprintf(convbuf, sizeof(convbuf), "%d", t); return convbuf; }

Parameter modes
Parameter modes are more complex because they need to store and retrieve data, duplicate and free data and have to deal with server synchronization.

These parameters (settings) are stored in a struct you define yourself.

Example: /* Example of mode +X module that accepts a number */

/* This is how you should declare your own custom struct. You normally put this quite high in the source, before all your MOD_TEST / MOD_INIT / etc functions */ typedef struct { int t; // change this or add more variables, whatever suits you. } aModeX;

In addition to an is_ok function described earlier, ALL the functions in the next few sections are required!

put_param
Store a parameter (or more accurately, a setting) in memory for the channel.

Declaration: void *put_param(void *data, char *para);

Regarding the data parameter:
 * If the parameter data is NULL, then you need to allocate your stuct. This happens if the mode is set on the channel and it previously was not set.
 * If data is not NULL, then use the existing struct. This happens if the mode was already set on the channel (eg: +X 5) but was changed to a new value (eg: +X 10).

Here is an example, which makes it easy to understand: void *modeX_put_param(void *Xpara, char *para) {   aModeX *r = (aModeX *)Xpara;

/* Allocate a structure if it does not already exist */ if (!r) r = safe_alloc(sizeof(aModeX));

/* Now, set the value (new or overwrite existing) */ r->t = atoi(para);

return (void *)r; }

get_param
Retrieve a parameter/setting of the channel from memory. Or, in other words: convert a setting from your struct to a readable string.

Declaration: char *get_param(void *data);

Example: /* Example of mode +X module that accepts a number */

/* This is how you should declare your own custom struct. You normally put this quite high in the source, before all your MOD_TEST / MOD_INIT / etc functions */ typedef struct { int t; // change this or add more variables, whatever suits you. } aModeX;

char *modeX_get_param(void *Xpara) { aModeX *r = (aModeX *)Xpara; static char buf[32];

if (!r) return NULL; ircsnprintf(buf, sizeof(buf), "%d", r->t); return buf; }

free_param
Free parameter/setting from memory.

Declaration: void free_param(void *data);

Example: void modeX_free_param(void *para) {   safe_free(para); }

dup_struct
Duplicate your param/settings struct. Yes, this function is mandatory for parameter modes. It is only called from a few places but it is required.

Declaration: void *dup_struct(void *data);

Example: typedef struct { int t; // change this or add more variables, whatever suits you. } aModeX; [..] void *modeX_dup_struct(void *src) {   aModeX *dst = MyMallocEx(sizeof(aModeX));

memcpy(dst, src, sizeof(aModeX)); dst->next = dst->prev = NULL; /* paranoid */ return (void *)dst; }

sjoin_check
When a server links in and all channels are synchronized, their timestamp is compared. If one of them has a lower creation timestamp then their setting is preserved. However it is possible for both sides to have the same creation timestamp, in fact this is quite normal if users were connected on both sides and the servers split and then later on reconnect. In that case the channels from both sides are considered equal but we still have to end up with one single sets of mode. So if one side has +X 100 set and the other sides has +X 200 then we have to agree on a value here, either 100 or 200. In other words, we have to assign a 'winner'. This function decides that.

Note: In general, as a principle throughout UnrealIRCd we use 'highest value wins'. So if one side has +l 5 and the other has +l 20 then the winner will be +l 20.

Declaration: int sjoin_check(Channel *channel, void *our, void *their);

You must return a value of EXSJ_*:
 * EXSJ_SAME: Paramaters are the same
 * EXSJ_WEWON: We won, our setting will be used on both sides
 * EXSJ_THEYWON: We lost, their setting will be used on our side as well
 * EXSJ_MERGE: There's no winner/loser, we have 'merged' the differences and came up with a value different than both. We have changed the settings in our, UnrealIRCd should use these settings on both this side and theirs. Actually EXSJ_MERGE is pretty rare, it's currently only used by channel mode +f.

Example: /* aModeX is our custom struct (see previous examples) */

int modeX_sjoin_check(Channel *channel, aModeX *our, aModeX *their) {   if (our->t > their->t) return EXSJ_WEWON; else if (their->t > our->t) return EXSJ_THEYWON; else return EXSJ_SAME; }

= More information = UnrealIRCd ships with quite a lot of channel modes, they are all in src/modules/chanmodes/

And of course, you can have a look at third party channel modules for some examples too.