JOBYX REFERENCE/MANUAL

Herein you will find enough information to write basic JobyX modules, and/or get JobyX running. If you need any other assistance, visit the SourceForge project page at http://www.sourceforge.net/projects/jobyx.

JobyX configuration

The file jobyxconfig.pm contains global constants that JobyX uses to login to the server, load default modules, etc. A sample is provided, called jobyxconfig.pm.demo. Rename this to jobyxconfig.pm and edit it to suit your needs. Note that you should remove the sample language customization constants, unless you want JobyX to parse shouts instead of tells.

For information on customizing what you call your TUs (Trusted Users), see the top of JX_Mod/CORE/English.pm. It will explain how to do this by editing jobyxconfig.pm.

I have tried to supply meaningful default command levels for each command. Feel free to tweak them. They are located in JX_Mod/CORE.pm. After you modify them, back up the file lest a JobyX update clobber your modified version. This also means you will have to re-edit them after a JobyX update. The backup you made will help you remember which settings you chose.

Location of Modules

Modules should be placed in path_to_jobyx/JX_Mod/ and should have a package name of JX_Mod::your_module_name. For example, my Tourney module is placed in /home/chris/jobyx/JX_Mod/Tourney.pm and the first line is package JX_Mod::Tourney;. Note that you may not create nested modules; JX_Mod::Tourney::Seek won't work.

User Levels

Every user has a numeric userlevel, which defaults to 10, unless the user is a guest, then the level is 1. Userlevels can range from 0-100. All commands should be above level 0, as this level is reserved for abusers. Userlevels can be customized by editing the @levels definition in jobyx.pl. The following levels should ALWAYS be in place - modification could cause problems.

0 = Abuser

1 = Guest

10 = Player

99 = Operator

Any level defined as 100 will be ignored, as it is reserved for JobyX itself. On startup it will automatically set level 100 to the bot's server handle.

You may modify the names of the default levels, but make sure they still mean the same thing. For example, 0 could be ``Offender'', 1 could be ``Unreg'', 10 could be ``User'', and 99 could be ``Super User''.

Qualifications

----------------

This is experimental and subject to change. In the future, requirements will be tested via a verification sub. For example:

        sub verify_insmod {
                my @ui=&::userinfo($_[0]);
                $ui[0] or return 0;
                return ($ui[1]->[4] >= 99 or &::inlist('tadmin',$_[0]));
        }

While a bit clunkier, this will allow more complicated checks, as well as a cleaner display of ListCommands.

Note that if only a userlevel check is needed, a number may be used in place of a verification sub. This level would also be displayed on the ListCommands output. If a verification sub is used, the userlevel will not be displayed on this output.

----------------

To restrict access to commands by userlevel or position in lists, a (sort of) language has evolved. Here are the possible arguments:

number (like 10 or 50)
Specifies the userlevel required.

ly:<list>
The user must be in the <list> list.

ln:<list>
The user must not be in the <list> list.

You can combine these with the following ``operators'':

+
logical AND

,
logical OR

Operators are read from left to right. There is no ``order of operations''.

Note that parentheses may not be used for grouping! If you need to do something really complex, verify it in the verification/command subroutine.

Any time that ``userlevel'' is mentioned, it really means ``qualifications string'' unless it says ``numeric userlevel''.

Core Verification Subs

The core provides several subroutines for common variable/list types. Note that some have no application for a list. For example, &::boolean probably wouldn't make a good list validator. To create your own verification subs, use either of the following formats:

lists
&verifysub($handle,$list,$new_item)

variables/avariables
&verifysub($handle,$var,$new_setting,$old_setting)

Note that it is not required to use any parameters. For example, &::handle only uses $new_item (or $new_setting for variables) to validate the item. On the other hand, &::boolean uses $old_setting when $new_setting is null. It is possible to create subs for lists and variables, like &::handle or &::notnull.

\&::boolean variable
Looks for ``n/no/off/0'' for false, and ``y/yes/on/1'' for true. Return value will be (0) when value is invalid, (1) when value is OK, or (2,0 || 1) if a string is used instead of ``0'' or ``1''. If the new setting is omitted, the old setting will be negated and returned.

\&::notnull list, variable
Return true when the new item is not null or whitespace.

\&::true variable
Always return true. (Dangerous! Consider \&::notnull.)

\&::handle list, variable
The item must be a server handle. Automatic name completion (I.E. ``techde'' -> ``TechDeck'') will occur.

\&::handle_no_complete list, variable
Same as above, but no completion. (I.E. ``techde'' -> error.)

\&::handle_lower list, variable
The person adding the item must be on a higher numeric userlevel than the person to be added.

\&::handle_lower_no_complete list, variable
Combination of previous two subs.

Server Connection

Because JobyX uses a nonblocking socket, detecting when the server closes the connection is not as easy as waiting for input to run out. In order to detect a broken connection, JobyX will examine each line against the regular expressions found in @Lang::CORE::connection_fail.

However, JobyX anticipates that your connection may not always be cleanly cut, so if it doesn't recieve anything from the server for one full minute, it will send the bogus ``notacommand'' command. If JobyX doesn't recieve a reply within 30 seconds, it assumes that the connection was dropped, saves all data, and restarts itself.

Command Definition

Commands are the heart of JobyX's operation. When JobyX is told something, it will attempt to locate the best match from the commands it knows, and pass execution to the appropriate subroutine.

@command=(

$name,
This is the name of the command (duh). It is what users will type to execute it. You should stick with upper and lowercase letters. An exception is when you are creating a one-character alias-type-thing, like ``+'', ``-'', ``='', or ``.''. If a command's name is one character, and is not a letter, then no space is required between it and the first argument. All other command names must start with a letter. If you make a command !helloworld, nobody will be able to use it, because the command parser will try to execute the ``!'' command with ``helloworld'' as the first argument.

\&handler,
Reference to the subroutine that will handle this command. The sub will be called like &handler($handle_of_caller,@args), where $handle_of_caller is the handle of the user that is executing the command, and @arg is the arguments supplied with the command.

$execlv,
Userlevel required to execute the command.

$seelv,
Userlevel required to see the command. Not neccessary if $visible==0, but you should still supply it so you don't confuse yourself.

$args,
Maximum number of arguments, 0 for infinite.

$visible,
Whether or not the command will be displayed on the ListCommands output. Generally, this should be on for the first instance of a command, and off for aliases (commands with different names but the same command handler). See the CORE module for some examples.

);

List Definition

JobyX comes with a standard ICS-compatible list feature. Lists can be used to store a variety of information including handles, IP addresses, email addresses, port numbers, ``bad words'', and anything else you can think of.

@list=(

$name,
The name of the list. This is used with the +addlist/-sublist/=showlist commands.

$seelv,
Userlevel required to see the list on the output of ShowList when no listname is specified.

$viewlv,
Userlevel required to view the contents of the list.

$addlv,
Userlevel required to add an item to the list.

$sublv,
Userlevel required to remove an item from the list.

$private,
If true, each user will have their own copy of the list, like ``notify'' or ``censor''. If false, the list is shared, like ``admin'' or ``muzzle''.

\&verifysub,
Reference to a subroutine that will be called to verify that an item is indeed valid for this list. It will only be called when an AddList is in progress. SubList will not make use of it. It will be called like:
        &verifysub($handle_of_caller,$list_name,$item)

and will expect a return list of:

        return (1)              #item is OK, add it
        return (2,$item)        #add $item instead
        return (0,$error)       #don't add the item, and give the error
                                #$error to the caller.

The (2,$item) format may be used to add a different item instead.

If anything is acceptable, set the reference to \&::notnull.

\&addsub,
Reference to a subroutine that will be called after an item has been added. It will be called like:
        &addsub($handle_of_caller,$list_name,$item,$add)
        # note that &subsub has the same parameters
        # $add == 1 if the item was added

A return value of true will qtell $item the You have been added to/removed from the $list_name list by $handle_of_caller. message. If you don't want to write a handler just to return true, set the reference to \&::true. You should definately not return true if the list contains something other than handles. To make the list automatically add a comment for the user, set the reference to \&::list_comment, which also returns true, meaning that the user will also be notified.

This can be employed to do a variety of things. For my Tourney module, I use it to call \&::setlevel to change the user's level to 20 when $list_name eq Manager, providing that the user is below level 20. I could also make it &::send("tell 6 $item has been made a TM by $handle_of_caller.\n"), though that could be overkill.

\&subsub,
Reference to a subroutine that will be called after an item has been removed. See \&addsub for details.

$max,
Maximum number of items that can be in this list. If you don't want a limit, use 0. It's a good idea to put limits on personal lists so that malicious users can't eat disk space or memory. Global lists shouldn't need a limit unless you're worried that your bot's admins won't behave. But you're the one training them, right?

$type,
The plural name of what is stored on the list. This is displayed like -- $listname list: $num $type --. For example, lists that store handles should stick with ``names''; lists that store IP addresses may want to use ``ips''; etc.

\@entries,
Reference to a list containing the default items for this list. Note that these will be used only if the list file on disk does not exist. If the file is merely empty, the list will be too. For example, my ProxyBuster module defines a few default ports to scan. You probably don't want to set this for personal lists.

);

Variable Definition

Variables are a simple, yet effective way for each user to keep their own settings. First, check to see if a personal list would be better for the option you're considering. For example, ``censor'' probably wouldn't make a good variable; a personal list would be more efficient.

@variable=(

$name,
Name of the variable. Used in Set and displayed in Variables.

$havelv,
Userlevel required to have the variable at all. In my Tourney module, for example, there is an Open variable, which is used by TMs to specify whether or not they are open to receive tourney requests. This variable's $havelv is 20, thus preventing non-TMs from getting confused about it.

$default,
Default value of the variable.

$visibility,
This specifies who can see the setting:

0 = Everybody can see the setting. 1 = Each user can only see his/her own setting. 2 = Nobody can see the setting.

\&verifysub,
Reference to a subroutine that will be called when a variable is about to be set. It will be called like:
        &verifysub($handle_of_caller,$variable,$setting)

and expects a return value of:

        return (1)              # setting is OK, set it
        return (2,$setting)     # set to $setting instead
        return (0,$error)       # setting is not OK.  use error $error
                                # if present, else use generic "Bad
                                # setting for variable $variable."

If the variable is a standard boolean (on/off) variable, use \&::boolean.

\&setsub,
Reference to a subroutine that will be called after a variable has been set. It will be called like:
        &setsub($handle_of_caller,$variable,$setting)

In my Tourney module, I use &::send to add or remove the user from the server's TM list depending on the $setting of the Open variable. This will turn their (TM) light on or off.

Note that you can use different subs for different variables, or the same sub for a few or all variables. It's up to you how you want to use it. $variable is supplied specifically for this case. If you use different subs for different variables, you can safely ignore it.

);

Avariable Definition

Avariables are in a sense JobyX's personal variables. They should be used to hold things that affect JobyX's behavior globally. Like variables and personal lists, consider whether a public list would be better suited. In my BadWord module, for example, I use a global list, BadWords, to store the list of unacceptable terms. However, the same module uses the MaxWarnings avariable to set how many warnings a user can receive before he/she is automatically muzzled. The ASet command is used to modify avariables, and you may view them by using AVariables.

@avariable=(

$name,
Name of the avariable. Used with ASet to modify it, and displayed with AVariables.

$seelevel,
Level required to see the avariable.

$setlevel,
Level required to set the avariable.

\&verifysub,
Reference to a subroutine that will be called when this avariable is about to be set. See the \&verifysub definition under Variable Definition.

It is probably safe to use \&::notnull here, since only people in the aconfig list can use it, and that will most likely be just you and a few select others that (supposedly) know what they are doing. If you plan on having a lot of (clueless) people in this list, you may wish to define verification subs.

$default,
Default value of the avariable.

\&setsub,
Reference to a subroutine that will be called after an avariable is set. It will be called like:
        &setsub($handle_of_caller,$avariable,$setting)

You probably won't need to use this one, but it is provided for flexibility.

);

Loading a Module

When a module is inserted, its ICS_init sub will be called like:

        &JX_Mod::<module>::ICS_init($username,$JobyX_version,$handle_of_insertee)

$username is JobyX's handle on the server, and $JobyX_version is the version of JobyX in the format major.minor.revision. You can use this to check for a particular version. $handle_of_insertee is the server handle of the user that inserted the module. Note that the module may be loading before the server connection is established, so don't do anything requiring a connection. Your module's ICS_begin sub will be called (without any parameters) after the connection to the server is complete. If a bot operator inserts the module, ICS_begin will be called immediately following ICS_init.

The real meat of this subroutine is the return value. This is your chance to tell JobyX about any commands, lists, variables, and avariables your module provides.

        return (
        [\@command1   ... \@commandN],
        [\@list1      ... \@listN],
        [\@variable1  ... \@variableN],
        [\@avariable1 ... \@avariableN],
        \&idlesub
        );

Notice that the actual commands/lists/etc are references. This is very important! The syntax of the @command/@list/etc lists are specified above.

Returning (0,$error) will abort the load. I use this in my Tourney module to abort a load when a stored tourney can't be read.

\&idlesub will be called when there is no input from the server. This means that your sub could get called as many as five times per second! This sub should be quick and efficient--too much work could delay JobyX's response, as it will not begin reading from the server again until this function returns. In my Tourney module, I use it to periodically announce tourneys. Like most other subroutine references, you may supply undef if you're not going to use it.

If one of your modules depends on another, use &::ismoduleloaded($module) to determine if a required module is loaded. Generally, modules should not be dependant on other modules. If you think two modules need each other to function, consider merging them in to one module. This will be cleaner and more efficient.

Also note that you can't specify what number the module will have. More on this in Conflicts.

Removing a Module

When a module is about to be removed, the ICS_term sub will be called like:

        &Module::ICS_term($handle_of_remover)

$handle_of_remover is the server handle of the user that removed the module. If the handle is *DEBUG*, JobyX itself unloaded the module, probably during a shutdown/restart.

Like ICS_init, you can return stuff here:

        return (0)              #All OK, remove the module
        return (1,$error)       #Something's wrong, don't remove the module,
                                #and tell the remover $error.

In my Tourney module, I return (1,$error) when I can't save the tourney list, for example. If the error is not serious, it is a good idea to return (0). Note that returning (1,$error) will not cancel the unload if the connection to the server was terminated, or if a bot operator forced a shutdown/restart.

This sub should be used to save any persistant items. Note that variable settings and lists are managed by JobyX; you don't need to do anything here to save those things.

If your sub returns (0), Joby will remove all commands, lists, variables, and avariables specific to your module from memory. This allows you to customize a bot on-the-fly. Note that the actual module code will not be removed, but all connections between JobyX and it will be effectively severed. No hooks will be called.

Core Features

The core provides some other subs that you can call from your modules:

$::last_flags
This is about the only global variable of interest. It will contain the flags captured from the last tell. See the Tell sample module for an example. I was considering passing it to the command handler, right after the user's handle, but it's not often you'd need it, so this seems more efficient and out-of-the-way.

&::columns(@items)
Arrange the elements of @items in columns. The width will never exceed 79. The return list is suitable for feeding to &::qtell.

&::execcommand($handle,$command,$args)
Pretend that user $handle is executing command $command with arguments $args. Note that $args is not a list! Doing this:
        &::execcommand('Hitmonchan','addlist','censor','tupac');

would not have the desired effect. Hitmonchan would be told how to use AddList. This is probably what you want:

        &::execcommand('Hitmonchan','addlist','censor tupac');

&::getavar($avariable)
Returns the current value of the $avariable avariable.

&::getvar($handle,$variable)
Returns the current value of $handle's $variable variable. Note that this is not always the same as the data contained in the %var hash in the user's parray, which will be unset if the variable hasn't been set by the user.

&::getnextline
Returns the next line from the server. Note that when called outside of the core input loop, lines are push()ed to an array and shift()ed off by the input loop after your handler returns. This means that any lines read via &::getnextline will be parsed by the input loop, which is probably a good thing. Also note that \&idle subs may be called while waiting for an input line, so don't do anything in your idle sub that could interfere with the sub you're calling &::getnextline from.

&::getpi($handle,$expect_complete)
Returns user info for $handle. Use &::userinfo if all the info you need is supplied by it, as it is more efficient. Generally, you should only need to call &::getpi for $flags or \%ratings. Note that $::last_flags could possibly take care of the flags you need, but on some servers xtells don't contain flags, so look out.

If $expect_complete is on, &::getpi will not return success when the server returns a completed handle. For example, if the user HotPawn existed, &::getpi("hotpaw",1) would fail. If $expect_complete was off, or omitted, &::getpi would return stats for ``HotPawn''. (These examples assume that the user ``HotPaw'' doesn't exist.)

return (

$exists,
1 if the user exists, 0 otherwise.

$handle,
The handle of the player, in the correct case.

$online,
1 if the user is online, 0 otherwise. If this is all you're going to check, &::userinfo will be more efficient.

$guest,
1 if the user is a guest, 0 otherwise.

$flags,
Current flags for this user, such as ``(*)'', ``(TM)'', or ``(C)''. Note that admins can turn off their (*), so only depend on constant flags. On all servers that I know of, the (C) flag is constant. I use this in my Tourney module to reject computer players from non-computer tourneys, and in my Vote module to restrict voting to human accounts.

\%ratings,
Hash reference to the user's ratings. $ratings->{'blitz'} will return the user's blitz rating, for example.

$email,
User's registered email address. Only available if JobyX's server handle has admin status.

$ip,
User's current IP address. Like $email, admin status is required.

);

&::inlist($list,$item)
Returns 1 if $item is in the $list list.

&::inlist($list,$item,$owner)
Returns 1 if $item is in ${owner}'s personal $list list.

&::ipc($module,@args)
Send list @args to module $module via the ICS_ipc hook. The return value will be whatever was returned from the other module's ICS_ipc. If module $module is not loaded, or does not implement ICS_ipc, undef will be returned. Since modules are meant to be independant, there is little use for this. Flexibility is the key though, so I leave the judgement up to you.

&::ismoduleloaded($module)
Returns 1 if module $module is currently loaded.

&::parse($string)
Parses $string as if it was received from the server. Be careful not to start an infinite loop by making it parse a tell to the same command handler that you're calling &::parse from.

&::qtell($handle,@lines)
Page the lines in @lines to user $handle. Don't worry about newlines embedded in the lines; they will be handled the same was as if the strings on both sides were separate elements of @lines. The user's Height variable is taken into account here.

&::qualified($handle,$userlevel)
Returns 1 if $handle meets the qualifications specified in $userlevel. See Qualifications at the beginning of this document for information on qualification strings.

&::table($padding,@rows)
Create a nicely formatted table, using the values you supplied. (Basically, beefed up version of &::columns.) If $padding is on, each cell will be wrapped with spaces to make the output easier to read. Please note that cells will not wrap, even if the width of the table would exceed 79! Cells are left-aligned by default. To make a cell right-aligned, prefix the content with character 0. The format of @rows is:
        @rows=(\@row1, \@row2, ... \@rowN)

For each row, you can do either of the following:

        # Standard row.
        @row1=('Column 1','Column 2', ... 'Column X');
        # Second column, value "50", is right-aligned.  Note that just "\050"
        # won't work... character 50 will be displayed left-aligned.  See the
        # perlop manpage.
        @row2=('Blitz:',"\00050");
        
        # Divider.
        @row2=();

The number of columns actually displayed depends on the number of items in the longest row.

For example, the following code:

        &::table(
        ['Manager','Level','Status'],
        [],
        ['ChrisHowie','Operator(99)','Always busy'],
        ['Venom','Master(30)'],
        );

Would return a list that, when used with &::qtell, would display the following:

        +-----------------------------------------+
        | Manager    | Level        | Status      |
        |------------|--------------|-------------|
        | ChrisHowie | Operator(99) | Always busy |
        | Venom      | Master(30)   |             |
        +-----------------------------------------+

Nifty, no? Of course, you can modify the returned list before you pass it to &::qtell, either to add a few status rows to the end, or something completely bizzare.

If you want to create a merged cell, span the area with undefs. For example:

        &::table(
        ['Statistics',undef],
        [],
        ['One','Two'],
        ['Three',undef],
        );

Would result in this:

        +--------------+
        | Statistics   |
        |--------------|
        | One   | Two  |
        | Three        |
        |       | Four |
        +--------------+

Note that you cannot supply undef as the content for the first column, or your command handler will die.

To right-align a cell, preceed the cell with the null character. For example, ``\0001.00'' will display ``1.00'' aligned to the right.

There is no special way to create extra-tall rows. If you want to wrap cells, you'll have to do something really interesting and complicated.

&::userinfo($handle)
Returns an array:

return (

$success,
1 if the user was online, or if the user was offline but the player's file was successfully read. Note that if the user is offline, changes to the player's array will not be saved!

\@parray,
The user's array.

);

As for the format of the player array:

[

$handle,
User's handle. The case of the handle is saved to disk, so the handle case is reliable whether the user is online or offline, unless an admin has changed the case of the handle since the user's last logoff.

$guest,
Guest status. If the file was read from disk, this will never be 1; guest parrays aren't saved.

$email,
Email address.

$ip,
IP address.

$userlevel,
User's numeric userlevel.

\%vars,
Hash reference to the user's variable settings. In general, &::getvar is more reliable, because it will take into account unset variables with default values.

\%misc,
Miscellanous data about the user. This stuff will get saved in the user's file, so you can put things you will want to remember later in here. For example, my Tourney module stores tourney statistics in this hash.

]

Hooks

Though not hooks in the traditional sense, JobyX will let your module know when something interesting happens on the server, or locally. It is not neccessary to supply any of these. (Exceptions: ICS_init and ICS_term.) If your module doesn't have a specific hook, JobyX will skip it when a hook event occurs. Modules receive hooks in the order that they were loaded in.

The list is sorted somewhat alphabetically; similar hooks are grouped.

ICS_init($username,$version,$handle)
$handle is trying to insert your module. See Loading a Module above.

ICS_begin
See Loading a Module above.

ICS_term
See Removing a Module above.

ICS_connect($handle,$guest,$ip)
$handle has connected to the server. $guest and $ip will only be available if JobyX's server account has admin status.

ICS_disconnect($handle)
$handle has left the server.

ICS_dis_connect($handle,$arrive)
$handle has connected if $arrive, else disconnected.

ICS_finger($handle_of_caller,$requested_handle)
$handle_of_caller is requesting finger information on $requested_handle. You can return a string to append to the finger notes, with newlines if more than one line is required. In my Tourney module, I return the user's tournament statistics. Each module's return information will be separated with a newline automatically.

ICS_input($line)
Called when a line is received from the server. $line will not have a trailing newline, and any prompt will be removed. A return value of (1) tells JobyX not to parse this line further. A return value of (2) tells JobyX to not parse this line further, and also to not call ICS_input for any remaining modules. Generally, if you want to stop JobyX from parsing, (1) should be used. There's no telling what another module can see in this line. An exception is when your module is going to take exclusive action on this line and you don't want another module to take action. For example, interpreting a shout as a command and you don't want another module doing the same thing and flooding the shouter.

ICS_tell($handle,$flags,$message)
$handle (with flags $flags) has told JobyX $message. A return value of (1) will make JobyX ignore this command. A return value of (2) will make JobyX ignore this command, and also not call ICS_tell for any remaining modules. Generally, you should use (2), for obvious reasons. We don't want a whole load of modules interpreting a tell as a command and responding at once. For example, a VoteBot module could do something like:
        sub ICS_tell {
                my($handle,undef,$mess)=@_;
                if ($mess=~/^ \d+ [a-z]? $/xi) {
                        my @res=&::userinfo($handle);
                        $res[0] or return;
                        my $lt=$res[1]->[6]->{'lasttopic'};
                        ($lt>0) or return;
                        &::execcommand($_[0],'vote',"$lt $message");
                        return 2;
                }
        }

to make it behave like giantfish's VoteBot. (Note that the Vote command would most likely be setting the user's $misc->{'lasttopic'} miscellaneous setting.

ICS_ipc($module,@args)
This will be called when module $module wants to talk to your module. This can be used to share any type of data, which is transmitted in @args. See &::ipc.

ICS_module_info
The modinfo command has been called on your module. Return either a string with lines separated by newlines, or a @list of lines. You can display anything that you think is relevant to the current state of the module. For example, a Tourney module might display how many tourneys are in memory. This can be very useful in debugging.

ICS_lsmod($maxlen)
Similar to ICS_module_info, but the return should be one string, without newlines. Called when lsmod is executed. It is perfectly okay to return nothing. Should yo decide to return anything, it should be a brief quip about the internal state of the module. Try not to exceed $maxlen characters. $maxlen is found by subtracting the length of your module name and number from 79, thus giving the longest string that you can return without wrapping. You can exceed this if neccessary, but you should try to keep long output for ICS_module_info.

ICS_pre_module_load($module)
Module $module is about to be loaded. A return value of (1,$error) will cancel the load and supply the error message Module1 refused the load of Module2. $error. Note that at this point, the module has not been loaded yet. If you need to do &::ipc with another module, use the ICS_module_loaded hook instead.

ICS_module_loaded($module)
Module $module has been loaded.

ICS_pre_module_unload($module)
Module $module is about to be unloaded. A return value of (1,$error) will cancel the unload and supply the error message Module1 refused the removal of Module2. $error. This can be useful if your module depends on another. Note that at this point, the module has not been unloaded yet. &::ipc will still work.

ICS_module_unloaded($module)
Module $module has been unloaded.

Dealing With Conflicts

Occasionally, two modules may want the same command, list, variable, or avariable. How does JobyX determine which module's command handler is called? Simple: The first module that was loaded is given preference.

Note that both commands are stored in memory, so if the first module is removed, the second module's command will become available. There is no way to specifically call the second module's handler while the first module is loaded.

Note that this applies to lists, variables, and avariables as well.

If you know of other modules that have the same commands/lists/etc as one of yours, you can do something like the following:

        package JX_Mod::Foo;
        
        sub ICS_init {
                if (&::ismoduleloaded('Bar')) {
                        return (0,"Bar conflicts with this module.");
                }
                # return commands and stuff...
        }
        
        sub ICS_pre_module_load {
                if (shift eq 'Bar') {
                        return (1,"Bar conflicts with this module.");
                }
        }

While not very compatible, this method is the best when you can't help a conflict. The downside is that you must be aware of the module that is causing the conflict. The upside is that the other module's author need not insert code to check for your module, becuase the ICS_pre_module_load hook will take care of it.

Try to name your commands and stuff with names that apply to your module. For example, my Vote module names the command used to create topics CreateTopic. Naming it Create could cause conflicts with other modules very easily.

To end on a happy note, if JobyX detects a conflict when you are using insmod, it will include them in the response. The best thing to do at that point is to use rmmod and try to fix the conflict.

Help System

JobyX provides a fully-functional helpfile system. By using the Help command, users can request documentation on any command or topic. To create help files for your module, simply place them in path_to_jobyx/help/module_name. For example, my Tourney module places its helpfiles in /home/chris/jobyx/help/Tourney/.

Usage files can be placed in path_to_jobyx/usage/module_name.

Conflicting helpfiles are dealt with in the same manner as commands/lists/etc. The first module loaded is given preference.

When the Help command is called, JobyX builds an index of all of the helpfiles, working through each module's help folders in the order that they were loaded. If two files have the same name, the second is ignored. The practical upshot of this work is for helpfile name completion. For example, if a user types help me, and the first module has the ``mean'' file, while the second has the ``metaphor'' helpfile, the result will be a Helpfile ambiguous error. If JobyX looked in each directory until it found a match, ``mean'' would be displayed.

To restrict access to helpfiles and usage files, the following check is performed: When a user requests a help/usage file, the entire command database is checked for commands with the same

Aside from plain helpfiles, you can use some directives to mutate them based on a user's qualifications. Directives are in effect until the _next_ directive is encountered. Note that directives are not effective in usage files. If a directive starts with ``#'', it must be flush left; if ``%'', then it can appear anywhere.

#SEE $string
This directive is totally ignored by the helpfile parser, but it is read by the index builder. In order to view the helpfile (including seeing it in a ``helpfile ambiguous'' error), the user must pass the qualification string $string.

To save time, the index builder only reads the first line of each helpfile. As a result, this directive is effective only on the first line.

#COMMAND
Similar to the #SEE directive, this line will limit visibility of the helpfile. Like #SEE, it is ignored by the parser. When the indexer encounters this line, it will search for a command with the same name within the same module. If it finds a match, the visibility level of the helpfile will be the same as the visibility of the matching command. This will save you time when editing command levels; you won't have to edit each helpfile as well. Note that this will cause a performance hit when building the index, but that's why we don't build the index each time a helpfile is requested.

Because it is also effective only on the first line, the #SEE and #COMMAND directives are mutually exclusive.

NOTE: Since usage files are always associated with commands, they have, hypothetically speaking, an implied #COMMAND directive in them. That is, the usage command will filter visibility based on the visibility of the matching command.

#QUALIFIED $string
The user must pass the qualification string $string to read the next chunk.

#!QUALIFIED $string
The user must not pass the qualification string $string to read the next chunk.

#STOP
Stop reading the helpfile. This is only effective if the user would normally be able to read this line. While not absolutely neccessary, it can speed up parsing. See the example below.

#BREAK
Synonym for #QUALIFIED 1, and slightly faster.

#USAGE
Dumps the usage file with the same name as the helpfile.

%me
The name of JobyX's server account is substituted in here. This is the only directive that does not have to be flush left, and can appear more than once per line.

%tell
The value of the TellMe avariable is substituted in here. This avariable should contain the quickest way to talk to JobyX. If defaults to ``tell $::username''.

%var'expression'
Substitutes the result of a Perl expression. In the simplest use, it returns the value of a variable, hence the name ``%var''. Note that all calls are from JX_Mod::CORE's namespace. You cannot extend expressions across lines.

Single quotes were chosen because you can always use double quotes in place of single quotes, but the reciprocal isn't always true.

Here is an example helpfile for the command CreateTourney from my Tourney module:

        #COMMAND
        Creates a tournament.
        
        #USAGE

Quite simple, really. I could also have done #SEE 20 instead of using #COMMAND, but now this helpfile and the CreateTourney command are effectively linked; any change to the visibility of CreateTourney will be followed by the helpfile.

Here is a more complex example of an index helpfile:

        Hello!  My name is %me, and I am your friendly tournament robot!
        
        For a list of tournaments, type "%tell lt".
        To join a tournament, type "%tell join <number>".
        #QUALIFIED 20
        For tournament managing directions, type "%tell help tm_guide".
        #QUALIFIED ly:admin
        For server admin commands, type "%tell help admin_guide".
        #BREAK
        
        Have fun!

Notice the creative use of directives to provide all staff members, TM or admin, with directions, and only when they are a TM or admin.

Please note that #comments are not allowed, and will be displayed. The # directive notation is used because it is fairly common in programming languages.

As far as restricting usage files, JobyX will try to find a command with the same name as a usage file requested. If it finds one, the user must be qualified to see the command in order to view the file. If JobyX can't find a command with the same name, the usage file will be displayed regardless of the user's level. (NOTE: In the future, helpfiles may be dealt with in the same manner; I.E. finding a matching command.)


BUGS

While I'm not aware of any yet, this is the first release, so I'm sure there are some. Post any you find on the Bug Tracker on the SourceForge project page at http://www.sourceforge.net/projects/jobyx and I'll fix them ASAP.


CREDITS

Special thanks to the Free Internet Chess Server (FICS - http://www.freechess.org) and the DeepNet Chess Server (DNCS - http://chess.deepnet.com) for allowing me to test JobyX on their servers.