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"