diff --git a/GNUmakefile b/GNUmakefile index abc2db2..e8bb0d2 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -152,6 +152,7 @@ libuhub_SOURCES := \ src/core/plugincallback.c \ src/core/plugininvoke.c \ src/core/pluginloader.c \ + src/core/pluginucmd.c \ src/network/backend.c \ src/network/connection.c \ src/network/epoll.c \ diff --git a/src/core/plugincallback.c b/src/core/plugincallback.c index fb782fb..74fd70e 100644 --- a/src/core/plugincallback.c +++ b/src/core/plugincallback.c @@ -198,6 +198,10 @@ void plugin_register_callback_functions(struct plugin_handle* handle) handle->hub.set_name = cbfunc_set_hub_name; handle->hub.get_description = cbfunc_get_hub_description; handle->hub.set_description = cbfunc_set_hub_description; + handle->hub.ucmd_create = cbfunc_ucmd_create; + handle->hub.ucmd_add_chat = cbfunc_ucmd_add_chat; + handle->hub.ucmd_send = cbfunc_ucmd_send; + handle->hub.ucmd_free = cbfunc_ucmd_free; } void plugin_unregister_callback_functions(struct plugin_handle* handle) diff --git a/src/core/pluginucmd.c b/src/core/pluginucmd.c new file mode 100644 index 0000000..c45d745 --- /dev/null +++ b/src/core/pluginucmd.c @@ -0,0 +1,223 @@ +/* + * 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 . + * + */ + +#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\n". + * Format of a 'me' chat message: "BMSG\s%[mySID]\s\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\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); +} diff --git a/src/core/pluginucmd.h b/src/core/pluginucmd.h new file mode 100644 index 0000000..0e6f8c8 --- /dev/null +++ b/src/core/pluginucmd.h @@ -0,0 +1,47 @@ +/* + * 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 . + * + */ + +#ifndef HAVE_UHUB_PLUGIN_UCMD_H +#define HAVE_UHUB_PLUGIN_UCMD_H + +/* Create a new user command object. + * Name: name of the entry in the clients user command menu - must be unique + * within a hub. + * Length: approximate length of message to be sent. Other functions can + * increase memory allocation as needed but having a sufficient size + * now removes the time taken for this resizing. Allow a factor of ~1/3 + * for the neccesary escaping. + */ +extern struct plugin_ucmd* cbfunc_ucmd_create(struct plugin_handle* plugin, const char* name, size_t length); + +/* Add a message to be sent in the main chat window when the user command is + * clicked. + * Me: If true, makes the message displayed in the same style as a '/me' + * message in IRC (and most hub clients). + */ +extern int cbfunc_ucmd_add_chat(struct plugin_handle* plugin, struct plugin_ucmd* ucmd, const char* message, int me); + +/* Send the user command to a user. */ +extern int cbfunc_ucmd_send(struct plugin_handle* plugin, struct plugin_user* user, struct plugin_ucmd* ucmd); + +/* Free the space used by a user command object. */ +extern void cbfunc_ucmd_free(struct plugin_handle* plugin, struct plugin_ucmd* command); + +#endif /* HAVE_UHUB_PLUGIN_UCMD_H */ diff --git a/src/plugin_api/handle.h b/src/plugin_api/handle.h index b9ab3ea..d37b8c2 100644 --- a/src/plugin_api/handle.h +++ b/src/plugin_api/handle.h @@ -123,6 +123,11 @@ typedef void (*hfunc_set_hub_name)(struct plugin_handle*, const char*); typedef char* (*hfunc_get_hub_description)(struct plugin_handle*); typedef void (*hfunc_set_hub_description)(struct plugin_handle*, const char*); +typedef struct plugin_ucmd* (*hfunc_ucmd_create)(struct plugin_handle*, const char*, size_t); +typedef int (*hfunc_ucmd_add_chat)(struct plugin_handle*, struct plugin_ucmd*, const char*, int); +typedef int (*hfunc_ucmd_send)(struct plugin_handle*, struct plugin_user*, struct plugin_ucmd*); +typedef void (*hfunc_ucmd_free)(struct plugin_handle*, struct plugin_ucmd*); + /** * These are functions created and initialized by the hub and which can be used * by plugins to access functionality internal to the hub. @@ -140,6 +145,10 @@ struct plugin_hub_funcs hfunc_set_hub_name set_name; hfunc_get_hub_description get_description; hfunc_set_hub_description set_description; + hfunc_ucmd_create ucmd_create; + hfunc_ucmd_add_chat ucmd_add_chat; + hfunc_ucmd_send ucmd_send; + hfunc_ucmd_free ucmd_free; }; struct plugin_handle diff --git a/src/plugin_api/types.h b/src/plugin_api/types.h index 4fce255..8cac2f7 100644 --- a/src/plugin_api/types.h +++ b/src/plugin_api/types.h @@ -20,7 +20,7 @@ #ifndef HAVE_UHUB_PLUGIN_TYPES_H #define HAVE_UHUB_PLUGIN_TYPES_H -#define PLUGIN_API_VERSION 1 +#define PLUGIN_API_VERSION 2 #ifndef MAX_NICK_LEN #define MAX_NICK_LEN 64 @@ -103,5 +103,57 @@ enum plugin_command_arg_type plugin_cmd_arg_type_credentials, }; +/* Specifies which categories a command appears in. To make a command appear in + * multiple categories, simply OR the appropriate values together. */ +enum plugin_ucmd_categories +{ + /* Appears in the main hub chat window. */ + ucmd_category_hub = 1, + + /* Appears in the hub user list. */ + ucmd_category_user = 2, + + /* Appears in the search results. */ + ucmd_category_search = 4, + + /* Appears in the file list. */ + ucmd_category_file = 8, + + /* Special case: appear everywhere. */ + ucmd_category_all = 15, +}; + +/* Holds information about a user command. Note that a unique name is required + * for every command, even if it is only a separator. + * + * You should not create one of these yourself but instead use the + * plugin_handle->hub.ucmd_create() function. + * + * Similarly, you should only manually modify the first four entries below, and + * use the plugin_handle->hub.ucmd_xxxx() functions to make any other changes. + * */ +struct plugin_ucmd +{ + /* Which categories the command appears in. */ + enum plugin_ucmd_categories categories; + + /* If true, removes an existing command rather than adding a new one. */ + int remove; + + /* If true, adds a separator to the user command menu rather than an actual command. */ + int separator; + + /* Sometimes a command can be sent on multiple users (e.g., in search + * results). If this field is true, the command is limited to run once per + * CID. */ + int constrained; + + /* Plugins must not modify the following fields. */ + char *name; + size_t namelen; + char *tt; + size_t length; + size_t capacity; +}; #endif /* HAVE_UHUB_PLUGIN_TYPES_H */ diff --git a/src/uhub.h b/src/uhub.h index d5fc0f7..57d527e 100644 --- a/src/uhub.h +++ b/src/uhub.h @@ -95,6 +95,7 @@ extern "C" { #include "core/plugincallback.h" #include "core/plugininvoke.h" #include "core/pluginloader.h" +#include "core/pluginucmd.h"