The user command (UCMD) extension to the ADC protocol allows for sending hub-specific commands to clients. These commands have a name (which generally appears in a menu in the client) and a command string which the client sends back to the hub when the command is selected. These command strings can contain keyword substitutions for useful information, such as the SID of another user that was selected in the client. This commit adds some support for sending user commands to the uhub plugin API. It currently restricts the command string to containing main chat messages (BMSG commands in ADC parlance). A plugin_ucmd structure is added to store the details of a user command, and four methods are added to the plugin_handle.hub structure: * plugin->hub.ucmd_create(plugin, name, length) creates a new user command. * plugin->hub.ucmd_add_chat(plugin, ucmd, message, me) adds a main chat message to the list of commands to be run when the user command is selected. The me flag allows IRC /me style messages. * plugin->hub.ucmd_send(plugin, user, ucmd) sends the command to a user. * plugin->hub.ucmd_free(plugin, ucmd) frees the memory taken by a user command. The structure has a number of flags (categories, remove, separator, constrained) which correspond to the flags in the UCMD specification. The categories flag must be set prior to sending to one (or more, via a logical OR) of the ucmd_category_* enumeration values defined in plugin_api/types.h. The PLUGIN_API_VERSION has been increased to 2 to mark the change.
224 lines
6.0 KiB
C
224 lines
6.0 KiB
C
/*
|
|
* uhub - A tiny ADC p2p connection hub
|
|
* Copyright (C) 2007-2011, Jan Vidar Krey
|
|
* Copyright (C) 2012, Blair Bonnett
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "uhub.h"
|
|
|
|
/* Internal helper function - expand the ucmd->tt member to accomodate the
|
|
* given number of byes size. Copies and then frees any existing data. All
|
|
* unused bytes will be set to zero. Returns 1 on success, 0 if the memory
|
|
* could not be allocated.
|
|
*
|
|
* If size is less than the existing capacity, no change is made.
|
|
*
|
|
* NB. one extra byte is always allocated to act as the end-of-string
|
|
* terminator, i.e., the size can be the length of the string ignoring the
|
|
* terminator. Since unused bytes are set to zero, there should always be a
|
|
* terminator.
|
|
*/
|
|
int ucmd_expand_tt(struct plugin_ucmd* ucmd, size_t size)
|
|
{
|
|
if(size < ucmd->capacity) return 1;
|
|
|
|
/* Try to allocate the space. NB we add one to the space to enforce a null
|
|
* byte. */
|
|
char* newtt = (char*)malloc(size+1);
|
|
if(newtt == NULL) return 0;
|
|
|
|
/* Empty the contents. */
|
|
memset(newtt, 0, size+1);
|
|
|
|
/* Copy any existing data. */
|
|
if(ucmd->tt != NULL)
|
|
{
|
|
memcpy(newtt, ucmd->tt, ucmd->length);
|
|
free(ucmd->tt);
|
|
}
|
|
|
|
/* Update the structure. */
|
|
ucmd->tt = newtt;
|
|
ucmd->capacity = size;
|
|
return 1;
|
|
}
|
|
|
|
struct plugin_ucmd* cbfunc_ucmd_create(struct plugin_handle* plugin, const char* name, size_t length){
|
|
/* Need a name. */
|
|
if(name == NULL)
|
|
{
|
|
plugin->error_msg = "Each user command needs a name.";
|
|
return NULL;
|
|
}
|
|
|
|
/* Allocate space for the command structure. */
|
|
struct plugin_ucmd* cmd = (struct plugin_ucmd*)malloc(sizeof(struct plugin_ucmd));
|
|
if(cmd == NULL)
|
|
{
|
|
plugin->error_msg = "Not enough memory to create user command.";
|
|
return NULL;
|
|
}
|
|
|
|
/* Store the name and initialise the flags. */
|
|
cmd->categories = 0;
|
|
cmd->remove = 0;
|
|
cmd->separator = 0;
|
|
cmd->constrained = 0;
|
|
cmd->name = adc_msg_escape(name);
|
|
cmd->namelen = strlen(cmd->name);
|
|
cmd->tt = NULL;
|
|
cmd->length = 0;
|
|
cmd->capacity = 0;
|
|
|
|
/* Allocate space for the command data. 18 bytes is the overhead for a chat
|
|
* message so we need to add space for this. */
|
|
length = ((length < 0) ? 0 : length) + 18;
|
|
int result = ucmd_expand_tt(cmd, length);
|
|
if(result == 0)
|
|
{
|
|
plugin->error_msg = "Not enough memory to store user command data.";
|
|
cbfunc_ucmd_free(plugin, cmd);
|
|
return NULL;
|
|
}
|
|
|
|
/* Done. */
|
|
return cmd;
|
|
}
|
|
|
|
int cbfunc_ucmd_add_chat(struct plugin_handle* plugin, struct plugin_ucmd* ucmd, const char* message, int me)
|
|
{
|
|
/* Double-escape the message - once for when the client sends it back, and
|
|
* then again to insert it into the user command message we send to them. */
|
|
char* temp = adc_msg_escape(message);
|
|
char* escmsg = adc_msg_escape(temp);
|
|
free(temp);
|
|
size_t msglen = strlen(escmsg);
|
|
|
|
/* Format of a chat message: "BMSG\s%[mySID]\s<double-escaped message>\n".
|
|
* Format of a 'me' chat message: "BMSG\s%[mySID]\s<double-escaped message>\sME1\n". */
|
|
size_t required = 18 + msglen + (me ? 5 : 0);
|
|
if(required > (ucmd->capacity - ucmd->length))
|
|
{
|
|
if(ucmd_expand_tt(ucmd, ucmd->capacity + required) == 0)
|
|
{
|
|
plugin->error_msg = "Could not expand memory to store chat message.";
|
|
free(escmsg);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Add in the chat command and placeholder for the client SID. */
|
|
strncpy(ucmd->tt + ucmd->length, "BMSG\\s%[mySID]\\s", 16);
|
|
ucmd->length += 16;
|
|
|
|
/* Copy the message. */
|
|
strncpy(ucmd->tt + ucmd->length, escmsg, msglen);
|
|
ucmd->length += msglen;
|
|
free(escmsg);
|
|
|
|
/* If it is a 'me' message, add the flag. */
|
|
if(me)
|
|
{
|
|
strncpy(ucmd->tt + ucmd->length, "\\sME1", 5);
|
|
ucmd->length += 5;
|
|
}
|
|
|
|
/* Add the (escaped) line break. */
|
|
ucmd->tt[ucmd->length++] = '\\';
|
|
ucmd->tt[ucmd->length++] = 'n';
|
|
|
|
/* Done. */
|
|
return 1;
|
|
}
|
|
|
|
int cbfunc_ucmd_send(struct plugin_handle* plugin, struct plugin_user* user, struct plugin_ucmd* ucmd)
|
|
{
|
|
/* Make sure we have a command. */
|
|
if(!ucmd->length && !(ucmd->separator || ucmd->remove))
|
|
{
|
|
plugin->error_msg = "Cannot send without adding a message.";
|
|
return 0;
|
|
}
|
|
|
|
/* Make sure the category is valid. */
|
|
if(!ucmd->remove)
|
|
{
|
|
if(ucmd->categories < 1 || ucmd->categories > 15)
|
|
{
|
|
plugin->error_msg = "Need a valid category to send.";
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Calculate the size needed for the message. */
|
|
size_t length = ucmd->namelen;
|
|
if(ucmd->remove || ucmd->separator) length += 4;
|
|
else
|
|
{
|
|
length += 10 + ucmd->length; /* "_TT<arg>\n CTnn" = 10 extra chars. */
|
|
if(ucmd->constrained) length += 4;
|
|
}
|
|
|
|
/* Create the message structure. */
|
|
struct adc_message* message = adc_msg_construct(ADC_CMD_ICMD, length);
|
|
if(message == NULL)
|
|
{
|
|
plugin->error_msg = "Cannot allocate space for ADC message.";
|
|
return 0;
|
|
}
|
|
|
|
/* Always have the name. */
|
|
adc_msg_add_argument(message, ucmd->name);
|
|
|
|
/* Remove / separator. */
|
|
if(ucmd->remove) adc_msg_add_argument(message, "RM1");
|
|
if(ucmd->separator)
|
|
{
|
|
adc_msg_add_argument(message, "SP1");
|
|
adc_msg_add_named_argument_int(message, "CT", ucmd->categories);
|
|
}
|
|
|
|
/* Add in the message. */
|
|
else
|
|
{
|
|
adc_msg_add_named_argument(message, "TT", ucmd->tt);
|
|
if(ucmd->constrained) adc_msg_add_argument(message, "CO1");
|
|
adc_msg_add_named_argument_int(message, "CT", ucmd->categories);
|
|
}
|
|
|
|
/* Send it. */
|
|
route_to_user(plugin_get_hub(plugin), (struct hub_user*)user, message);
|
|
|
|
/* Success. */
|
|
adc_msg_free(message);
|
|
return 1;
|
|
}
|
|
|
|
void cbfunc_ucmd_free(struct plugin_handle* plugin, struct plugin_ucmd* ucmd){
|
|
if(ucmd->name != NULL)
|
|
{
|
|
free(ucmd->name);
|
|
ucmd->name = NULL;
|
|
}
|
|
if(ucmd->tt != NULL)
|
|
{
|
|
free(ucmd->tt);
|
|
ucmd->tt = NULL;
|
|
}
|
|
free(ucmd);
|
|
}
|