added possibility for subcommands
[NeonServV5.git] / src / modcmd.c
index eeaee94974644a18764c2aca0a1b69a7d82dddaa..f6ff75fca8e6fffb7e6b31cbdb54c96eb1905b1f 100644 (file)
@@ -1,4 +1,4 @@
-/* modcmd.c - NeonServ v5.1
+/* modcmd.c - NeonServ v5.2
  * Copyright (C) 2011  Philipp Kreil (pk910)
  * 
  * This program is free software: you can redistribute it and/or modify
@@ -35,20 +35,30 @@ struct trigger_callback {
     struct trigger_callback *next;
 };
 
+struct cmd_bot_alias {
+    int botid;
+    char *alias;
+    
+    struct cmd_bot_alias *next;
+};
+
 struct command_check_user_cache {
     struct ClientSocket *client, *textclient;
     struct UserNode *user;
     struct ChanNode *chan, *sent_chan;
     char **argv;
     int argc;
-    char *message;
+    char *message, *args_buffer;
     struct cmd_binding *cbind;
 };
 
 static struct cmd_binding **cmd_binds;
 static struct cmd_function *cmd_functions = NULL;
 static struct trigger_callback *trigger_callbacks = NULL;
+static struct cmd_bot_alias *bot_aliases = NULL;
 static struct ClientSocket *tmp_text_client;
+static int total_triggered = 0;
+int statistics_commands = 0;
 
 static const struct default_language_entry msgtab[] = {
     {"MODCMD_LESS_PARAM_COUNT", "This command requires more parameters."},
@@ -58,6 +68,7 @@ static const struct default_language_entry msgtab[] = {
     {"MODCMD_PRIVILEGED",       "$b%s$b is a privileged command."}, /* {ARGS: "god"} */
     {"MODCMD_PUBCMD",           "Public commands in $b%s$b are restricted."}, /* {ARGS: "#TestChan"} */
     {"MODCMD_ACCESS_DENIED",    "Access denied."},
+    {"MODCMD_SUBCOMMANDS",      "Subcommands of %s: %s"}, /* {ARGS: "bot", "ADD, DEL, EDIT"} */
     {NULL, NULL}
 };
 
@@ -103,7 +114,7 @@ static char* get_channel_trigger(int botid, struct ChanNode *chan) {
         return 0;
     }
     trigger->botid = botid;
-    trigger->trigger = strdup(triggerStr);
+    trigger->trigger = (triggerStr[0] ? strdup(triggerStr) : NULL);
     trigger->next = chan->trigger;
     chan->trigger = trigger;
     return trigger->trigger;
@@ -116,9 +127,17 @@ static USERAUTH_CALLBACK(command_checked_auth) {
     tmp_text_client = cache->textclient;
     handle_command_async(cache->client, user, cache->chan, cache->sent_chan, cache->cbind, cache->argv, cache->argc);
     free(cache->message);
+    if(cache->args_buffer)
+        free(cache->args_buffer);
+    free(cache->argv);
     free(cache);
 }
 
+static CMD_BIND(modcmd_linker) {
+    //fake command for subcommands
+    //just empty
+}
+
 static void handle_command(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, char *message) {
     struct ChanNode *sent_chan = chan;
     if(message[0] == '#') {
@@ -134,6 +153,7 @@ static void handle_command(struct ClientSocket *client, struct UserNode *user, s
     message = strdup(message);
     int bind_index = get_binds_index(message[0]);
     char *args = strstr(message, " ");
+    char *args_buffer = NULL; //we need this to save a possible pointer to a allocation we need to free
     if(args) {
         *args = '\0';
         args++;
@@ -141,12 +161,55 @@ static void handle_command(struct ClientSocket *client, struct UserNode *user, s
     struct cmd_binding *cbind;
     for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
         if(cbind->botid == client->botid && stricmp(cbind->cmd, message) == 0) {
+            if(cbind->func->func == modcmd_linker) {
+                //links subcommands
+                char command[MAXLEN];
+                struct cmd_binding *parent_bind = cbind;
+                if(args) {
+                    char *subcmd = args;
+                    args = strstr(args, " ");
+                    if(args) {
+                        *args = '\0';
+                        args++;
+                    }
+                    sprintf(command, "%s %s", parent_bind->cmd, subcmd);
+                    for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
+                        if(cbind->botid == client->botid && stricmp(cbind->cmd, command) == 0)
+                            break;
+                    }
+                    if(!cbind)
+                        break;
+                } else {
+                    //list all sub commands
+                    int commandlen = sprintf(command, "%s ", parent_bind->cmd);
+                    char subcommands[MAXLEN];
+                    int subcompos = 0;
+                    for(cbind = cmd_binds[bind_index]; cbind; cbind = cbind->next) {
+                        if(cbind->botid == client->botid && stricmplen(cbind->cmd, command, commandlen) == 0) {
+                            subcompos += sprintf(subcommands + subcompos, (subcompos ? ", %s" : "%s"), cbind->cmd + commandlen);
+                        }
+                    }
+                    reply(tmp_text_client, user, "MODCMD_SUBCOMMANDS", parent_bind->cmd, subcommands);
+                    break;
+                }
+            }
+            if(statistics_enabled)
+                statistics_commands++;
+            total_triggered++;
+            cbind->triggered++;
+            if((cbind->func->flags & CMDFLAG_FUNCMD)) {
+                if(!sent_chan)
+                    break;
+                chan = sent_chan;
+            }
             //get a text bot
             tmp_text_client = get_prefered_bot(client->botid);
             //parse the arguments...
             char *arga[MAXNUMPARAMS];
             char **argv;
             int argc = 0;
+            int escape = 0;
+            int offset = 0;
             if(args) {
                 while(*args) {
                     //skip leading spaces
@@ -155,8 +218,22 @@ static void handle_command(struct ClientSocket *client, struct UserNode *user, s
                     arga[argc++] = args;
                     if (argc >= MAXNUMPARAMS)
                         break;
-                    while (*args != ' ' && *args)
+                    while ((escape || *args != ' ') && *args) {
+                        if((cbind->func->flags & CMDFLAG_ESCAPE_ARGS) && *args == '\\') {
+                            escape = 1;
+                            offset++;
+                        } else if(escape)
+                            escape = 0;
+                        if(!escape && offset) {
+                            args[0 - offset] = args[0];
+                        }
                         args++;
+                        
+                    }
+                    if(offset) {
+                        args[0-offset] = '\0';
+                        offset = 0;
+                    }
                 }
             }
             argv = arga;
@@ -170,6 +247,8 @@ static void handle_command(struct ClientSocket *client, struct UserNode *user, s
             }
             if(cbind->paramcount) {
                 //userdefined parameters...
+                args_buffer = malloc(MAXLEN * 2 * sizeof(*args_buffer));
+                int args_pos = 0;
                 char *uargs[MAXNUMPARAMS];
                 int uargc = 0;
                 char *b;
@@ -185,25 +264,48 @@ static void handle_command(struct ClientSocket *client, struct UserNode *user, s
                             argi = atoi(b);
                             b[allargs] = '-';
                             allargs = 1;
+                        } else if(b[strlen(b)-1] == '+') {
+                            allargs = strlen(b)-1;
+                            b[allargs] = '\0';
+                            argi = atoi(b);
+                            b[allargs] = '+';
+                            allargs = 2;
                         } else {
                             allargs = 0;
                             argi = atoi(b);
                         }
                         if(argi > 0) {
                             if(argi <= argc) {
-                                uargs[uargc++] = argv[argi-1];
-                                if(allargs) {
-                                    for(argi++; argi <= argc; argi++)
-                                        uargs[uargc++] = argv[argi-1];
+                                uargs[uargc++] = args_buffer + args_pos;
+                                if(allargs == 0) {
+                                    args_pos += sprintf(args_buffer + args_pos, "%s", argv[argi-1]) + 1;
+                                } else if(allargs == 1) {
+                                    args_pos += sprintf(args_buffer + args_pos, "%s", argv[argi-1]) + 1;
+                                    for(argi++; argi <= argc; argi++) {
+                                        uargs[uargc++] = args_buffer + args_pos;
+                                        args_pos += sprintf(args_buffer + args_pos, "%s", argv[argi-1]) + 1;
+                                    }
+                                } else if(allargs == 2) {
+                                    for(;argi <= argc; argi++) {
+                                        args_pos += sprintf(args_buffer + args_pos, (allargs ? "%s" : " %s"), argv[argi-1]);
+                                        allargs = 0;
+                                    }
+                                    args_pos++;
                                 }
+                            } else if((cbind->func->flags & CMDFLAG_EMPTY_ARGS)) {
+                                uargs[uargc++] = args_buffer + args_pos;
+                                args_buffer[args_pos++] = '\0';
                             }
                         } else if(!strcmp(b, "c")) {
-                            uargs[uargc++] = (chan ? chan->name : NULL);
+                            uargs[uargc++] = args_buffer + args_pos;
+                            args_pos += sprintf(args_buffer + args_pos, "%s", (chan ? chan->name : "")) + 1;
                         } else if(!strcmp(b, "n")) {
-                            uargs[uargc++] = user->nick;
+                            uargs[uargc++] = args_buffer + args_pos;
+                            args_pos += sprintf(args_buffer + args_pos, "%s", user->nick) + 1;
                         }
                     } else {
-                        uargs[uargc++] = b;
+                        uargs[uargc++] = args_buffer + args_pos;
+                        args_pos += sprintf(args_buffer + args_pos, "%s", b) + 1;
                     }
                 }
                 argv = uargs;
@@ -233,6 +335,7 @@ static void handle_command(struct ClientSocket *client, struct UserNode *user, s
                 data->chan = chan;
                 data->sent_chan = sent_chan;
                 data->message = message;
+                data->args_buffer = args_buffer;
                 data->cbind = cbind;
                 data->textclient = tmp_text_client;
                 get_userauth(user, command_checked_auth, data);
@@ -243,6 +346,8 @@ static void handle_command(struct ClientSocket *client, struct UserNode *user, s
         }
     }
     free(message);
+    if(args_buffer)
+        free(args_buffer);
 }
 
 static void handle_command_async(struct ClientSocket *client, struct UserNode *user, struct ChanNode *chan, struct ChanNode *sent_chan, struct cmd_binding *cbind, char **argv, int argc) {
@@ -259,8 +364,8 @@ static void handle_command_async(struct ClientSocket *client, struct UserNode *u
         printf_mysql_query("SELECT `channel_pubcmd` FROM `channels` WHERE `channel_name` = '%s'", escape_string(sent_chan->name));
         res = mysql_use();
         if ((row = mysql_fetch_row(res)) != NULL) {
-            uaccess = getChannelAccess(user, sent_chan, 1);
-            if(row[0] && uaccess < atoi(row[0])) { //NOTE: HARDCODED DEFAULT: pubcmd = 0
+            uaccess = getChannelAccess(user, sent_chan);
+            if(row[0] && uaccess < atoi(row[0]) && !isGodMode(user)) { //NOTE: HARDCODED DEFAULT: pubcmd = 0
                 reply(tmp_text_client, user, "MODCMD_PUBCMD", sent_chan->name);
                 return;
             }
@@ -319,7 +424,7 @@ static void handle_command_async(struct ClientSocket *client, struct UserNode *u
                 chan->flags |= CHANFLAG_CHAN_REGISTERED;
                 chan->channel_id = atoi(row[0]);
                 if((sent_chan && sent_chan == chan) || access_count || minaccess) {
-                    uaccess = getChannelAccess(user, chan, 0);
+                    uaccess = getChannelAccess(user, chan);
                     if(uaccess < minaccess && isGodMode(user)) {
                         eventflags |= CMDFLAG_OPLOG;
                     } else if(uaccess < minaccess) {
@@ -377,7 +482,7 @@ static void handle_command_async(struct ClientSocket *client, struct UserNode *u
         reply(tmp_text_client, user, "MODCMD_PRIVILEGED", cbind->cmd);
         return;
     }
-    struct Event *event = createEvent(client, user, chan, cbind->func->name, argv, argc, eventflags);
+    struct Event *event = createEvent(client, user, chan, cbind, argv, argc, eventflags);
     cbind->func->func(client, user, chan, argv, argc, event);
 }
 
@@ -390,7 +495,7 @@ static void got_chanmsg(struct UserNode *user, struct ChanNode *chan, char *mess
         if(isUserOnChan(client->user, chan) && (client->flags & SOCKET_FLAG_PREFERRED) && !FD_ISSET(client->botid, &fds)) {
             FD_SET(client->botid, &fds);
             trigger = get_channel_trigger(client->botid, chan);
-            if(stricmplen(message, trigger, strlen(trigger)) == 0) {
+            if(trigger && stricmplen(message, trigger, strlen(trigger)) == 0) {
                 handle_command(client, user, chan, message + strlen(trigger));
             }
         }
@@ -399,7 +504,7 @@ static void got_chanmsg(struct UserNode *user, struct ChanNode *chan, char *mess
         if(isUserOnChan(client->user, chan) && !FD_ISSET(client->botid, &fds)) {
             FD_SET(client->botid, &fds);
             trigger = get_channel_trigger(client->botid, chan);
-            if(stricmplen(message, trigger, strlen(trigger)) == 0) {
+            if(trigger && stricmplen(message, trigger, strlen(trigger)) == 0) {
                 handle_command(client, user, chan, message + strlen(trigger));
             }
         }
@@ -418,7 +523,7 @@ static void got_privmsg(struct UserNode *user, struct UserNode *target, char *me
 int register_command(int botid, char *name, cmd_bind_t *func, int paramcount, char *channel_access, int global_access, unsigned int flags) {
     struct cmd_function *cmdfunc;
     for(cmdfunc = cmd_functions; cmdfunc; cmdfunc = cmdfunc->next) {
-        if(cmdfunc->botid == botid && strcmp(cmdfunc->name, name) == 0)
+        if((cmdfunc->botid == botid || cmdfunc->botid == 0) && strcmp(cmdfunc->name, name) == 0)
             return 0;
     }
     cmdfunc = malloc(sizeof(*cmdfunc));
@@ -489,6 +594,7 @@ int bind_cmd_to_function(int botid, char *cmd, struct cmd_function *func) {
     cbind->global_access = 0;
     cbind->channel_access = NULL;
     cbind->flags = 0;
+    cbind->triggered = 0;
     cbind->next = cmd_binds[bind_index];
     cmd_binds[bind_index] = cbind;
     return 1;
@@ -496,8 +602,22 @@ int bind_cmd_to_function(int botid, char *cmd, struct cmd_function *func) {
 
 int bind_cmd_to_command(int botid, char *cmd, char *func) {
     struct cmd_function *cmdfunc;
+    int fbotid = botid;
+    char *c;
+    if((c = strstr(func, "."))) {
+        *c = '\0';
+        struct cmd_bot_alias *botalias;
+        for(botalias = bot_aliases; botalias; botalias = botalias->next) {
+            if(!stricmp(botalias->alias, func)) {
+                fbotid = botalias->botid;
+                break;
+            }
+        }
+        *c = '.';
+        func = c+1;
+    }
     for(cmdfunc = cmd_functions; cmdfunc; cmdfunc = cmdfunc->next) {
-        if(cmdfunc->botid == botid && strcmp(cmdfunc->name, func) == 0)
+        if((cmdfunc->botid == fbotid || cmdfunc->botid == 0) && strcmp(cmdfunc->name, func) == 0)
             break;
     }
     if(!cmdfunc) return 0;
@@ -520,6 +640,7 @@ int bind_cmd_to_command(int botid, char *cmd, char *func) {
     cbind->global_access = 0;
     cbind->channel_access = NULL;
     cbind->flags = 0;
+    cbind->triggered = 0;
     cmd_binds[bind_index] = cbind;
     return 1;
 }
@@ -549,8 +670,21 @@ int unbind_cmd(int botid, char *cmd) {
 
 struct cmd_function *find_cmd_function(int botid, char *name) {
     struct cmd_function *cmdfunc;
+    char *c;
+    if((c = strstr(name, "."))) {
+        *c = '\0';
+        struct cmd_bot_alias *botalias;
+        for(botalias = bot_aliases; botalias; botalias = botalias->next) {
+            if(!stricmp(botalias->alias, name)) {
+                botid = botalias->botid;
+                break;
+            }
+        }
+        *c = '.';
+        name = c+1;
+    }
     for(cmdfunc = cmd_functions; cmdfunc; cmdfunc = cmdfunc->next) {
-        if(cmdfunc->botid == botid && stricmp(cmdfunc->name, name) == 0)
+        if((cmdfunc->botid == botid || cmdfunc->botid == 0) && stricmp(cmdfunc->name, name) == 0)
             break;
     }
     return cmdfunc;
@@ -565,6 +699,7 @@ void init_modcmd() {
     bind_chanmsg(got_chanmsg);
     bind_privmsg(got_privmsg);
     register_default_language_table(msgtab);
+    register_command(0, "linker", modcmd_linker, 0, 0, 0, 0); //fake command for subcommands
 }
 
 void free_modcmd() {
@@ -596,8 +731,15 @@ void free_modcmd() {
         next_cb = cb->next;
         free(next_cb);
     }
+    struct cmd_bot_alias *botalias, *next_botalias;
+    for(botalias = bot_aliases; botalias; botalias = next_botalias) {
+        next_botalias = botalias->next;
+        free(botalias->alias);
+        free(botalias);
+    }
     cmd_functions = NULL;
     trigger_callbacks = NULL;
+    bot_aliases = NULL;
 }
 
 void bind_set_parameters(int botid, char *cmd, char *parameters) {
@@ -669,3 +811,62 @@ struct cmd_binding *find_cmd_binding(int botid, char *cmd) {
     return NULL;
 }
 
+void bind_unbound_required_functions(int botid) {
+    struct cmd_function *cmdfunc;
+    int i, found;
+    struct cmd_binding *cbind;
+    for(cmdfunc = cmd_functions; cmdfunc; cmdfunc = cmdfunc->next) {
+        if((cmdfunc->flags & CMDFLAG_REQUIRED)) {
+            found = 0;
+            for(i = 0; i < 27; i++) {
+                for(cbind = cmd_binds[i]; cbind; cbind = cbind->next) {
+                    if(cbind->botid == botid && cbind->func == cmdfunc) {
+                        found = 1;
+                        break;
+                    }
+                }
+                if(found)
+                    break;
+            }
+            if(!found && bind_cmd_to_function(botid, cmdfunc->name, cmdfunc)) {
+                cbind = find_cmd_binding(botid, cmdfunc->name);
+                cbind->flags |= CMDFLAG_TEMPONARY_BIND;
+            }
+        }
+    }
+}
+
+void register_command_alias(int botid, char *alias) {
+    struct cmd_bot_alias *botalias;
+    for(botalias = bot_aliases; botalias; botalias = botalias->next) {
+        if(!stricmp(botalias->alias, alias))
+            return;
+    }
+    botalias = malloc(sizeof(*botalias));
+    if (!botalias) {
+        perror("malloc() failed");
+        return;
+    }
+    botalias->botid = botid;
+    botalias->alias = strdup(alias);
+    botalias->next = bot_aliases;
+    bot_aliases = botalias;
+}
+
+struct cmd_binding *getAllBinds(struct cmd_binding *last) {
+    int bind_index;
+    if(last) {
+        if(last->next)
+            return last->next;
+        bind_index = get_binds_index(last->cmd[0]) + 1;
+        if(bind_index > 26)
+            return NULL;
+    } else
+        bind_index = 0;
+    do {
+        if(cmd_binds[bind_index])
+            return cmd_binds[bind_index];
+        bind_index++;
+    } while(bind_index < 27);
+    return NULL;
+}