Dev:Channel Mode API

= Introduction = By using the "extended channel modes" API you can create your own channel modes. You can create both channel modes with or without parameters. The only thing that is currently unavailable to modules are "list" type channel modes such as creating something similar to +vhoaq and +beI (but we have the Dev:Extended Bans API to compensate for the latter).

Channel modes with no parameter (eg: +X) are considerably easier than channel modes that require a parameter (eg: +X 123). That being said, the API tries to make it as easy as possible for both.

Of course, once you have written your own channel mode it doesn't actually do anything other than you being able to set and unset it through /MODE. The actual functionality is normally provided by Hooks.

= Creating a channel mode =

You create a channel mode by calling CmodeAdd from MOD_INIT like this: long EXTCMODE_DUMMY = 0L;

DLLFUNC int MOD_INIT(mymod)(ModuleInfo *modinfo) { CmodeInfo cmodereq;

memset(&cmodereq, 0, sizeof(cmodereq)); cmodereq.flag = 'X'; cmodereq.paracount = 0; cmodereq.is_ok = modeX_is_ok; /* your own function */ // cmodereq.is_ok = extcmode_default_requirechop; /* or... for paramless modes that simply require +o/+a/+q */

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:
 * flag specifies which letter to assign to the channel mode. There may be no other module using this letter.
 * local specifies if the channel mode is local, that is: remote servers will never know this channel mode is set or unset. This is rarely used.

Other than that, the CmodeInfo struct is full of 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)(aClient *sptr, aChannel *chptr, 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(aClient *sptr, aChannel *chptr, char mode, char *para, int checkt, int what) {

if ((checkt == EXCHK_ACCESS) || (checkt == EXCHK_ACCESS_ERR)) {       if (!is_chan_op(sptr, chptr)) {           if (checkt == EXCHK_ACCESS_ERR) sendto_one(sptr, err_str(ERR_CHANOPRIVSNEEDED), me.name, sptr->name, chptr->chname); return EX_DENY; }       return EX_ALLOW; } else if (checkt == EXCHK_PARAM) {       int v = atoi(para); if ((v < 1) || (v > 20)) {           sendnotice(sptr, "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, aClient *sptr);

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) { static char convbuf[32]; int t;

t = atoi(param); if (t < 1) t=1; if (t > 20) t=20; ircsprintf(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, the only requirement is that the first few members of the struct contain a number of variables, for that the EXTCM_PAR_HEADER macro is used.

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 { EXTCM_PAR_HEADER 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);

Remarks:
 * The return value must be the HEAD of the linked list (FIXME: is that so? hmm.)
 * Before allocating a new entry you should check if there's already one stored first. For example if a channel is +X 5 and then someone does +X 6 then you don't need to allocate a new struct)

Best to give an example as the above makes it all sounds rather complex: CmodeParam *modeX_put_param(CmodeParam *Xpara, char *para) { aModeX *r = (aModeX *)Xpara;

if (!r) {       r = (aModeX *)MyMallocEx(sizeof(aModeX)); r->flag = 'X'; } /* else... 'r' already exists and contains an (soon to be) old value. */

r->t = atoi(para); /* set or overwrite value */

return (CmodeParam *)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 { EXTCM_PAR_HEADER int t; // change this or add more variables, whatever suits you. } aModeX;

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

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

free_param
Free parameter/setting from memory.

Declaration: void free_param(void *data);

Example: void modeX_free_param(CmodeParam *para) {   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 { EXTCM_PAR_HEADER int t; // change this or add more variables, whatever suits you. } aModeX; [..] CmodeParam *modeX_dup_struct(CmodeParam *src) {   aModeX *dst = MyMallocEx(sizeof(aModeX));

memcpy(dst, src, sizeof(aModeX)); dst->next = dst->prev = NULL; /* paranoid */ return (CmodeParam *)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(aChannel *chptr, 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(aChannel *chptr, 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; }