]> jfr.im git - irc/quakenet/newserv.git/blobdiff - qabot/qabot_chancommands.c
Implement --help parameter.
[irc/quakenet/newserv.git] / qabot / qabot_chancommands.c
index 2b27cd7c3c0e0c022d0d8c6e51f00cad9658ebcf..36a38fe4158fad637a69de770a45a2ed2aeee115 100644 (file)
-#include <stdio.h>\r
-#include <string.h>\r
-#include <time.h>\r
-\r
-#include "../nick/nick.h"\r
-#include "../localuser/localuserchannel.h"\r
-#include "../core/hooks.h"\r
-#include "../core/schedule.h"\r
-#include "../lib/array.h"\r
-#include "../lib/base64.h"\r
-#include "../lib/irc_string.h"\r
-#include "../lib/splitline.h"\r
-\r
-#include "qabot.h"\r
-\r
-int qabot_dochananswer(void* np, int cargc, char** cargv) {\r
-  nick* sender = (nick*)np;\r
-  qab_bot* bot = qabot_getcurrentbot();\r
-  channel* cp = qabot_getcurrentchannel();\r
-  int id;\r
-  char* ch;\r
-  qab_question* q;\r
-  \r
-  if (cargc < 2) {\r
-    sendnoticetouser(bot->np, sender, "Syntax: !answer <id> <answer>");\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  id = strtol(cargv[0], NULL, 10);\r
-  ch = cargv[1];\r
-  \r
-  if ((id < 1) || (id > bot->lastquestionID)) {\r
-    sendnoticetouser(bot->np, sender, "Invalid question ID %d.", id);\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  for (q = bot->questions[id % QUESTIONHASHSIZE]; q; q = q->next)\r
-    if (q->id == id)\r
-      break;\r
-  \r
-  if (!q) {\r
-    sendnoticetouser(bot->np, sender, "Can't find question %d.", id);\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  switch (q->flags & QAQ_QSTATE) {\r
-  case QAQ_ANSWERED:\r
-    sendnoticetouser(bot->np, sender, "Question %d has already been answered.", id);\r
-    return CMD_ERROR;\r
-    \r
-  case QAQ_OFFTOPIC:\r
-    sendnoticetouser(bot->np, sender, "Question %d has been marked as off-topic.", id);\r
-    return CMD_ERROR;\r
-  \r
-  case QAQ_SPAM:\r
-    sendnoticetouser(bot->np, sender, "Question %d has been marked as spam.", id);\r
-    return CMD_ERROR;\r
-  \r
-  default:\r
-    break;\r
-  }\r
-  \r
-  q->flags = ((q->flags) & ~QAQ_QSTATE) | QAQ_ANSWERED;\r
-  q->answer = strdup(ch);\r
-  \r
-  bot->answered++;\r
-  \r
-  if (!bot->nextspam && !bot->micnumeric) {\r
-    sendmessagetochannel(bot->np, bot->public_chan->channel, "%s asked: %s", q->nick, q->question);\r
-    sendmessagetochannel(bot->np, bot->public_chan->channel, "%s answers: %s", sender->nick, ch);\r
-  }\r
-  else {\r
-    qab_answer* a;\r
-    \r
-    a = (qab_answer*)malloc(sizeof(qab_answer));\r
-    a->question = q;\r
-    strncpy(a->nick, sender->nick, NICKLEN);\r
-    a->nick[NICKLEN] = '\0';\r
-    a->next = bot->answers;\r
-    bot->answers = a;\r
-    \r
-    sendnoticetouser(bot->np, sender, "Can't send your answer right now. Answer was stored and will be sent later on.");\r
-    return CMD_OK;\r
-  }\r
-  \r
-  sendnoticetouser(bot->np, sender, "Answer to question %d has been sent and stored.", id);\r
-  \r
-  return CMD_OK;\r
-}\r
-\r
-int qabot_dochanblock(void* np, int cargc, char** cargv) {\r
-  nick* sender = (nick*)np;\r
-  qab_bot* bot = qabot_getcurrentbot();\r
-  channel* cp = qabot_getcurrentchannel();\r
-  qab_block* b;\r
-  \r
-  if (cargc < 1) {\r
-    sendnoticetouser(bot->np, sender, "Syntax: !block [-q|-t] <account|mask>");\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  if (cargc > 1) {\r
-    if (!ircd_strncmp(cargv[0], "-q", 2)) {\r
-      /* account block */\r
-      char* target = cargv[1];\r
-      \r
-      if (*target == '#') {\r
-        target++;\r
-        \r
-        if (strchr(target, '*') || strchr(target, '?')) {\r
-          sendnoticetouser(bot->np, sender, "Wildcard account blocks are not supported.");\r
-          return CMD_ERROR;\r
-        }\r
-      }\r
-      else {\r
-        nick* tnick;\r
-        \r
-        if (!(tnick = getnickbynick(target))) {\r
-          sendnoticetouser(bot->np, sender, "Couldn't find user %s.", target);\r
-          return CMD_ERROR;\r
-        }\r
-        \r
-        if (!IsAccount(tnick)) {\r
-           sendnoticetouser(bot->np, sender, "%s is not authed.", tnick->nick);\r
-           return CMD_ERROR;\r
-        }\r
-        \r
-        target = tnick->authname;\r
-      }\r
-      \r
-      b = (qab_block*)malloc(sizeof(qab_block));\r
-      b->type = QABBLOCK_ACCOUNT;\r
-      b->created = time(0);\r
-      strncpy(b->creator, IsAccount(sender) ? sender->authname : "UNKNOWN", ACCOUNTLEN);\r
-      b->creator[ACCOUNTLEN] = '\0';\r
-      b->blockstr = strdup(target);\r
-      b->prev = 0;\r
-      b->next = bot->blocks;\r
-      if (bot->blocks)\r
-        bot->blocks->prev = b;\r
-      bot->blocks = b;\r
-      bot->block_count++;\r
-      \r
-      sendnoticetouser(bot->np, sender, "Now blocking all messages from users with accountname %s.", target);\r
-      \r
-      if (bot->flags & QAB_BLOCKMARK) {\r
-        qab_question* q;\r
-        int i, spamqcount = 0;\r
-        nick* qnick;\r
-        \r
-        for (i = 0; i < QUESTIONHASHSIZE; i++) {\r
-          for (q = bot->questions[i]; q; q = q->next) {\r
-            if ((q->flags & QAQ_QSTATE) != QAQ_NEW)\r
-              continue;\r
-            \r
-            if (!(qnick = getnickbynumeric(q->numeric)))\r
-              continue;\r
-            \r
-            if (!IsAccount(qnick))\r
-              continue;\r
-            \r
-            if (ircd_strcmp(qnick->authname, b->blockstr))\r
-              continue;\r
-            \r
-            q->flags = ((q->flags) & ~QAQ_QSTATE) | QAQ_SPAM;\r
-            spamqcount++;\r
-          }\r
-        }\r
-        \r
-        sendnoticetouser(bot->np, sender, "Block caused %d message%s to be marked as spam.", spamqcount, (spamqcount == 1) ? "" : "s");\r
-      }\r
-    }\r
-    else if (!ircd_strncmp(cargv[0], "-t", 2)) {\r
-      /* text block */\r
-      char* mask = cargv[1];\r
-      \r
-      b = (qab_block*)malloc(sizeof(qab_block));\r
-      b->type = QABBLOCK_TEXT;\r
-      b->created = time(0);\r
-      strncpy(b->creator, sender->authname, ACCOUNTLEN);\r
-      b->creator[ACCOUNTLEN] = '\0';\r
-      b->blockstr = strdup(mask);\r
-      b->prev = 0;\r
-      b->next = bot->blocks;\r
-      if (bot->blocks)\r
-        bot->blocks->prev = b;\r
-      bot->blocks = b;\r
-      bot->block_count++;\r
-      \r
-      sendnoticetouser(bot->np, sender, "Now blocking all questions which match %s.", mask);\r
-      \r
-      if (bot->flags & QAB_BLOCKMARK) {\r
-        qab_question* q;\r
-        int i, spamqcount = 0;\r
-        \r
-        for (i = 0; i < QUESTIONHASHSIZE; i++) {\r
-          for (q = bot->questions[i]; q; q = q->next) {\r
-            if ((q->flags & QAQ_QSTATE) != QAQ_NEW)\r
-              continue;\r
-            \r
-            if (match(b->blockstr, q->question))\r
-              continue;\r
-            \r
-            q->flags = ((q->flags) & ~QAQ_QSTATE) | QAQ_SPAM;\r
-            spamqcount++;\r
-          }\r
-        }\r
-        \r
-        sendnoticetouser(bot->np, sender, "Block caused %d message%s to be marked as spam.", spamqcount, (spamqcount == 1) ? "" : "s");\r
-      }\r
-    }\r
-    else {\r
-      sendnoticetouser(bot->np, sender, "Invalid flag.");\r
-      return CMD_ERROR;\r
-    }\r
-  }\r
-  else {\r
-    /* hostmask block */\r
-    char* mask = cargv[0];\r
-    \r
-    if (!strchr(mask, '@') || !strchr(mask, '!')) {\r
-      sendnoticetouser(bot->np, sender, "%s is not a valid hostmask.", mask);\r
-      return;\r
-    }\r
-    \r
-    b = (qab_block*)malloc(sizeof(qab_block));\r
-    b->type = QABBLOCK_HOST;\r
-    b->created = time(0);\r
-    strncpy(b->creator, sender->authname, ACCOUNTLEN);\r
-    b->creator[ACCOUNTLEN] = '\0';\r
-    b->blockstr = strdup(mask);\r
-    b->prev = 0;\r
-    b->next = bot->blocks;\r
-    if (bot->blocks)\r
-      bot->blocks->prev = b;\r
-    bot->blocks = b;\r
-    bot->block_count++;\r
-    \r
-    sendnoticetouser(bot->np, sender, "Now blocking all messages from users with a hostmask matching %s.", mask);\r
-    \r
-    if (bot->flags & QAB_BLOCKMARK) {\r
-        qab_question* q;\r
-        int i, spamqcount = 0;\r
-        nick* qnick;\r
-        char hostbuf[NICKLEN + USERLEN + HOSTLEN + 3];\r
-        \r
-        for (i = 0; i < QUESTIONHASHSIZE; i++) {\r
-          for (q = bot->questions[i]; q; q = q->next) {\r
-            if ((q->flags & QAQ_QSTATE) != QAQ_NEW)\r
-              continue;\r
-            \r
-            if (!(qnick = getnickbynumeric(q->numeric)))\r
-              continue;\r
-            \r
-            sprintf(hostbuf,"%s!%s@%s", qnick->nick, qnick->ident, qnick->host->name->content);\r
-            \r
-            if (match(b->blockstr, hostbuf))\r
-              continue;\r
-            \r
-            q->flags = ((q->flags) & ~QAQ_QSTATE) | QAQ_SPAM;\r
-            spamqcount++;\r
-          }\r
-        }\r
-        \r
-        sendnoticetouser(bot->np, sender, "Block caused %d message%s to be marked as spam.", spamqcount, (spamqcount == 1) ? "" : "s");\r
-      }\r
-  }\r
-  \r
-  return CMD_OK;\r
-}\r
-\r
-int qabot_dochanclear(void* np, int cargc, char** cargv) {\r
-  nick* sender = (nick*)np;\r
-  qab_bot* bot = qabot_getcurrentbot();\r
-  channel* cp = qabot_getcurrentchannel();\r
-  qab_spam* s;\r
-  qab_spam* ns;\r
-  qab_answer* a;\r
-  qab_answer* na;\r
-  \r
-  for (s = bot->nextspam; s; s = ns) {\r
-    ns = s->next;\r
-    free(s->message);\r
-    free(s);\r
-  }\r
-  \r
-  bot->nextspam = bot->lastspam = 0;\r
-  \r
-  sendmessagetochannel(bot->np, cp, "Cleared message buffer.");\r
-  if (bot->micnumeric) {\r
-    bot->micnumeric = 0;\r
-    sendmessagetochannel(bot->np, cp, "Mic deactivated.");\r
-  }\r
-  \r
-  return CMD_OK;\r
-}\r
-\r
-int qabot_dochanclosechan(void* np, int cargc, char** cargv) {\r
-  nick* sender = (nick*)np;\r
-  qab_bot* bot = qabot_getcurrentbot();\r
-  channel* cp = qabot_getcurrentchannel();\r
-  modechanges changes;\r
-  \r
-  localsetmodeinit(&changes, bot->public_chan->channel, bot->np);\r
-  localdosetmode_simple(&changes, CHANMODE_INVITEONLY, 0);\r
-  localsetmodeflush(&changes, 1);\r
-  sendnoticetouser(bot->np, sender, "Public channel has been closed.");\r
-  \r
-  return CMD_OK;\r
-}\r
-\r
-int qabot_dochanconfig(void* np, int cargc, char** cargv) {\r
-  nick* sender = (nick*)np;\r
-  qab_bot* bot = qabot_getcurrentbot();\r
-  channel* cp = qabot_getcurrentchannel();\r
-  char* opt;\r
-  char* value;\r
-  \r
-  if (cargc < 1) {\r
-    sendnoticetouser(bot->np, sender, "Syntax: !config <option> [<value>]");\r
-    sendnoticetouser(bot->np, sender, "Displays or sets configuration option. Valid options are:");\r
-    sendnoticetouser(bot->np, sender, "blockcontrol - Block questions containing control chars.");\r
-    sendnoticetouser(bot->np, sender, "blockcolour  - Block questions containing colour.");\r
-    sendnoticetouser(bot->np, sender, "blockmark    - Mark questions affected by blocks as spam.");\r
-    sendnoticetouser(bot->np, sender, "authedonly   - Accept questions from authed users only.");\r
-    sendnoticetouser(bot->np, sender, "linebreak    - Separate questions with line breaks.");\r
-    sendnoticetouser(bot->np, sender, "flooddetect  - Attempt to detect floodclone spam.");\r
-    sendnoticetouser(bot->np, sender, "floodblock   - Automatically block floodclone spam.");\r
-    sendnoticetouser(bot->np, sender, "spamint      - Text spam interval.");\r
-    sendnoticetouser(bot->np, sender, "nickblockint - Time to wait before sending another question.");\r
-    sendnoticetouser(bot->np, sender, "queuedqint   - Queued answer spam interval.");\r
-    sendnoticetouser(bot->np, sender, "mictimeout   - Idle time before mic is automatically disabled.");\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  opt = cargv[0];\r
-  if (cargc == 2)\r
-    value = cargv[1];\r
-  else\r
-    value = 0;\r
-  \r
-  if (!ircd_strcmp(opt, "blockcontrol")) {\r
-    if (value) {\r
-      if (!ircd_strcmp(value, "on")) {\r
-        bot->flags |= QAB_CONTROLCHAR;\r
-        sendnoticetouser(bot->np, sender, "Questions containing control characters will now be blocked.");\r
-      }\r
-      else if (!ircd_strcmp(value, "off")) {\r
-        bot->flags &= (~QAB_CONTROLCHAR);\r
-        sendnoticetouser(bot->np, sender, "Questions containing control characters will no longer be blocked.");\r
-      }\r
-      else\r
-        sendnoticetouser(bot->np, sender, "Invalid option. Valid options are 'on' or 'off'.");\r
-    }\r
-    else\r
-      sendnoticetouser(bot->np, sender, "Control characters are currently %s blocked.", (bot->flags & QAB_CONTROLCHAR) ? "being" : "not being");\r
-  }\r
-  else if (!ircd_strcmp(opt, "blockcolour")) {\r
-    if (value) {\r
-      if (!ircd_strcmp(value, "on")) {\r
-        bot->flags |= QAB_COLOUR;\r
-        sendnoticetouser(bot->np, sender, "Questions containing colour will now be blocked.");\r
-      }\r
-      else if (!ircd_strcmp(value, "off")) {\r
-        bot->flags &= (~QAB_COLOUR);\r
-        sendnoticetouser(bot->np, sender, "Questions containing colour will no longer be blocked.");\r
-      }\r
-      else\r
-        sendnoticetouser(bot->np, sender, "Invalid option. Valid options are 'on' or 'off'.");\r
-    }\r
-    else\r
-      sendnoticetouser(bot->np, sender, "Colours are currently %s blocked.", (bot->flags & QAB_COLOUR) ? "being" : "not being");\r
-  }\r
-  else if (!ircd_strcmp(opt, "blockmark")) {\r
-    if (value) {\r
-      if (!ircd_strcmp(value, "on")) {\r
-        bot->flags |= QAB_BLOCKMARK;\r
-        sendnoticetouser(bot->np, sender, "New blocks will automatically mark affected questions as spam.");\r
-      }\r
-      else if (!ircd_strcmp(value, "off")) {\r
-        bot->flags &= (~QAB_BLOCKMARK);\r
-        sendnoticetouser(bot->np, sender, "New blocks will no longer automatically mark affected questions as spam.");\r
-      }\r
-      else\r
-        sendnoticetouser(bot->np, sender, "Invalid option. Valid options are 'on' or 'off'.");\r
-    }\r
-    else\r
-      sendnoticetouser(bot->np, sender, "Blocks are currently %smarking affected questions as spam.", (bot->flags & QAB_BLOCKMARK) ? "" : "not ");\r
-  }\r
-  else if (!ircd_strcmp(opt, "authedonly")) {\r
-    if (value) {\r
-      if (!ircd_strcmp(value, "on")) {\r
-        bot->flags |= QAB_AUTHEDONLY;\r
-        sendnoticetouser(bot->np, sender, "Questions from unauthed users will now be blocked.");\r
-      }\r
-      else if (!ircd_strcmp(value, "off")) {\r
-        bot->flags &= (~QAB_AUTHEDONLY);\r
-        sendnoticetouser(bot->np, sender, "Questions from unauthed users will no longer be blocked.");\r
-      }\r
-      else\r
-        sendnoticetouser(bot->np, sender, "Invalid option. Valid options are 'on' or 'off'.");\r
-    }\r
-    else\r
-      sendnoticetouser(bot->np, sender, "Unauthed users may currently %ssend questions.", (bot->flags & QAB_AUTHEDONLY) ? "NOT " : "");\r
-  }\r
-  else if (!ircd_strcmp(opt, "linebreak")) {\r
-    if (value) {\r
-      if (!ircd_strcmp(value, "on")) {\r
-        bot->flags |= QAB_LINEBREAK;\r
-        sendnoticetouser(bot->np, sender, "Line breaks are now enabled.");\r
-      }\r
-      else if (!ircd_strcmp(value, "off")) {\r
-        bot->flags &= (~QAB_LINEBREAK);\r
-        sendnoticetouser(bot->np, sender, "Line breaks are now disabled.");\r
-      }\r
-      else\r
-        sendnoticetouser(bot->np, sender, "Invalid option. Valid options are 'on' or 'off'.");\r
-    }\r
-    else\r
-      sendnoticetouser(bot->np, sender, "Line breaks are currently %s.", (bot->flags & QAB_LINEBREAK) ? "enabled" : "disabled");\r
-  }\r
-  else if (!ircd_strcmp(opt, "flooddetect")) {\r
-    if (value) {\r
-      if (!ircd_strcmp(value, "on")) {\r
-        bot->flags |= QAB_FLOODDETECT;\r
-        sendnoticetouser(bot->np, sender, "Flood detection is now enabled.");\r
-      }\r
-      else if (!ircd_strcmp(value, "off")) {\r
-        bot->flags &= (~QAB_FLOODDETECT);\r
-        sendnoticetouser(bot->np, sender, "Flood detection is now disabled.");\r
-      }\r
-      else\r
-        sendnoticetouser(bot->np, sender, "Invalid option. Valid options are 'on' or 'off'.");\r
-    }\r
-    else\r
-      sendnoticetouser(bot->np, sender, "Flood detection is currently %s.", (bot->flags & QAB_FLOODDETECT) ? "enabled" : "disabled");\r
-  }\r
-  else if (!ircd_strcmp(opt, "floodblock")) {\r
-    if (value) {\r
-      if (!ircd_strcmp(value, "on")) {\r
-        bot->flags |= QAB_FLOODSTOP;\r
-        sendnoticetouser(bot->np, sender, "Flood blocking is now enabled.");\r
-      }\r
-      else if (!ircd_strcmp(value, "off")) {\r
-        bot->flags &= (~QAB_FLOODSTOP);\r
-        sendnoticetouser(bot->np, sender, "Flood blocking is now disabled.");\r
-      }\r
-      else\r
-        sendnoticetouser(bot->np, sender, "Invalid option. Valid options are 'on' or 'off'.");\r
-    }\r
-    else\r
-      sendnoticetouser(bot->np, sender, "Flood blocking is currently %s.", (bot->flags & QAB_FLOODSTOP) ? "enabled" : "disabled");\r
-  }\r
-  else if (!ircd_strcmp(opt, "mictimeout")) {\r
-    if (value) {\r
-      int v = (int)strtol(value, NULL, 10);\r
-      \r
-      if ((v < 0) || (v > 300)) {\r
-        sendnoticetouser(bot->np, sender, "Value must be between 0 (off) and 300.");\r
-        return CMD_ERROR;\r
-      }\r
-      \r
-      bot->mic_timeout = v;\r
-      sendnoticetouser(bot->np, sender, "Value set.");\r
-    }\r
-    else {\r
-      if (bot->mic_timeout)\r
-        sendnoticetouser(bot->np, sender, "Mic timeout is currently %d second%s.", bot->mic_timeout, (bot->mic_timeout == 1) ? "" : "s");\r
-      else\r
-        sendnoticetouser(bot->np, sender, "Mic timeout is currently disabled.");\r
-    }\r
-  }\r
-  else if (!ircd_strcmp(opt, "spamint")) {\r
-    if (value) {\r
-      int v = (int)strtol(value, NULL, 10);\r
-      \r
-      if ((v < 1) || (v > 30)) {\r
-        sendnoticetouser(bot->np, sender, "Value must be between 1 and 30.");\r
-        return CMD_ERROR;\r
-      }\r
-      \r
-      bot->spam_interval = v;\r
-      sendnoticetouser(bot->np, sender, "Value set.");\r
-    }\r
-    else\r
-      sendnoticetouser(bot->np, sender, "Spam interval is currently %d second%s.", bot->spam_interval, (bot->spam_interval == 1) ? "" : "s");\r
-  }\r
-  else if (!ircd_strcmp(opt, "nickblockint")) {\r
-    if (value) {\r
-      int v = (int)strtol(value, NULL, 10);\r
-      \r
-      if ((v < 1) || (v > 300)) {\r
-        sendnoticetouser(bot->np, sender, "Value must be between 1 and 300.");\r
-        return CMD_ERROR;\r
-      }\r
-      \r
-      bot->ask_wait = v;\r
-      sendnoticetouser(bot->np, sender, "Value set.");\r
-    }\r
-    else\r
-      sendnoticetouser(bot->np, sender, "Nick block interval is currently %d second%s.", bot->ask_wait, (bot->ask_wait == 1) ? "" : "s");\r
-  }\r
-  else if (!ircd_strcmp(opt, "queuedqint")) {\r
-    if (value) {\r
-      int v = (int)strtol(value, NULL, 10);\r
-      \r
-      if ((v < 1) || (v > 20)) {\r
-        sendnoticetouser(bot->np, sender, "Value must be between 1 and 20.");\r
-        return CMD_ERROR;\r
-      }\r
-      \r
-      bot->queued_question_interval = v;\r
-      sendnoticetouser(bot->np, sender, "Value set.");\r
-    }\r
-    else\r
-      sendnoticetouser(bot->np, sender, "Queued question interval is currently %d second%s.", bot->queued_question_interval, (bot->queued_question_interval == 1) ? "" : "s");\r
-  }\r
-  else\r
-    sendnoticetouser(bot->np, sender, "Invalid configuration option.");\r
-  \r
-  return CMD_OK;\r
-}\r
-\r
-int qabot_dochanhelp(void* np, int cargc, char** cargv) {\r
-  nick* sender = (nick*)np;\r
-  qab_bot* bot = qabot_getcurrentbot();\r
-  channel* cp = qabot_getcurrentchannel();\r
-  char* ch;\r
-  \r
-  if (cargc < 1)\r
-    ch = "";\r
-  else\r
-    ch = cargv[0];\r
-  \r
-  if (*ch) {\r
-    if (!ircd_strcmp(ch, "mic")) {\r
-      sendnoticetouser(bot->np, sender, "Syntax: !mic");\r
-      sendnoticetouser(bot->np, sender, "Turn the microphone on or off. When turned on, anything said by the microphone holder is relayed to %s.", bot->public_chan->name->content);\r
-    }\r
-    else if (!ircd_strcmp(ch, "clear")) {\r
-      sendnoticetouser(bot->np, sender, "Syntax: !clear");\r
-      sendnoticetouser(bot->np, sender, "Clear currently queued text to relay, and turn off the microphone.");\r
-    }\r
-    else if (!ircd_strcmp(ch, "ping")) {\r
-      sendnoticetouser(bot->np, sender, "Syntax: !ping");\r
-      sendnoticetouser(bot->np, sender, "Pings the bot.");\r
-    }\r
-    else if (!ircd_strcmp(ch, "config")) {\r
-      sendnoticetouser(bot->np, sender, "Syntax: !config <option|help> [<value>]");\r
-      sendnoticetouser(bot->np, sender, "Display or set bot configuration options.");\r
-    }\r
-    else if (!ircd_strcmp(ch, "answer")) {\r
-      sendnoticetouser(bot->np, sender, "Syntax: !answer <id> <answer>");\r
-      sendnoticetouser(bot->np, sender, "Answer a question.");\r
-    }\r
-    else if (!ircd_strcmp(ch, "block")) {\r
-      sendnoticetouser(bot->np, sender, "Syntax: !block [<-q|-t>] <mask>");\r
-      sendnoticetouser(bot->np, sender, "Add a block, where:");\r
-      sendnoticetouser(bot->np, sender, "-q: blocks a Q account.");\r
-      sendnoticetouser(bot->np, sender, "-t: blocks question text.");\r
-      sendnoticetouser(bot->np, sender, "No flag results in a hostmask block.");\r
-    }\r
-    else if (!ircd_strcmp(ch, "listblocks")) {\r
-      sendnoticetouser(bot->np, sender, "Syntax: !listblocks");\r
-      sendnoticetouser(bot->np, sender, "View the currently added blocks.");\r
-    }\r
-    else if (!ircd_strcmp(ch, "spam")) {\r
-      sendnoticetouser(bot->np, sender, "Syntax: !spam <id>");\r
-      sendnoticetouser(bot->np, sender, "Mark a question as spam. This stops it being answered.");\r
-    }\r
-    else if (!ircd_strcmp(ch, "offtopic")) {\r
-      sendnoticetouser(bot->np, sender, "Syntax: !offtopic <id>");\r
-      sendnoticetouser(bot->np, sender, "Mark a question as off-topic. This stops it being answered.");\r
-    }\r
-    else if (!ircd_strcmp(ch, "unblock")) {\r
-      sendnoticetouser(bot->np, sender, "Syntax: !unblock [<-q|-t>] <mask>");\r
-      sendnoticetouser(bot->np, sender, "Removes a block. See \"!help block\" for a description of the flags.");\r
-    }\r
-    else if (!ircd_strcmp(ch, "reset")) {\r
-      sendnoticetouser(bot->np, sender, "Syntax: !reset <all|questions|blocks|stats>");\r
-      sendnoticetouser(bot->np, sender, "Reset the questions, blocks or both; or the stats.");\r
-    }\r
-    else if (!ircd_strcmp(ch, "closechan")) {\r
-      sendnoticetouser(bot->np, sender, "Syntax: !closechan");\r
-      sendnoticetouser(bot->np, sender, "Closes the public channel.");\r
-    }\r
-    else if (!ircd_strcmp(ch, "openchan")) {\r
-      sendnoticetouser(bot->np, sender, "Syntax: !openchan");\r
-      sendnoticetouser(bot->np, sender, "Opens the public channel.");\r
-    }\r
-    else if (!ircd_strcmp(ch, "status")) {\r
-      sendnoticetouser(bot->np, sender, "Syntax: !status");\r
-      sendnoticetouser(bot->np, sender, "Displays some status information and statistics.");\r
-    }\r
-    else if (!ircd_strcmp(ch, "help")) {\r
-      sendnoticetouser(bot->np, sender, "Syntax !help [<command>]");\r
-      sendnoticetouser(bot->np, sender, "List available commands or view help for a particular command.");\r
-    }\r
-    else {\r
-      sendnoticetouser(bot->np, sender, "No help available for '%s'.", ch);\r
-    }\r
-  }\r
-  else {\r
-    sendnoticetouser(bot->np, sender, "The following channel commands are recognised:");\r
-    sendnoticetouser(bot->np, sender, "!answer     - Answer a question.");\r
-    sendnoticetouser(bot->np, sender, "!block      - Block a hostmask, account or string.");\r
-    sendnoticetouser(bot->np, sender, "!clear      - Clear currently queued text to spam.");\r
-    sendnoticetouser(bot->np, sender, "!closechan  - Close the public channel.");\r
-    sendnoticetouser(bot->np, sender, "!config     - Display or set bot configuration options.");\r
-    sendnoticetouser(bot->np, sender, "!help       - List commands or view the help for a command");\r
-    sendnoticetouser(bot->np, sender, "!listblocks - List currently added blocks.");\r
-    sendnoticetouser(bot->np, sender, "!mic        - Turn the microphone on or off.");\r
-    sendnoticetouser(bot->np, sender, "!offtopic   - Mark a question or questions as off-topic.");\r
-    sendnoticetouser(bot->np, sender, "!openchan   - Open the public channel.");\r
-    sendnoticetouser(bot->np, sender, "!ping       - Ping the bot.");\r
-    sendnoticetouser(bot->np, sender, "!reset      - Clear all blocks, questions or both.");\r
-    sendnoticetouser(bot->np, sender, "!spam       - Mark a question or questions as spam.");\r
-    sendnoticetouser(bot->np, sender, "!status     - Display some status statistics.");\r
-    sendnoticetouser(bot->np, sender, "!unblock    - Remove a block.");\r
-    sendnoticetouser(bot->np, sender, "End of list.");\r
-  }\r
-  \r
-  return CMD_OK;\r
-}\r
-\r
-int qabot_dochanlistblocks(void* np, int cargc, char** cargv) {\r
-  nick* sender = (nick*)np;\r
-  qab_bot* bot = qabot_getcurrentbot();\r
-  channel* cp = qabot_getcurrentchannel();\r
-  qab_block* b;\r
-  \r
-  if (!(b = bot->blocks)) {\r
-    sendnoticetouser(bot->np, sender, "There are no blocks currently added.");\r
-    return;\r
-  }\r
-  \r
-  sendnoticetouser(bot->np, sender, "Type: Hostmask/Account/Textmask:");\r
-  \r
-  for (; b; b = b->next) {\r
-    if (b->type == QABBLOCK_ACCOUNT)\r
-      sendnoticetouser(bot->np, sender, "A     %s", b->blockstr);\r
-    else if (b->type == QABBLOCK_HOST)\r
-      sendnoticetouser(bot->np, sender, "H     %s", b->blockstr);\r
-    else\r
-      sendnoticetouser(bot->np, sender, "T     %s", b->blockstr);\r
-  }\r
-  \r
-  sendnoticetouser(bot->np, sender, "End of list.");\r
-  \r
-  return CMD_OK;\r
-}\r
-\r
-int qabot_dochanmic(void* np, int cargc, char** cargv) {\r
-  nick* sender = (nick*)np;\r
-  qab_bot* bot = qabot_getcurrentbot();\r
-  channel* cp = qabot_getcurrentchannel();\r
-  \r
-  if (bot->micnumeric) {\r
-    if (bot->micnumeric == sender->numeric) {\r
-      bot->micnumeric = 0;\r
-      sendmessagetochannel(bot->np, cp, "Mic deactivated.");\r
-      if (!bot->lastspam)\r
-        qabot_spamstored((void*)bot);\r
-    }\r
-    else {\r
-      bot->lastmic = time(NULL);\r
-      bot->micnumeric = sender->numeric;\r
-      sendmessagetochannel(bot->np, cp, "%s now has the mic. Anything said by %s will be relayed in %s.", \r
-        sender->nick, sender->nick, bot->public_chan->name->content);\r
-      deleteschedule(0, qabot_spamstored, (void*)bot);\r
-    }\r
-  }\r
-  else {\r
-    bot->lastmic = time(NULL);\r
-    bot->micnumeric = sender->numeric;\r
-    sendmessagetochannel(bot->np, cp, "Mic activated. Anything said by %s will be relayed in %s.", \r
-      sender->nick, bot->public_chan->name->content);\r
-    deleteschedule(0, qabot_spamstored, (void*)bot);\r
-  }\r
-  \r
-  return CMD_OK;\r
-}\r
-\r
-int qabot_dochanmoo(void* np, int cargc, char** cargv) {\r
-  nick* sender = (nick*)np;\r
-  qab_bot* bot = qabot_getcurrentbot();\r
-  channel* cp = qabot_getcurrentchannel();\r
-  char moostr[50];\r
-  int i, moocount = 5 + (rand() % 40);\r
-  \r
-  moostr[0] = 'm';\r
-  for (i = 1; i < moocount; i++)\r
-    moostr[i] = ((rand() % 100) > 50) ? 'o': '0';\r
-  moostr[i] = '\0';\r
-  \r
-  sendmessagetochannel(bot->np, cp, "%s", moostr);\r
-  \r
-  return CMD_OK;\r
-}\r
-\r
-int qabot_dochanofftopic(void* np, int cargc, char** cargv) {\r
-  nick* sender = (nick*)np;\r
-  qab_bot* bot = qabot_getcurrentbot();\r
-  channel* cp = qabot_getcurrentchannel();\r
-  int id;\r
-  int i;\r
-  qab_question* q;\r
-  \r
-  if (cargc < 1) {\r
-    sendnoticetouser(bot->np, sender, "Syntax: !spam <id> [<id> ... <id>]");\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  for (i = 0; i < cargc; i++) {\r
-    id = strtol(cargv[i], NULL, 10);\r
-    \r
-    if ((id < 1) || (id > bot->lastquestionID)) {\r
-      sendnoticetouser(bot->np, sender, "Invalid question ID %d.", id);\r
-      continue;\r
-    }\r
-    \r
-    for (q = bot->questions[id % QUESTIONHASHSIZE]; q; q = q->next)\r
-      if (q->id == id)\r
-        break;         \r
-    \r
-    if (!q) {\r
-      sendnoticetouser(bot->np, sender, "Can't find question %d.", id);\r
-      continue;\r
-    }\r
-    \r
-    switch (q->flags & QAQ_QSTATE) {\r
-    case QAQ_ANSWERED:\r
-      sendnoticetouser(bot->np, sender, "Question %d has already been answered.", id);\r
-      continue;\r
-    \r
-    case QAQ_OFFTOPIC:\r
-      sendnoticetouser(bot->np, sender, "Question %d has already been marked as off-topic.", id);\r
-      continue;\r
-    \r
-    case QAQ_SPAM:\r
-      sendnoticetouser(bot->np, sender, "Question %d has already been marked as spam.", id);\r
-      continue;\r
-    \r
-    default:\r
-      break;\r
-    }\r
-    \r
-    q->flags = ((q->flags) & ~QAQ_QSTATE) | QAQ_OFFTOPIC;\r
-    sendnoticetouser(bot->np, sender, "Question %d has been marked as off-topic.", id);\r
-  }\r
-  \r
-  return CMD_OK;\r
-}\r
-\r
-int qabot_dochanopenchan(void* np, int cargc, char** cargv) {\r
-  nick* sender = (nick*)np;\r
-  qab_bot* bot = qabot_getcurrentbot();\r
-  channel* cp = qabot_getcurrentchannel();\r
-  modechanges changes;\r
-  \r
-  localsetmodeinit(&changes, bot->public_chan->channel, bot->np);\r
-  localdosetmode_simple(&changes, CHANMODE_MODERATE|CHANMODE_DELJOINS, CHANMODE_INVITEONLY);\r
-  localsetmodeflush(&changes, 1);\r
-  sendnoticetouser(bot->np, sender, "Public channel has been opened.");\r
-  \r
-  return CMD_OK;\r
-}\r
-\r
-int qabot_dochanping(void* np, int cargc, char** cargv) {\r
-  nick* sender = (nick*)np;\r
-  qab_bot* bot = qabot_getcurrentbot();\r
-  channel* cp = qabot_getcurrentchannel();\r
-  \r
-  sendmessagetochannel(bot->np, cp, "pong!");\r
-  \r
-  return CMD_OK;\r
-}\r
-\r
-int qabot_dochanreset(void* np, int cargc, char** cargv) {\r
-  nick* sender = (nick*)np;\r
-  qab_bot* bot = qabot_getcurrentbot();\r
-  channel* cp = qabot_getcurrentchannel();\r
-  int r = 0;\r
-  \r
-  if (cargc < 1) {\r
-    sendnoticetouser(bot->np, sender, "Syntax: !reset <blocks|questions|stats|all>");\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  if (!ircd_strcmp(cargv[0], "blocks"))\r
-    r = 1;\r
-  else if (!ircd_strcmp(cargv[0], "questions"))\r
-    r = 2;\r
-  else if (!ircd_strcmp(cargv[0], "stats"))\r
-    r = 4;\r
-  else if (!ircd_strcmp(cargv[0], "all"))\r
-    r = 3;\r
-  else {\r
-    sendnoticetouser(bot->np, sender, "Unknown parameter: %s.", cargv[0]);\r
-    return;\r
-  }\r
-  \r
-  if (r & 1) {\r
-    qab_block* b;\r
-   \r
-    while (bot->blocks) {\r
-      b = bot->blocks;\r
-      bot->blocks = bot->blocks->next;\r
-      if (b->blockstr)\r
-        free(b->blockstr);\r
-      free(b);\r
-    }\r
-    \r
-    bot->block_count = 0;\r
-    \r
-    sendnoticetouser(bot->np, sender, "Reset (blocks): Done.");\r
-  }\r
-  \r
-  if (r & 2) {\r
-    qab_question* q;\r
-    int i;\r
-    \r
-    for (i = 0; i < QUESTIONHASHSIZE; i++) {\r
-      while (bot->questions[i]) {\r
-        q = bot->questions[i];\r
-        bot->questions[i] = bot->questions[i]->next;\r
-        if (q->question)\r
-          free(q->question);\r
-        if (q->answer)\r
-          free(q->answer);\r
-        free(q);\r
-      }\r
-    }\r
-    \r
-    bot->lastquestionID = 0;\r
-    bot->answered = 0;\r
-    \r
-    sendnoticetouser(bot->np, sender, "Reset (questions): Done.");\r
-  }\r
-  \r
-  if (r & 4) {\r
-    bot->answered = 0;\r
-    bot->spammed = 0;\r
-    sendnoticetouser(bot->np, sender, "Reset (stats): Done.");\r
-  }\r
-  \r
-  return CMD_OK;\r
-}\r
-\r
-int qabot_dochanspam(void* np, int cargc, char** cargv) {\r
-  nick* sender = (nick*)np;\r
-  qab_bot* bot = qabot_getcurrentbot();\r
-  channel* cp = qabot_getcurrentchannel();\r
-  int id;\r
-  int i;\r
-  qab_question* q;\r
-  \r
-  if (cargc < 1) {\r
-    sendnoticetouser(bot->np, sender, "Syntax: !spam <id> [<id> ... <id>]");\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  for (i = 0; i < cargc; i++) {\r
-    id = strtol(cargv[i], NULL, 10);\r
-    \r
-    if ((id < 1) || (id > bot->lastquestionID)) {\r
-      sendnoticetouser(bot->np, sender, "Invalid question ID %d.", id);\r
-      continue;\r
-    }\r
-    \r
-    for (q = bot->questions[id % QUESTIONHASHSIZE]; q; q = q->next)\r
-      if (q->id == id)\r
-        break;         \r
-    \r
-    if (!q) {\r
-      sendnoticetouser(bot->np, sender, "Can't find question %d.", id);\r
-      continue;\r
-    }\r
-    \r
-    switch (q->flags & QAQ_QSTATE) {\r
-    case QAQ_ANSWERED:\r
-      sendnoticetouser(bot->np, sender, "Question %d has already been answered.", id);\r
-      continue;\r
-    \r
-    case QAQ_OFFTOPIC:\r
-      sendnoticetouser(bot->np, sender, "Question %d has already been marked as off-topic.", id);\r
-      continue;\r
-    \r
-    case QAQ_SPAM:\r
-      sendnoticetouser(bot->np, sender, "Question %d has already been marked as spam.", id);\r
-      continue;\r
-    \r
-    default:\r
-      break;\r
-    }\r
-    \r
-    q->flags = ((q->flags) & ~QAQ_QSTATE) | QAQ_SPAM;\r
-    sendnoticetouser(bot->np, sender, "Question %d has been marked as spam.", id);\r
-  }\r
-  \r
-  return CMD_OK;\r
-}\r
-\r
-int qabot_dochanstatus(void* np, int cargc, char** cargv) {\r
-  nick* sender = (nick*)np;\r
-  qab_bot* bot = qabot_getcurrentbot();\r
-  channel* cp = qabot_getcurrentchannel();\r
-  \r
-  sendnoticetouser(bot->np, sender, "Lines spammed:            %d", bot->spammed);\r
-  sendnoticetouser(bot->np, sender, "Questions asked:          %d", bot->lastquestionID);\r
-  sendnoticetouser(bot->np, sender, "Questions answered:       %d", bot->answered);\r
-  sendnoticetouser(bot->np, sender, "Blocks:                   %d", bot->block_count);\r
-  /*sendnoticetouser(bot->np, sender, "Question interval:        %d seconds", bot->question_interval);*/\r
-  sendnoticetouser(bot->np, sender, "Spam interval:            %d seconds", bot->spam_interval);\r
-  sendnoticetouser(bot->np, sender, "Nick block interval:      %d seconds", bot->ask_wait);\r
-  sendnoticetouser(bot->np, sender, "Queued question interval: %d seconds", bot->queued_question_interval);\r
-  sendnoticetouser(bot->np, sender, "Block control chars:      %s", (bot->flags & QAB_CONTROLCHAR) ? "Yes" : "No");\r
-  sendnoticetouser(bot->np, sender, "Block colour:             %s", (bot->flags & QAB_COLOUR) ? "Yes" : "No");\r
-  sendnoticetouser(bot->np, sender, "Authed users only:        %s", (bot->flags & QAB_AUTHEDONLY) ? "Yes" : "No");\r
-  sendnoticetouser(bot->np, sender, "Line break:               %s", (bot->flags & QAB_LINEBREAK) ? "Yes" : "No");\r
-  sendnoticetouser(bot->np, sender, "Question flood detection: %s", (bot->flags & QAB_FLOODDETECT) ? "Yes" : "No");\r
-  sendnoticetouser(bot->np, sender, "Question flood blocking:  %s", (bot->flags & QAB_FLOODSTOP) ? "Yes" : "No");\r
-  sendnoticetouser(bot->np, sender, "Blocks mark as spam:      %s", (bot->flags & QAB_BLOCKMARK) ? "Yes" : "No");\r
-  if (bot->micnumeric) {\r
-    nick* mnick = getnickbynumeric(bot->micnumeric);\r
-    \r
-    sendnoticetouser(bot->np, np, "Mic:                      Enabled (%s)", mnick ? mnick->nick : "UNKNOWN");\r
-  }\r
-  else\r
-    sendnoticetouser(bot->np, np, "Mic:                      Disabled");\r
-  sendnoticetouser(bot->np, sender, "Mic timeout:              %d", bot->mic_timeout);\r
-  /*sendnoticetouser(bot->np, sender, "");*/\r
-  \r
-  return CMD_OK;\r
-}\r
-\r
-int qabot_dochanunblock(void* np, int cargc, char** cargv) {\r
-  nick* sender = (nick*)np;\r
-  qab_bot* bot = qabot_getcurrentbot();\r
-  channel* cp = qabot_getcurrentchannel();\r
-  char* ch;\r
-  qab_block* b;\r
-  char type = -1;\r
-  \r
-  if (cargc < 1) {\r
-    sendnoticetouser(bot->np, sender, "Syntax: !unblock [-q|-t] <account|mask>");\r
-    return CMD_ERROR;\r
-  }\r
-  \r
-  if (cargc > 1) {\r
-    if (!ircd_strncmp(cargv[0], "-q", 2)) {\r
-      type = QABBLOCK_ACCOUNT;\r
-      ch = cargv[1];\r
-      \r
-      if (*ch == '#')\r
-        ch++;\r
-    }\r
-    else if (!ircd_strncmp(cargv[0], "-t", 2)) {\r
-      type = QABBLOCK_TEXT;\r
-      ch = cargv[1];\r
-    }\r
-    else {\r
-      sendnoticetouser(bot->np, sender, "Invalid flag.");\r
-      return CMD_ERROR;\r
-    }\r
-  }\r
-  else {\r
-    type = QABBLOCK_HOST;\r
-    ch = cargv[0];\r
-  }\r
-  \r
-  for (b = bot->blocks; b; b = b->next) {\r
-    if (b->type != type)\r
-      continue;\r
-    \r
-    if (!ircd_strcmp(b->blockstr, ch)) {\r
-      if (b->next)\r
-        b->next->prev = b->prev;\r
-      if (b->prev)\r
-        b->prev->next = b->next;\r
-      else\r
-        bot->blocks = b->next;\r
-      \r
-      free(b->blockstr);\r
-      free(b);\r
-      \r
-      bot->block_count--;\r
-      \r
-      sendnoticetouser(bot->np, sender, "Block removed.");\r
-      return CMD_OK;\r
-    }\r
-  }\r
-  \r
-  sendnoticetouser(bot->np, sender, "No such block.");\r
-  \r
-  return CMD_ERROR;\r
-}\r
+#include <stdio.h>
+#include <time.h>
+#include <dirent.h>
+
+#include "../nick/nick.h"
+#include "../localuser/localuserchannel.h"
+#include "../core/hooks.h"
+#include "../core/schedule.h"
+#include "../lib/array.h"
+#include "../lib/base64.h"
+#include "../lib/irc_string.h"
+#include "../lib/splitline.h"
+
+#include "qabot.h"
+
+int qabot_dochananswer(void* np, int cargc, char** cargv) {
+  nick* sender = (nick*)np;
+  qab_bot* bot = qabot_getcurrentbot();
+  int id;
+  char* ch;
+  qab_question* q;
+  
+  if (cargc < 2) {
+    sendnoticetouser(bot->np, sender, "Syntax: !answer <id> <answer>");
+    return CMD_ERROR;
+  }
+  
+  id = strtol(cargv[0], NULL, 10);
+  ch = cargv[1];
+  
+  if ((id < 1) || (id > bot->lastquestionID)) {
+    sendnoticetouser(bot->np, sender, "Invalid question ID %d.", id);
+    return CMD_ERROR;
+  }
+  
+  for (q = bot->questions[id % QUESTIONHASHSIZE]; q; q = q->next)
+    if (q->id == id)
+      break;
+  
+  if (!q) {
+    sendnoticetouser(bot->np, sender, "Can't find question %d.", id);
+    return CMD_ERROR;
+  }
+  
+  switch (q->flags & QAQ_QSTATE) {
+  case QAQ_ANSWERED:
+    sendnoticetouser(bot->np, sender, "Question %d has already been answered.", id);
+    return CMD_ERROR;
+    
+  case QAQ_OFFTOPIC:
+    sendnoticetouser(bot->np, sender, "Question %d has been marked as off-topic.", id);
+    return CMD_ERROR;
+  
+  case QAQ_SPAM:
+    sendnoticetouser(bot->np, sender, "Question %d has been marked as spam.", id);
+    return CMD_ERROR;
+  
+  default:
+    break;
+  }
+  
+  q->flags = ((q->flags) & ~QAQ_QSTATE) | QAQ_ANSWERED;
+  q->answer = strdup(ch);
+  
+  bot->answered++;
+  
+  if (!bot->nextspam && !bot->micnumeric) {
+    sendmessagetochannel(bot->np, bot->public_chan->channel, "%s asked: %s", q->nick, q->question);
+    sendmessagetochannel(bot->np, bot->public_chan->channel, "%s answers: %s", sender->nick, ch);
+  }
+  else {
+    qab_answer* a;
+    
+    a = (qab_answer*)malloc(sizeof(qab_answer));
+    a->question = q;
+    strncpy(a->nick, sender->nick, NICKLEN);
+    a->nick[NICKLEN] = '\0';
+    a->next = bot->answers;
+    bot->answers = a;
+    
+    sendnoticetouser(bot->np, sender, "Can't send your answer right now. Answer was stored and will be sent later on.");
+    return CMD_OK;
+  }
+  
+  sendnoticetouser(bot->np, sender, "Answer to question %d has been sent and stored.", id);
+  
+  return CMD_OK;
+}
+
+int qabot_dochanblock(void* np, int cargc, char** cargv) {
+  nick* sender = (nick*)np;
+  qab_bot* bot = qabot_getcurrentbot();
+  qab_block* b;
+  
+  if (cargc < 1) {
+    sendnoticetouser(bot->np, sender, "Syntax: !block [-q|-t] <account|mask>");
+    return CMD_ERROR;
+  }
+  
+  if (cargc > 1) {
+    if (!ircd_strncmp(cargv[0], "-q", 2)) {
+      /* account block */
+      char* target = cargv[1];
+      
+      if (*target == '#') {
+        target++;
+        
+        if (strchr(target, '*') || strchr(target, '?')) {
+          sendnoticetouser(bot->np, sender, "Wildcard account blocks are not supported.");
+          return CMD_ERROR;
+        }
+      }
+      else {
+        nick* tnick;
+        
+        if (!(tnick = getnickbynick(target))) {
+          sendnoticetouser(bot->np, sender, "Couldn't find user %s.", target);
+          return CMD_ERROR;
+        }
+        
+        if (!IsAccount(tnick)) {
+           sendnoticetouser(bot->np, sender, "%s is not authed.", tnick->nick);
+           return CMD_ERROR;
+        }
+        
+        target = tnick->authname;
+      }
+      
+      b = (qab_block*)malloc(sizeof(qab_block));
+      b->type = QABBLOCK_ACCOUNT;
+      b->created = time(0);
+      strncpy(b->creator, IsAccount(sender) ? sender->authname : "UNKNOWN", ACCOUNTLEN);
+      b->creator[ACCOUNTLEN] = '\0';
+      b->blockstr = strdup(target);
+      b->prev = 0;
+      b->next = bot->blocks;
+      if (bot->blocks)
+        bot->blocks->prev = b;
+      bot->blocks = b;
+      bot->block_count++;
+      
+      sendnoticetouser(bot->np, sender, "Now blocking all messages from users with accountname %s.", target);
+      
+      if (bot->flags & QAB_BLOCKMARK) {
+        qab_question* q;
+        int i, spamqcount = 0;
+        nick* qnick;
+        
+        for (i = 0; i < QUESTIONHASHSIZE; i++) {
+          for (q = bot->questions[i]; q; q = q->next) {
+            if ((q->flags & QAQ_QSTATE) != QAQ_NEW)
+              continue;
+            
+            if (!(qnick = getnickbynumeric(q->numeric)))
+              continue;
+            
+            if (!IsAccount(qnick))
+              continue;
+            
+            if (ircd_strcmp(qnick->authname, b->blockstr))
+              continue;
+            
+            q->flags = ((q->flags) & ~QAQ_QSTATE) | QAQ_SPAM;
+            spamqcount++;
+          }
+        }
+        
+        sendnoticetouser(bot->np, sender, "Block caused %d message%s to be marked as spam.", spamqcount, (spamqcount == 1) ? "" : "s");
+      }
+    }
+    else if (!ircd_strncmp(cargv[0], "-t", 2)) {
+      /* text block */
+      char* mask = cargv[1];
+      
+      b = (qab_block*)malloc(sizeof(qab_block));
+      b->type = QABBLOCK_TEXT;
+      b->created = time(0);
+      strncpy(b->creator, sender->authname, ACCOUNTLEN);
+      b->creator[ACCOUNTLEN] = '\0';
+      b->blockstr = strdup(mask);
+      b->prev = 0;
+      b->next = bot->blocks;
+      if (bot->blocks)
+        bot->blocks->prev = b;
+      bot->blocks = b;
+      bot->block_count++;
+      
+      sendnoticetouser(bot->np, sender, "Now blocking all questions which match %s.", mask);
+      
+      if (bot->flags & QAB_BLOCKMARK) {
+        qab_question* q;
+        int i, spamqcount = 0;
+        
+        for (i = 0; i < QUESTIONHASHSIZE; i++) {
+          for (q = bot->questions[i]; q; q = q->next) {
+            if ((q->flags & QAQ_QSTATE) != QAQ_NEW)
+              continue;
+            
+            if (match(b->blockstr, q->question))
+              continue;
+            
+            q->flags = ((q->flags) & ~QAQ_QSTATE) | QAQ_SPAM;
+            spamqcount++;
+          }
+        }
+        
+        sendnoticetouser(bot->np, sender, "Block caused %d message%s to be marked as spam.", spamqcount, (spamqcount == 1) ? "" : "s");
+      }
+    }
+    else {
+      sendnoticetouser(bot->np, sender, "Invalid flag.");
+      return CMD_ERROR;
+    }
+  }
+  else {
+    /* hostmask block */
+    char* mask = cargv[0];
+    
+    if (!strchr(mask, '@') || !strchr(mask, '!')) {
+      sendnoticetouser(bot->np, sender, "%s is not a valid hostmask.", mask);
+      return CMD_ERROR;
+    }
+    
+    b = (qab_block*)malloc(sizeof(qab_block));
+    b->type = QABBLOCK_HOST;
+    b->created = time(0);
+    strncpy(b->creator, sender->authname, ACCOUNTLEN);
+    b->creator[ACCOUNTLEN] = '\0';
+    b->blockstr = strdup(mask);
+    b->prev = 0;
+    b->next = bot->blocks;
+    if (bot->blocks)
+      bot->blocks->prev = b;
+    bot->blocks = b;
+    bot->block_count++;
+    
+    sendnoticetouser(bot->np, sender, "Now blocking all messages from users with a hostmask matching %s.", mask);
+    
+    if (bot->flags & QAB_BLOCKMARK) {
+        qab_question* q;
+        int i, spamqcount = 0;
+        nick* qnick;
+        char hostbuf[NICKLEN + USERLEN + HOSTLEN + 3];
+        
+        for (i = 0; i < QUESTIONHASHSIZE; i++) {
+          for (q = bot->questions[i]; q; q = q->next) {
+            if ((q->flags & QAQ_QSTATE) != QAQ_NEW)
+              continue;
+            
+            if (!(qnick = getnickbynumeric(q->numeric)))
+              continue;
+            
+            sprintf(hostbuf,"%s!%s@%s", qnick->nick, qnick->ident, qnick->host->name->content);
+            
+            if (match(b->blockstr, hostbuf))
+              continue;
+            
+            q->flags = ((q->flags) & ~QAQ_QSTATE) | QAQ_SPAM;
+            spamqcount++;
+          }
+        }
+        
+        sendnoticetouser(bot->np, sender, "Block caused %d message%s to be marked as spam.", spamqcount, (spamqcount == 1) ? "" : "s");
+      }
+  }
+  
+  return CMD_OK;
+}
+
+int qabot_dochanclear(void* np, int cargc, char** cargv) {
+  qab_bot* bot = qabot_getcurrentbot();
+  channel* cp = qabot_getcurrentchannel();
+  qab_spam* s;
+  qab_spam* ns;
+  
+  for (s = bot->nextspam; s; s = ns) {
+    ns = s->next;
+    free(s->message);
+    free(s);
+  }
+  
+  bot->nextspam = bot->lastspam = 0;
+  
+  sendmessagetochannel(bot->np, cp, "Cleared message buffer.");
+  if (bot->micnumeric) {
+    bot->micnumeric = 0;
+    sendmessagetochannel(bot->np, cp, "Mic deactivated.");
+  }
+  if (bot->recnumeric) {
+    bot->recnumeric = 0;
+    sendmessagetochannel(bot->np, cp, "Recorder deactivated.");
+  }
+  if (bot->recfile) {
+    fclose(bot->recfile);
+    bot->recfile = NULL;
+  }
+  if (bot->playfile) {
+    fclose(bot->playfile);
+    bot->playfile = NULL;
+  }
+  
+  return CMD_OK;
+}
+
+int qabot_dochanclosechan(void* np, int cargc, char** cargv) {
+  nick* sender = (nick*)np;
+  qab_bot* bot = qabot_getcurrentbot();
+  modechanges changes;
+  
+  localsetmodeinit(&changes, bot->public_chan->channel, bot->np);
+  localdosetmode_simple(&changes, CHANMODE_INVITEONLY, 0);
+  localsetmodeflush(&changes, 1);
+  sendnoticetouser(bot->np, sender, "Public channel has been closed.");
+  
+  return CMD_OK;
+}
+
+int qabot_dochanconfig(void* np, int cargc, char** cargv) {
+  nick* sender = (nick*)np;
+  qab_bot* bot = qabot_getcurrentbot();
+  char* opt;
+  char* value;
+  
+  if (cargc < 1) {
+    sendnoticetouser(bot->np, sender, "Syntax: !config <option> [<value>]");
+    sendnoticetouser(bot->np, sender, "Displays or sets configuration option. Valid options are:");
+    sendnoticetouser(bot->np, sender, "blockcontrol - Block questions containing control chars.");
+    sendnoticetouser(bot->np, sender, "blockcolour  - Block questions containing colour.");
+    sendnoticetouser(bot->np, sender, "blockmark    - Mark questions affected by blocks as spam.");
+    sendnoticetouser(bot->np, sender, "authedonly   - Accept questions from authed users only.");
+    sendnoticetouser(bot->np, sender, "linebreak    - Separate questions with line breaks.");
+    sendnoticetouser(bot->np, sender, "flooddetect  - Attempt to detect floodclone spam.");
+    sendnoticetouser(bot->np, sender, "floodblock   - Automatically block floodclone spam.");
+    sendnoticetouser(bot->np, sender, "spamint      - Text spam interval.");
+    sendnoticetouser(bot->np, sender, "nickblockint - Time to wait before sending another question.");
+    sendnoticetouser(bot->np, sender, "queuedqint   - Queued answer spam interval.");
+    sendnoticetouser(bot->np, sender, "mictimeout   - Idle time before mic is automatically disabled.");
+    return CMD_ERROR;
+  }
+  
+  opt = cargv[0];
+  if (cargc == 2)
+    value = cargv[1];
+  else
+    value = 0;
+  
+  if (!ircd_strcmp(opt, "blockcontrol")) {
+    if (value) {
+      if (!ircd_strcmp(value, "on")) {
+        bot->flags |= QAB_CONTROLCHAR;
+        sendnoticetouser(bot->np, sender, "Questions containing control characters will now be blocked.");
+      }
+      else if (!ircd_strcmp(value, "off")) {
+        bot->flags &= (~QAB_CONTROLCHAR);
+        sendnoticetouser(bot->np, sender, "Questions containing control characters will no longer be blocked.");
+      }
+      else
+        sendnoticetouser(bot->np, sender, "Invalid option. Valid options are 'on' or 'off'.");
+    }
+    else
+      sendnoticetouser(bot->np, sender, "Control characters are currently %s blocked.", (bot->flags & QAB_CONTROLCHAR) ? "being" : "not being");
+  }
+  else if (!ircd_strcmp(opt, "blockcolour")) {
+    if (value) {
+      if (!ircd_strcmp(value, "on")) {
+        bot->flags |= QAB_COLOUR;
+        sendnoticetouser(bot->np, sender, "Questions containing colour will now be blocked.");
+      }
+      else if (!ircd_strcmp(value, "off")) {
+        bot->flags &= (~QAB_COLOUR);
+        sendnoticetouser(bot->np, sender, "Questions containing colour will no longer be blocked.");
+      }
+      else
+        sendnoticetouser(bot->np, sender, "Invalid option. Valid options are 'on' or 'off'.");
+    }
+    else
+      sendnoticetouser(bot->np, sender, "Colours are currently %s blocked.", (bot->flags & QAB_COLOUR) ? "being" : "not being");
+  }
+  else if (!ircd_strcmp(opt, "blockmark")) {
+    if (value) {
+      if (!ircd_strcmp(value, "on")) {
+        bot->flags |= QAB_BLOCKMARK;
+        sendnoticetouser(bot->np, sender, "New blocks will automatically mark affected questions as spam.");
+      }
+      else if (!ircd_strcmp(value, "off")) {
+        bot->flags &= (~QAB_BLOCKMARK);
+        sendnoticetouser(bot->np, sender, "New blocks will no longer automatically mark affected questions as spam.");
+      }
+      else
+        sendnoticetouser(bot->np, sender, "Invalid option. Valid options are 'on' or 'off'.");
+    }
+    else
+      sendnoticetouser(bot->np, sender, "Blocks are currently %smarking affected questions as spam.", (bot->flags & QAB_BLOCKMARK) ? "" : "not ");
+  }
+  else if (!ircd_strcmp(opt, "authedonly")) {
+    if (value) {
+      if (!ircd_strcmp(value, "on")) {
+        bot->flags |= QAB_AUTHEDONLY;
+        sendnoticetouser(bot->np, sender, "Questions from unauthed users will now be blocked.");
+      }
+      else if (!ircd_strcmp(value, "off")) {
+        bot->flags &= (~QAB_AUTHEDONLY);
+        sendnoticetouser(bot->np, sender, "Questions from unauthed users will no longer be blocked.");
+      }
+      else
+        sendnoticetouser(bot->np, sender, "Invalid option. Valid options are 'on' or 'off'.");
+    }
+    else
+      sendnoticetouser(bot->np, sender, "Unauthed users may currently %ssend questions.", (bot->flags & QAB_AUTHEDONLY) ? "NOT " : "");
+  }
+  else if (!ircd_strcmp(opt, "linebreak")) {
+    if (value) {
+      if (!ircd_strcmp(value, "on")) {
+        bot->flags |= QAB_LINEBREAK;
+        sendnoticetouser(bot->np, sender, "Line breaks are now enabled.");
+      }
+      else if (!ircd_strcmp(value, "off")) {
+        bot->flags &= (~QAB_LINEBREAK);
+        sendnoticetouser(bot->np, sender, "Line breaks are now disabled.");
+      }
+      else
+        sendnoticetouser(bot->np, sender, "Invalid option. Valid options are 'on' or 'off'.");
+    }
+    else
+      sendnoticetouser(bot->np, sender, "Line breaks are currently %s.", (bot->flags & QAB_LINEBREAK) ? "enabled" : "disabled");
+  }
+  else if (!ircd_strcmp(opt, "flooddetect")) {
+    if (value) {
+      if (!ircd_strcmp(value, "on")) {
+        bot->flags |= QAB_FLOODDETECT;
+        sendnoticetouser(bot->np, sender, "Flood detection is now enabled.");
+      }
+      else if (!ircd_strcmp(value, "off")) {
+        bot->flags &= (~QAB_FLOODDETECT);
+        sendnoticetouser(bot->np, sender, "Flood detection is now disabled.");
+      }
+      else
+        sendnoticetouser(bot->np, sender, "Invalid option. Valid options are 'on' or 'off'.");
+    }
+    else
+      sendnoticetouser(bot->np, sender, "Flood detection is currently %s.", (bot->flags & QAB_FLOODDETECT) ? "enabled" : "disabled");
+  }
+  else if (!ircd_strcmp(opt, "floodblock")) {
+    if (value) {
+      if (!ircd_strcmp(value, "on")) {
+        bot->flags |= QAB_FLOODSTOP;
+        sendnoticetouser(bot->np, sender, "Flood blocking is now enabled.");
+      }
+      else if (!ircd_strcmp(value, "off")) {
+        bot->flags &= (~QAB_FLOODSTOP);
+        sendnoticetouser(bot->np, sender, "Flood blocking is now disabled.");
+      }
+      else
+        sendnoticetouser(bot->np, sender, "Invalid option. Valid options are 'on' or 'off'.");
+    }
+    else
+      sendnoticetouser(bot->np, sender, "Flood blocking is currently %s.", (bot->flags & QAB_FLOODSTOP) ? "enabled" : "disabled");
+  }
+  else if (!ircd_strcmp(opt, "mictimeout")) {
+    if (value) {
+      int v = (int)strtol(value, NULL, 10);
+      
+      if ((v < 0) || (v > 300)) {
+        sendnoticetouser(bot->np, sender, "Value must be between 0 (off) and 300.");
+        return CMD_ERROR;
+      }
+      
+      bot->mic_timeout = v;
+      sendnoticetouser(bot->np, sender, "Value set.");
+    }
+    else {
+      if (bot->mic_timeout)
+        sendnoticetouser(bot->np, sender, "Mic timeout is currently %d second%s.", bot->mic_timeout, (bot->mic_timeout == 1) ? "" : "s");
+      else
+        sendnoticetouser(bot->np, sender, "Mic timeout is currently disabled.");
+    }
+  }
+  else if (!ircd_strcmp(opt, "spamint")) {
+    if (value) {
+      int v = (int)strtol(value, NULL, 10);
+      
+      if ((v < 1) || (v > 30)) {
+        sendnoticetouser(bot->np, sender, "Value must be between 1 and 30.");
+        return CMD_ERROR;
+      }
+      
+      bot->spam_interval = v;
+      sendnoticetouser(bot->np, sender, "Value set.");
+    }
+    else
+      sendnoticetouser(bot->np, sender, "Spam interval is currently %d second%s.", bot->spam_interval, (bot->spam_interval == 1) ? "" : "s");
+  }
+  else if (!ircd_strcmp(opt, "nickblockint")) {
+    if (value) {
+      int v = (int)strtol(value, NULL, 10);
+      
+      if ((v < 1) || (v > 300)) {
+        sendnoticetouser(bot->np, sender, "Value must be between 1 and 300.");
+        return CMD_ERROR;
+      }
+      
+      bot->ask_wait = v;
+      sendnoticetouser(bot->np, sender, "Value set.");
+    }
+    else
+      sendnoticetouser(bot->np, sender, "Nick block interval is currently %d second%s.", bot->ask_wait, (bot->ask_wait == 1) ? "" : "s");
+  }
+  else if (!ircd_strcmp(opt, "queuedqint")) {
+    if (value) {
+      int v = (int)strtol(value, NULL, 10);
+      
+      if ((v < 1) || (v > 20)) {
+        sendnoticetouser(bot->np, sender, "Value must be between 1 and 20.");
+        return CMD_ERROR;
+      }
+      
+      bot->queued_question_interval = v;
+      sendnoticetouser(bot->np, sender, "Value set.");
+    }
+    else
+      sendnoticetouser(bot->np, sender, "Queued question interval is currently %d second%s.", bot->queued_question_interval, (bot->queued_question_interval == 1) ? "" : "s");
+  }
+  else
+    sendnoticetouser(bot->np, sender, "Invalid configuration option.");
+  
+  return CMD_OK;
+}
+
+int qabot_dochanhelp(void* np, int cargc, char** cargv) {
+  nick* sender = (nick*)np;
+  qab_bot* bot = qabot_getcurrentbot();
+  char* ch;
+  
+  if (cargc < 1)
+    ch = "";
+  else
+    ch = cargv[0];
+  
+  if (*ch) {
+    if (!ircd_strcmp(ch, "record")) {
+      sendnoticetouser(bot->np, sender, "Syntax: !record [filename]");
+      sendnoticetouser(bot->np, sender, "Turn the recorder on or off. When turned on, anything said is recorded.");
+      sendnoticetouser(bot->np, sender, "File name is required when starting the recorder.");
+    }
+    else if (!ircd_strcmp(ch, "play")) {
+      sendnoticetouser(bot->np, sender, "Syntax: !play <filename>");
+      sendnoticetouser(bot->np, sender, "Begin playback from the file specified.");
+    }
+    else if (!ircd_strcmp(ch, "continue")) {
+      sendnoticetouser(bot->np, sender, "Syntax: !continue");
+      sendnoticetouser(bot->np, sender, "Continue playback from a file.");
+    }
+    else if (!ircd_strcmp(ch, "stop")) {
+      sendnoticetouser(bot->np, sender, "Syntax: !stop");
+      sendnoticetouser(bot->np, sender, "Stop playback of a file before it reaches the end.");
+    }
+    else if (!ircd_strcmp(ch, "list")) {
+      sendnoticetouser(bot->np, sender, "Syntax: !list");
+      sendnoticetouser(bot->np, sender, "Lists recording files.");
+    }
+    else if (!ircd_strcmp(ch, "delete")) {
+      sendnoticetouser(bot->np, sender, "Syntax: !delete <filename>");
+      sendnoticetouser(bot->np, sender, "Deletes a recording file.");
+    }
+    else if (!ircd_strcmp(ch, "mic")) {
+      sendnoticetouser(bot->np, sender, "Syntax: !mic");
+      sendnoticetouser(bot->np, sender, "Turn the microphone on or off. When turned on, anything said by the microphone holder is relayed to %s.", bot->public_chan->name->content);
+    }
+    else if (!ircd_strcmp(ch, "clear")) {
+      sendnoticetouser(bot->np, sender, "Syntax: !clear");
+      sendnoticetouser(bot->np, sender, "Clear currently queued text to relay, and turn off the microphone.");
+    }
+    else if (!ircd_strcmp(ch, "ping")) {
+      sendnoticetouser(bot->np, sender, "Syntax: !ping");
+      sendnoticetouser(bot->np, sender, "Pings the bot.");
+    }
+    else if (!ircd_strcmp(ch, "config")) {
+      sendnoticetouser(bot->np, sender, "Syntax: !config <option|help> [<value>]");
+      sendnoticetouser(bot->np, sender, "Display or set bot configuration options.");
+    }
+    else if (!ircd_strcmp(ch, "answer")) {
+      sendnoticetouser(bot->np, sender, "Syntax: !answer <id> <answer>");
+      sendnoticetouser(bot->np, sender, "Answer a question.");
+    }
+    else if (!ircd_strcmp(ch, "block")) {
+      sendnoticetouser(bot->np, sender, "Syntax: !block [<-q|-t>] <mask>");
+      sendnoticetouser(bot->np, sender, "Add a block, where:");
+      sendnoticetouser(bot->np, sender, "-q: blocks a Q account.");
+      sendnoticetouser(bot->np, sender, "-t: blocks question text.");
+      sendnoticetouser(bot->np, sender, "No flag results in a hostmask block.");
+    }
+    else if (!ircd_strcmp(ch, "listblocks")) {
+      sendnoticetouser(bot->np, sender, "Syntax: !listblocks");
+      sendnoticetouser(bot->np, sender, "View the currently added blocks.");
+    }
+    else if (!ircd_strcmp(ch, "spam")) {
+      sendnoticetouser(bot->np, sender, "Syntax: !spam <id>");
+      sendnoticetouser(bot->np, sender, "Mark a question as spam. This stops it being answered.");
+    }
+    else if (!ircd_strcmp(ch, "offtopic")) {
+      sendnoticetouser(bot->np, sender, "Syntax: !offtopic <id>");
+      sendnoticetouser(bot->np, sender, "Mark a question as off-topic. This stops it being answered.");
+    }
+    else if (!ircd_strcmp(ch, "unblock")) {
+      sendnoticetouser(bot->np, sender, "Syntax: !unblock [<-q|-t>] <mask>");
+      sendnoticetouser(bot->np, sender, "Removes a block. See \"!help block\" for a description of the flags.");
+    }
+    else if (!ircd_strcmp(ch, "reset")) {
+      sendnoticetouser(bot->np, sender, "Syntax: !reset <all|questions|blocks|stats>");
+      sendnoticetouser(bot->np, sender, "Reset the questions, blocks or both; or the stats.");
+    }
+    else if (!ircd_strcmp(ch, "closechan")) {
+      sendnoticetouser(bot->np, sender, "Syntax: !closechan");
+      sendnoticetouser(bot->np, sender, "Closes the public channel.");
+    }
+    else if (!ircd_strcmp(ch, "openchan")) {
+      sendnoticetouser(bot->np, sender, "Syntax: !openchan");
+      sendnoticetouser(bot->np, sender, "Opens the public channel.");
+    }
+    else if (!ircd_strcmp(ch, "status")) {
+      sendnoticetouser(bot->np, sender, "Syntax: !status");
+      sendnoticetouser(bot->np, sender, "Displays some status information and statistics.");
+    }
+    else if (!ircd_strcmp(ch, "help")) {
+      sendnoticetouser(bot->np, sender, "Syntax !help [<command>]");
+      sendnoticetouser(bot->np, sender, "List available commands or view help for a particular command.");
+    }
+    else {
+      sendnoticetouser(bot->np, sender, "No help available for '%s'.", ch);
+    }
+  }
+  else {
+    sendnoticetouser(bot->np, sender, "The following channel commands are recognised:");
+    sendnoticetouser(bot->np, sender, "!answer     - Answer a question.");
+    sendnoticetouser(bot->np, sender, "!block      - Block a hostmask, account or string.");
+    sendnoticetouser(bot->np, sender, "!clear      - Clear currently queued text to spam.");
+    sendnoticetouser(bot->np, sender, "!closechan  - Close the public channel.");
+    sendnoticetouser(bot->np, sender, "!config     - Display or set bot configuration options.");
+    sendnoticetouser(bot->np, sender, "!help       - List commands or view the help for a command");
+    sendnoticetouser(bot->np, sender, "!listblocks - List currently added blocks.");
+    sendnoticetouser(bot->np, sender, "!mic        - Turn the microphone on or off.");
+    sendnoticetouser(bot->np, sender, "!offtopic   - Mark a question or questions as off-topic.");
+    sendnoticetouser(bot->np, sender, "!openchan   - Open the public channel.");
+    sendnoticetouser(bot->np, sender, "!ping       - Ping the bot.");
+    sendnoticetouser(bot->np, sender, "!reset      - Clear all blocks, questions or both.");
+    sendnoticetouser(bot->np, sender, "!spam       - Mark a question or questions as spam.");
+    sendnoticetouser(bot->np, sender, "!status     - Display some status statistics.");
+    sendnoticetouser(bot->np, sender, "!unblock    - Remove a block.");
+    sendnoticetouser(bot->np, sender, "!record     - Turn the recorder on or off.");
+    sendnoticetouser(bot->np, sender, "!play       - Start playback of a recording.");
+    sendnoticetouser(bot->np, sender, "!continue   - Continue playback of a recording.");
+    sendnoticetouser(bot->np, sender, "!stop       - Stop playback of a recording.");
+    sendnoticetouser(bot->np, sender, "!list       - Lists recordings.");
+    sendnoticetouser(bot->np, sender, "!delete     - Deletes a previous recording.");
+    sendnoticetouser(bot->np, sender, "End of list.");
+  }
+  
+  return CMD_OK;
+}
+
+int qabot_dochanlistblocks(void* np, int cargc, char** cargv) {
+  nick* sender = (nick*)np;
+  qab_bot* bot = qabot_getcurrentbot();
+  qab_block* b;
+  
+  if (!(b = bot->blocks)) {
+    sendnoticetouser(bot->np, sender, "There are no blocks currently added.");
+    return CMD_ERROR;
+  }
+  
+  sendnoticetouser(bot->np, sender, "Type: Hostmask/Account/Textmask:");
+  
+  for (; b; b = b->next) {
+    if (b->type == QABBLOCK_ACCOUNT)
+      sendnoticetouser(bot->np, sender, "A     %s", b->blockstr);
+    else if (b->type == QABBLOCK_HOST)
+      sendnoticetouser(bot->np, sender, "H     %s", b->blockstr);
+    else
+      sendnoticetouser(bot->np, sender, "T     %s", b->blockstr);
+  }
+  
+  sendnoticetouser(bot->np, sender, "End of list.");
+  
+  return CMD_OK;
+}
+
+int qabot_dochanmic(void* np, int cargc, char** cargv) {
+  nick* sender = (nick*)np;
+  qab_bot* bot = qabot_getcurrentbot();
+  channel* cp = qabot_getcurrentchannel();
+  
+  if (bot->micnumeric) {
+    if (bot->micnumeric == sender->numeric) {
+      bot->micnumeric = 0;
+      sendmessagetochannel(bot->np, cp, "Mic deactivated.");
+      if (!bot->lastspam)
+        qabot_spamstored((void*)bot);
+    }
+    else {
+      bot->lastmic = time(NULL);
+      bot->micnumeric = sender->numeric;
+      sendmessagetochannel(bot->np, cp, "%s now has the mic. Anything said by %s will be relayed in %s.", 
+        sender->nick, sender->nick, bot->public_chan->name->content);
+      deleteschedule(0, qabot_spamstored, (void*)bot);
+    }
+  }
+  else {
+    bot->lastmic = time(NULL);
+    bot->micnumeric = sender->numeric;
+    sendmessagetochannel(bot->np, cp, "Mic activated. Anything said by %s will be relayed in %s.", 
+      sender->nick, bot->public_chan->name->content);
+    deleteschedule(0, qabot_spamstored, (void*)bot);
+  }
+  
+  return CMD_OK;
+}
+
+int qabot_dochanrecord(void *np, int cargc, char** cargv) {
+  char buf[200];
+  unsigned int i, filenamelen;
+  nick* sender = (nick*)np;
+  qab_bot* bot = qabot_getcurrentbot();
+  channel* cp = qabot_getcurrentchannel();
+  
+  if (bot->recnumeric) {
+    if (bot->recnumeric == sender->numeric) {
+      bot->recnumeric = 0;
+      fclose(bot->recfile);
+      bot->recfile = NULL;
+      sendmessagetochannel(bot->np, cp, "Recorder deactivated.");
+    } else {
+      sendmessagetochannel(bot->np, cp, "Someone else is recording at the moment.");
+      return CMD_ERROR;
+    }
+  }
+  else {
+    if (bot->playfile) {
+      sendmessagetochannel(bot->np, cp, "You cannot record whilst a playback is in progress.");
+      return CMD_ERROR;
+    }
+    
+    if (cargc < 1) {
+      sendmessagetochannel(bot->np, cp, "You did not specify a file name.");
+      return CMD_ERROR;
+    }
+    
+    filenamelen = strlen(cargv[0]);
+    
+    if (filenamelen > 50) {
+      sendmessagetochannel(bot->np, cp, "File name too long.");
+      return CMD_ERROR;
+    }
+    
+    for (i = 0; i < filenamelen; i++) {
+      if (cargv[0][i] < '0' || (cargv[0][i] > '9' && cargv[0][i] < 'A') || (cargv[0][i] > 'Z' && cargv[0][i] < 'a') || cargv[0][i] > 'z') {
+        sendmessagetochannel(bot->np, cp, "Invalid characters in file name.");
+        return CMD_ERROR;
+      }
+    }
+    
+    snprintf(buf, 150, "./qabotrecords/%s_%s", bot->nick, cargv[0]);
+    bot->recfile = fopen(buf, "w");
+    
+    if (!(bot->recfile)) {
+      sendmessagetochannel(bot->np, cp, "Could not open record file.");
+      return CMD_ERROR;
+    }
+    
+    bot->recnumeric = sender->numeric;
+    
+    sendmessagetochannel(bot->np, cp, "Recorder activated. Anything said by %s will be recorded.", 
+      sender->nick);
+  }
+  return CMD_OK;
+}
+
+int qabot_dochanplay(void *np, int cargc, char** cargv) {
+  char buf[200];
+  unsigned int i, filenamelen;
+  qab_bot* bot = qabot_getcurrentbot();
+  channel* cp = qabot_getcurrentchannel();
+  
+  if (bot->playfile) {
+    sendmessagetochannel(bot->np, cp, "A playback is already in progress, use !stop to abort current playback.");
+    return CMD_ERROR;
+  }
+  
+  if (bot->recfile) {
+    sendmessagetochannel(bot->np, cp, "You cannot playback whilst recording is in progress.");
+    return CMD_ERROR;
+  }
+  
+  if (cargc < 1) {
+    sendmessagetochannel(bot->np, cp, "You did not specify a file name.");
+    return CMD_ERROR;
+  }
+  
+  filenamelen = strlen(cargv[0]);
+  
+  if (filenamelen > 50) {
+    sendmessagetochannel(bot->np, cp, "File name too long.");
+    return CMD_ERROR;
+  }
+  
+  for (i = 0; i < filenamelen; i++) {
+    if (cargv[0][i] < '0' || (cargv[0][i] > '9' && cargv[0][i] < 'A') || (cargv[0][i] > 'Z' && cargv[0][i] < 'a') || cargv[0][i] > 'z') {
+      sendmessagetochannel(bot->np, cp, "Invalid characters in file name.");
+      return CMD_ERROR;
+    }
+  }
+  
+  snprintf(buf, 150, "./qabotrecords/%s_%s", bot->nick, cargv[0]);
+  bot->playfile = fopen(buf, "r");
+  
+  if (!(bot->playfile)) {
+    sendmessagetochannel(bot->np, cp, "Could not open playback file.");
+    return CMD_ERROR;
+  }
+  
+  sendmessagetochannel(bot->np, cp, "Starting playback...");
+  qabot_playback(bot);
+  return CMD_OK;
+}
+
+int qabot_dochancontinue(void *np, int cargc, char** cargv) {
+  qab_bot* bot = qabot_getcurrentbot();
+  channel* cp = qabot_getcurrentchannel();
+  
+  if (!(bot->playfile)) {
+    sendmessagetochannel(bot->np, cp, "No playback in progress.");
+    return CMD_ERROR;
+  }
+  
+  sendmessagetochannel(bot->np, cp, "Continuing playback...");
+  qabot_playback(bot);
+  return CMD_OK;
+}
+
+int qabot_dochanstop(void *np, int cargc, char** cargv) {
+  qab_bot* bot = qabot_getcurrentbot();
+  channel* cp = qabot_getcurrentchannel();
+  
+  if (!(bot->playfile)) {
+    sendmessagetochannel(bot->np, cp, "No playback in progress.");
+    return CMD_ERROR;
+  }
+  
+  fclose(bot->playfile);
+  bot->playfile = NULL;
+  sendmessagetochannel(bot->np, cp, "Stopped playback.");
+  return CMD_OK;
+}
+
+int qabot_dochanlist(void *np, int cargc, char** cargv) {
+  DIR *recordlist;
+  struct dirent *direntry;
+  nick* sender = (nick*)np;
+  qab_bot* bot = qabot_getcurrentbot();
+  
+  recordlist = opendir("./qabotrecords");
+  
+  if (!recordlist) {
+    sendnoticetouser(bot->np, sender, "Unable to retreive directory list.");
+    return CMD_ERROR;
+  }
+  
+  sendnoticetouser(bot->np, sender, "Recording list:");
+  
+  for (direntry = readdir(recordlist); direntry; direntry = readdir(recordlist)) {
+    if (direntry->d_name[0] == '.')
+      continue;
+    
+    sendnoticetouser(bot->np, sender, "  %s", direntry->d_name);
+  }
+  
+  sendnoticetouser(bot->np, sender, "End of list.");
+  closedir(recordlist);
+  
+  return CMD_OK;
+}
+
+int qabot_dochandelete(void *np, int cargc, char** cargv) {
+  char buf[200];
+  unsigned int i, filenamelen;
+  qab_bot* bot = qabot_getcurrentbot();
+  channel* cp = qabot_getcurrentchannel();
+  
+  if (bot->playfile) {
+    sendmessagetochannel(bot->np, cp, "You cannot delete recordings whilst playback is in progress.");
+    return CMD_ERROR;
+  }
+  
+  if (bot->recfile) {
+    sendmessagetochannel(bot->np, cp, "You cannot delete recordings whilst recording is in progress.");
+    return CMD_ERROR;
+  }
+  
+  if (cargc < 1) {
+    sendmessagetochannel(bot->np, cp, "You did not specify a file name.");
+    return CMD_ERROR;
+  }
+  
+  filenamelen = strlen(cargv[0]);
+  
+  if (filenamelen > 50) {
+    sendmessagetochannel(bot->np, cp, "File name too long.");
+    return CMD_ERROR;
+  }
+  
+  for (i = 0; i < filenamelen; i++) {
+    if (cargv[0][i] < '0' || (cargv[0][i] > '9' && cargv[0][i] < 'A') || (cargv[0][i] > 'Z' && cargv[0][i] < 'a') || cargv[0][i] > 'z') {
+      sendmessagetochannel(bot->np, cp, "Invalid characters in file name.");
+      return CMD_ERROR;
+    }
+  }
+  
+  snprintf(buf, 150, "./qabotrecords/%s_%s", bot->nick, cargv[0]);
+  
+  if (!remove(buf)) {
+    sendmessagetochannel(bot->np, cp, "Recording deleted.");
+  } else {
+    sendmessagetochannel(bot->np, cp, "Unable to delete recording.");
+  }
+  
+  return CMD_OK;
+}
+
+int qabot_dochanmoo(void* np, int cargc, char** cargv) {
+  qab_bot* bot = qabot_getcurrentbot();
+  char moostr[50];
+  int i, moocount = 5 + (rand() % 40);
+  channel* cp = qabot_getcurrentchannel();
+  
+  moostr[0] = 'm';
+  for (i = 1; i < moocount; i++)
+    moostr[i] = ((rand() % 100) > 50) ? 'o': '0';
+  moostr[i] = '\0';
+  
+  sendmessagetochannel(bot->np, cp, "%s", moostr);
+  
+  return CMD_OK;
+}
+
+int qabot_dochanofftopic(void* np, int cargc, char** cargv) {
+  nick* sender = (nick*)np;
+  qab_bot* bot = qabot_getcurrentbot();
+  int id;
+  int i;
+  qab_question* q;
+  
+  if (cargc < 1) {
+    sendnoticetouser(bot->np, sender, "Syntax: !spam <id> [<id> ... <id>]");
+    return CMD_ERROR;
+  }
+  
+  for (i = 0; i < cargc; i++) {
+    id = strtol(cargv[i], NULL, 10);
+    
+    if ((id < 1) || (id > bot->lastquestionID)) {
+      sendnoticetouser(bot->np, sender, "Invalid question ID %d.", id);
+      continue;
+    }
+    
+    for (q = bot->questions[id % QUESTIONHASHSIZE]; q; q = q->next)
+      if (q->id == id)
+        break;         
+    
+    if (!q) {
+      sendnoticetouser(bot->np, sender, "Can't find question %d.", id);
+      continue;
+    }
+    
+    switch (q->flags & QAQ_QSTATE) {
+    case QAQ_ANSWERED:
+      sendnoticetouser(bot->np, sender, "Question %d has already been answered.", id);
+      continue;
+    
+    case QAQ_OFFTOPIC:
+      sendnoticetouser(bot->np, sender, "Question %d has already been marked as off-topic.", id);
+      continue;
+    
+    case QAQ_SPAM:
+      sendnoticetouser(bot->np, sender, "Question %d has already been marked as spam.", id);
+      continue;
+    
+    default:
+      break;
+    }
+    
+    q->flags = ((q->flags) & ~QAQ_QSTATE) | QAQ_OFFTOPIC;
+    sendnoticetouser(bot->np, sender, "Question %d has been marked as off-topic.", id);
+  }
+  
+  return CMD_OK;
+}
+
+int qabot_dochanopenchan(void* np, int cargc, char** cargv) {
+  nick* sender = (nick*)np;
+  qab_bot* bot = qabot_getcurrentbot();
+  modechanges changes;
+  
+  localsetmodeinit(&changes, bot->public_chan->channel, bot->np);
+  localdosetmode_simple(&changes, CHANMODE_MODERATE|CHANMODE_DELJOINS, CHANMODE_INVITEONLY);
+  localsetmodeflush(&changes, 1);
+  sendnoticetouser(bot->np, sender, "Public channel has been opened.");
+  
+  return CMD_OK;
+}
+
+int qabot_dochanping(void* np, int cargc, char** cargv) {
+  qab_bot* bot = qabot_getcurrentbot();
+  channel* cp = qabot_getcurrentchannel();
+  
+  sendmessagetochannel(bot->np, cp, "pong!");
+  
+  return CMD_OK;
+}
+
+int qabot_dochanreset(void* np, int cargc, char** cargv) {
+  nick* sender = (nick*)np;
+  qab_bot* bot = qabot_getcurrentbot();
+  int r = 0;
+  
+  if (cargc < 1) {
+    sendnoticetouser(bot->np, sender, "Syntax: !reset <blocks|questions|stats|all>");
+    return CMD_ERROR;
+  }
+  
+  if (!ircd_strcmp(cargv[0], "blocks"))
+    r = 1;
+  else if (!ircd_strcmp(cargv[0], "questions"))
+    r = 2;
+  else if (!ircd_strcmp(cargv[0], "stats"))
+    r = 4;
+  else if (!ircd_strcmp(cargv[0], "all"))
+    r = 3;
+  else {
+    sendnoticetouser(bot->np, sender, "Unknown parameter: %s.", cargv[0]);
+    return CMD_ERROR;
+  }
+  
+  if (r & 1) {
+    qab_block* b;
+   
+    while (bot->blocks) {
+      b = bot->blocks;
+      bot->blocks = bot->blocks->next;
+      if (b->blockstr)
+        free(b->blockstr);
+      free(b);
+    }
+    
+    bot->block_count = 0;
+    
+    sendnoticetouser(bot->np, sender, "Reset (blocks): Done.");
+  }
+  
+  if (r & 2) {
+    qab_question* q;
+    int i;
+    
+    for (i = 0; i < QUESTIONHASHSIZE; i++) {
+      while (bot->questions[i]) {
+        q = bot->questions[i];
+        bot->questions[i] = bot->questions[i]->next;
+        if (q->question)
+          free(q->question);
+        if (q->answer)
+          free(q->answer);
+        free(q);
+      }
+    }
+    
+    bot->lastquestionID = 0;
+    bot->answered = 0;
+    
+    sendnoticetouser(bot->np, sender, "Reset (questions): Done.");
+  }
+  
+  if (r & 4) {
+    bot->answered = 0;
+    bot->spammed = 0;
+    sendnoticetouser(bot->np, sender, "Reset (stats): Done.");
+  }
+  
+  return CMD_OK;
+}
+
+int qabot_dochanspam(void* np, int cargc, char** cargv) {
+  nick* sender = (nick*)np;
+  qab_bot* bot = qabot_getcurrentbot();
+  int id;
+  int i;
+  qab_question* q;
+  
+  if (cargc < 1) {
+    sendnoticetouser(bot->np, sender, "Syntax: !spam <id> [<id> ... <id>]");
+    return CMD_ERROR;
+  }
+  
+  for (i = 0; i < cargc; i++) {
+    id = strtol(cargv[i], NULL, 10);
+    
+    if ((id < 1) || (id > bot->lastquestionID)) {
+      sendnoticetouser(bot->np, sender, "Invalid question ID %d.", id);
+      continue;
+    }
+    
+    for (q = bot->questions[id % QUESTIONHASHSIZE]; q; q = q->next)
+      if (q->id == id)
+        break;         
+    
+    if (!q) {
+      sendnoticetouser(bot->np, sender, "Can't find question %d.", id);
+      continue;
+    }
+    
+    switch (q->flags & QAQ_QSTATE) {
+    case QAQ_ANSWERED:
+      sendnoticetouser(bot->np, sender, "Question %d has already been answered.", id);
+      continue;
+    
+    case QAQ_OFFTOPIC:
+      sendnoticetouser(bot->np, sender, "Question %d has already been marked as off-topic.", id);
+      continue;
+    
+    case QAQ_SPAM:
+      sendnoticetouser(bot->np, sender, "Question %d has already been marked as spam.", id);
+      continue;
+    
+    default:
+      break;
+    }
+    
+    q->flags = ((q->flags) & ~QAQ_QSTATE) | QAQ_SPAM;
+    sendnoticetouser(bot->np, sender, "Question %d has been marked as spam.", id);
+  }
+  
+  return CMD_OK;
+}
+
+int qabot_dochanstatus(void* np, int cargc, char** cargv) {
+  nick* sender = (nick*)np;
+  qab_bot* bot = qabot_getcurrentbot();
+  
+  sendnoticetouser(bot->np, sender, "Lines spammed:            %d", bot->spammed);
+  sendnoticetouser(bot->np, sender, "Questions asked:          %d", bot->lastquestionID);
+  sendnoticetouser(bot->np, sender, "Questions answered:       %d", bot->answered);
+  sendnoticetouser(bot->np, sender, "Blocks:                   %d", bot->block_count);
+  /*sendnoticetouser(bot->np, sender, "Question interval:        %d seconds", bot->question_interval);*/
+  sendnoticetouser(bot->np, sender, "Spam interval:            %d seconds", bot->spam_interval);
+  sendnoticetouser(bot->np, sender, "Nick block interval:      %d seconds", bot->ask_wait);
+  sendnoticetouser(bot->np, sender, "Queued question interval: %d seconds", bot->queued_question_interval);
+  sendnoticetouser(bot->np, sender, "Block control chars:      %s", (bot->flags & QAB_CONTROLCHAR) ? "Yes" : "No");
+  sendnoticetouser(bot->np, sender, "Block colour:             %s", (bot->flags & QAB_COLOUR) ? "Yes" : "No");
+  sendnoticetouser(bot->np, sender, "Authed users only:        %s", (bot->flags & QAB_AUTHEDONLY) ? "Yes" : "No");
+  sendnoticetouser(bot->np, sender, "Line break:               %s", (bot->flags & QAB_LINEBREAK) ? "Yes" : "No");
+  sendnoticetouser(bot->np, sender, "Question flood detection: %s", (bot->flags & QAB_FLOODDETECT) ? "Yes" : "No");
+  sendnoticetouser(bot->np, sender, "Question flood blocking:  %s", (bot->flags & QAB_FLOODSTOP) ? "Yes" : "No");
+  sendnoticetouser(bot->np, sender, "Blocks mark as spam:      %s", (bot->flags & QAB_BLOCKMARK) ? "Yes" : "No");
+  if (bot->micnumeric) {
+    nick* mnick = getnickbynumeric(bot->micnumeric);
+    
+    sendnoticetouser(bot->np, np, "Mic:                      Enabled (%s)", mnick ? mnick->nick : "UNKNOWN");
+  }
+  else
+    sendnoticetouser(bot->np, np, "Mic:                      Disabled");
+  sendnoticetouser(bot->np, sender, "Mic timeout:              %d", bot->mic_timeout);
+  /*sendnoticetouser(bot->np, sender, "");*/
+  
+  return CMD_OK;
+}
+
+int qabot_dochanunblock(void* np, int cargc, char** cargv) {
+  nick* sender = (nick*)np;
+  qab_bot* bot = qabot_getcurrentbot();
+  char* ch;
+  qab_block* b;
+  char type = -1;
+  
+  if (cargc < 1) {
+    sendnoticetouser(bot->np, sender, "Syntax: !unblock [-q|-t] <account|mask>");
+    return CMD_ERROR;
+  }
+  
+  if (cargc > 1) {
+    if (!ircd_strncmp(cargv[0], "-q", 2)) {
+      type = QABBLOCK_ACCOUNT;
+      ch = cargv[1];
+      
+      if (*ch == '#')
+        ch++;
+    }
+    else if (!ircd_strncmp(cargv[0], "-t", 2)) {
+      type = QABBLOCK_TEXT;
+      ch = cargv[1];
+    }
+    else {
+      sendnoticetouser(bot->np, sender, "Invalid flag.");
+      return CMD_ERROR;
+    }
+  }
+  else {
+    type = QABBLOCK_HOST;
+    ch = cargv[0];
+  }
+  
+  for (b = bot->blocks; b; b = b->next) {
+    if (b->type != type)
+      continue;
+    
+    if (!ircd_strcmp(b->blockstr, ch)) {
+      if (b->next)
+        b->next->prev = b->prev;
+      if (b->prev)
+        b->prev->next = b->next;
+      else
+        bot->blocks = b->next;
+      
+      free(b->blockstr);
+      free(b);
+      
+      bot->block_count--;
+      
+      sendnoticetouser(bot->np, sender, "Block removed.");
+      return CMD_OK;
+    }
+  }
+  
+  sendnoticetouser(bot->np, sender, "No such block.");
+  
+  return CMD_ERROR;
+}