janvidar/uhub — pull request #9 by blairbonnett (2012-08-14 02:56:36+00:00)
Title: User command support

These commits add support for custom user commands (the UCMD extension to the ADC protocol). To do so, they define a data structure to store UCMD info and add some new functions to the plugin API to create and work with them, as well as send them to a user. A plugin, ``mod_ucmd``, to send user commands (defined in a text file) based on credential level is added. I've also made a couple of other changes (reserved SIDs, a plugin API method to get a list of currently connected users) that were needed to get UCMDs working properly.

They also fix a bug in the !broadcast command - the message being sent was not escaped, so it didn't send correctly - which I discovered while testing the UCMD code.

I have bumped the PLUGIN_API_VERSION to 2 as a result of the changes I made to the API.

Reserved SIDs
----------------------

If you want to have a user command which interacts with a bot connected to the hub via PM, the bot needs to have a fixed SID, since PMs are sent by SID rather than nickname. I have added support for reserving SIDs for particular users via a ``reserved_sids`` key in the configuration file. The first SID (``AAAA``) was already reserved for the hub, so the first entry in the ``reserved_sids`` list gets ``AAAB``, the second gets ``AAAC`` and so on.

The actual 'reserving' is a bit of a hack, since the ADC protocol assigns SIDs before the nickname is known, and there is no provision for switching SIDs. Instead, the 'reserved' SID is actually treated as an alias. The hub monitors users logging on and off to keep track of the reserved users. Any user lookups by SID are intercepted to return the SID of the real user rather than the alias.

Example:

1. I reserve AAAB for user Phil.
2. Phil logs in and is assigned AF3D. The hub notes this mapping.
3. A PM is sent for AAAB and the user manager lookup is called for AAAB. It returns the user AF3D i.e., the real Phil.

If the reserved user is not logged on the hub drops the message as per usual. I have only really tested this for private messages as this should be the only place it is needed.

To be eligible to get a reserved SID, the user must be registered.

The documentation I added to the config.xml on the new option says its available from 0.5.0, under the assumption this will be the next release number.

mod_ucmd plugin
-------------------------

I've put together a plugin, ``mod_ucmd``, which reads user command settings from a file and sends them to users when they log in. The commands can be given a minimum credential level so they aren't sent to users who cannot access the corresponding hub command (e.g., the kick command can only be used by operators or above, so a 'Kick' user command should only be sent to operators or above).

A sample configuration file, which documents the format and provides 16 user commands for all the standard hub features, is in ``doc/ucmd.conf``.

Getting user lists
------------------------

I also added a function to get a linked list of connected users (able to be filtered by credential level) to the plugin API, plus a corresponding function to free the memory taken by a list.

The initial reason for this was to auto-refresh the user commands when the configuration is reloaded. As it stands, any changes to the user command configuration will be picked up by the plugin when the reload happens, but users will not see the changes until they reconnect.

To refresh the commands, we would need to send messages to each user telling them to delete each existing user command, and then send the new user commands. If you have the 16 standard user commands and 100 users, this is 1600 messages to delete the old commands and another 1600 to send the new ones. So I'm not so sure this is a good idea (especially since the hub I am doing this for will have even more custom commands). But having written the extra plugin API functions I thought it would be silly not to include them in case they are useful to other plugins.

New plugin API functions
------------------------------------

Six plugin API functions are added by these commits. The following is some brief documentation - I'm not sure if there is a suitable place to put this in the documentation folder or what, but its here for reference.

**For working with user commands:**

* ``struct_plugin ucmd* ucmd_create(struct plugin_handle* plugin, const char* name, size_t approx_size)``

Creates a new user command structure. Name must be unique per hub. The given size is used to allocate initial memory for the command (it will be automatically expanded if its not large enough).

* ``int ucmd_add_chat(struct plugin_handle* plugin, struct plugin_ucmd* ucmd, const char* message, int me_flag)``

Add a chat message to the list of actions taken when the user command is run. ``me_flag`` indicates whether or not it should be formatted as a '/me' IRC-style message. Returns 1 on success, 0 on error.

* ``int ucmd_add_pm(struct plugin_handle* plugin, struct plugin_ucmd* ucmd, const char* target, const char* message, int echo_flag)``

Add a private message to the list of actions taken when the user command is run. Target can be a SID string or ``NULL`` to send it to the currently selected user in the user list. ``echo_flag`` indicates whether the PM should be echoed to the sender (i.e., so it is displayed on their screen). Returns 1 on success, 0 on error.

* ``int ucmd_send(struct plugin_handle* plugin, struct plugin_user* user, struct plugin_ucmd* ucmd)``

Send the user command to a user. Returns 1 on success, 0 on error.

* ``void ucmd_free(struct plugin_handle* plugin, struct plugin_ucmd* ucmd)``

Free the memory taken up by a user command.

**For getting a list of connected users:**

* ``struct linked_list* get_user_list(struct plugin_handle* plugin, enum auth_credentials credentials)``

Returns a linked list of ``struct plugin_user*`` objects. If ``credentials`` is ``auth_cred_none``, all connected users are returned. If it is one of the other ``auth_cred_xxx`` values, only users with that credential level are returned. If it is the negative of one of the values, users with that credential level *or greater* are returned e.g., ``credentials = -auth_cred_operator`` returns all users who are operators or greater.

* free_user_list(struct plugin_handle* plugin, struct linked_list* list)

Frees the memory taken up by a user list.