]> jfr.im git - irc/evilnet/x3.git/blobdiff - src/mod-helpserv.c
Couple of srvx updates.
[irc/evilnet/x3.git] / src / mod-helpserv.c
index e608a93bc358897865890da48d50e851271d174f..5726a8ed7fa6af94aa97f9040b76b0f9576d86e5 100644 (file)
@@ -1,11 +1,11 @@
 /* mod-helpserv.c - Support Helper assistant service
  * Copyright 2002-2003 srvx Development Team
  *
- * This file is part of srvx.
+ * This file is part of x3.
  *
- * srvx is free software; you can redistribute it and/or modify
+ * x3 is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
+ * the Free Software Foundation; either version 3 of the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
  * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
  */
 
-/* Wishlist for helpserv.c
- * - Make HelpServ send unassigned request count to helpers as they join the
- *   channel
+/* Wishlist for mod-helpserv.c
  * - FAQ responses
  * - Get cmd_statsreport to sort by time and show requests closed
  * - .. then make statsreport show weighted combination of time and requests closed :)
- * - Something for users to use a subset of commands... like closing their
- *   own request(s), viewing what they've sent so far, and finding their
- *   helper's nick.
- * - It would be nice to have some variable expansions for the custom
- *   request open/close/etc messages. ${request/id}, etc
  * - Allow manipulation of the persist types on a per-request basis... so
  *   if it's normally set to close requests after a user quits, but there
  *   is a long-term issue, then that single request can remain
- * - Bot suspension
  */
 
 #include "conf.h"
@@ -87,6 +79,13 @@ const char *helpserv_module_deps[] = { NULL };
 #define KEY_AUTO_VOICE "auto_voice"
 #define KEY_AUTO_DEVOICE "auto_devoice"
 #define KEY_LAST_ACTIVE "last_active"
+#define KEY_JOIN_TOTAL "join_total"
+#define KEY_ALERT_NEW "alert_new" 
+#define KEY_SUSPENDED "suspended"
+#define KEY_EXPIRY "expiry"
+#define KEY_ISSUED "issued"
+#define KEY_SUSPENDER "suspender"
+#define KEY_REASON "reason"
 
 /* General */
 #define HSFMT_TIME               "%a, %d %b %Y %H:%M:%S %Z"
@@ -107,6 +106,7 @@ static const struct message_entry msgtab[] = {
 /* Greetings */
     { "HSMSG_GREET_EXISTING_REQ", "Welcome back to %s. You have already opened request ID#%lu. Any messages you send to $S will be appended to that request." },
     { "HSMSG_GREET_PRIVMSG_EXISTREQ", "Hello again. Your account has a previously opened request ID#%lu. This message and any further messages you send to $S will be appended to that request." },
+    { "HSMSG_REQUESTS_OPEN", "Hello. There are %d requests open. Use /msg %s LIST to view all requests, or use /msg %s NEXT to pickup the oldest request." },
 
 /* User Management */
     { "HSMSG_CANNOT_ADD", "You do not have permission to add users." },
@@ -136,7 +136,7 @@ static const struct message_entry msgtab[] = {
     { "HSMSG_MOVE_SAME_CHANNEL", "You cannot move %s to the same channel it is on." },
     { "HSMSG_INVALID_MOVE", "$b%s$b is not a valid nick or channel name." },
     { "HSMSG_NEED_GIVEOWNERSHIP_CONFIRM", "To transfer ownership of this bot, you must /msg $S giveownership newowner CONFIRM" },
-    { "HSMSG_MULTIPLE_OWNERS", "There is more than one owner of %s; please use other commands to change ownership." },
+    { "HSMSG_MULTIPLE_OWNERS", "There is more than one owner of %s; please use other commands to change ownership."},
     { "HSMSG_NO_TRANSFER_SELF", "You cannot give ownership to your own account." },
     { "HSMSG_OWNERSHIP_GIVEN", "Ownership of $b%s$b has been transferred to account $b%s$b." },
 
@@ -167,6 +167,8 @@ static const struct message_entry msgtab[] = {
     { "HSMSG_SET_REQONJOIN",      "$bReqOnJoin       $b %s" },
     { "HSMSG_SET_AUTOVOICE",      "$bAutoVoice       $b %s" },
     { "HSMSG_SET_AUTODEVOICE",    "$bAutoDevoice     $b %s" },
+    { "HSMSG_SET_JOINTOTAL",      "$bJoinTotal       $b %s" },
+    { "HSMSG_SET_ALERTNEW",       "$bAlertNew        $b %s" },
     { "HSMSG_PAGE_NOTICE", "notice" },
     { "HSMSG_PAGE_PRIVMSG", "privmsg" },
     { "HSMSG_PAGE_ONOTICE", "onotice" },
@@ -198,26 +200,28 @@ static const struct message_entry msgtab[] = {
     { "HSMSG_REQ_HIM_NOT_IN_OVERRIDE", "Note: %s is being assigned this request even though they are not in %s, because the restriction was overridden (you are a manager or owner). If they join and then part, this request will be marked unassigned." },
     { "HSMSG_REQ_ASSIGNED_YOU", "You have been assigned request ID#%lu:" },
     { "HSMSG_REQ_REASSIGNED", "You have been assigned request ID#%lu (reassigned from %s):" },
-    { "HSMSG_REQ_INFO_1", "Request ID#%lu:" },
-    { "HSMSG_REQ_INFO_2a", " - Nick %s / Account %s" },
-    { "HSMSG_REQ_INFO_2b", " - Nick %s / Not authed" },
-    { "HSMSG_REQ_INFO_2c", " - Online somewhere / Account %s" },
-    { "HSMSG_REQ_INFO_2d", " - Not online / Account %s" },
-    { "HSMSG_REQ_INFO_2e", " - Not online / No account" },
-    { "HSMSG_REQ_INFO_3", " - Opened at %s (%s ago)" },
-    { "HSMSG_REQ_INFO_4", " - Message:" },
+    { "HSMSG_REQ_INFO_1",       "Request ID#%lu:" },
+    { "HSMSG_REQ_INFO_2a",      " - Nick %s / Account %s" },
+    { "HSMSG_REQ_INFO_2b",      " - Nick %s / Not authed" },
+    { "HSMSG_REQ_INFO_2c",      " - Online somewhere / Account %s" },
+    { "HSMSG_REQ_INFO_2d",      " - Not online / Account %s" },
+    { "HSMSG_REQ_INFO_2e",      " - Not online / No account" },
+    { "HSMSG_REQ_INFO_3",       " - Opened at %s (%s ago)" },
+    { "HSMSG_REQ_INFO_4",       " - Message:" },
     { "HSMSG_REQ_INFO_MESSAGE", "   %s" },
     { "HSMSG_REQ_ASSIGNED", "Your helper for request ID#%lu is %s (Current nick: %s)" },
     { "HSMSG_REQ_ASSIGNED_AGAIN", "Your helper for request ID#%lu has been changed to %s (Current nick: %s)" },
     { "HSMSG_REQ_UNASSIGNED", "Your helper for request ID#%lu has %s, so your request is no longer being handled by them. It has been placed near the front of the unhandled request queue, based on how long ago your request was opened." },
     { "HSMSG_REQ_NEW", "Your message has been recorded and assigned request ID#%lu. A helper should contact you shortly." },
     { "HSMSG_REQ_NEWONJOIN", "Welcome to %s. You have been assigned request ID#%lu. A helper should contact you shortly." },
+    { "HSMSG_REQ_ALERT", "A new request (ID#%lu) has been opened in %s. Use /msg %s PICKUP %lu to pickup this request, or use /msg %s NEXT to pickup the oldest request." },
     { "HSMSG_REQ_UNHANDLED_TIME", "The oldest unhandled request has been waiting for %s." },
     { "HSMSG_REQ_NO_UNHANDLED", "There are no other unhandled requests." },
     { "HSMSG_REQ_PERSIST_QUIT", "Everything you tell me until you are helped (or you quit) will be recorded. If you disconnect, your request will be lost." },
     { "HSMSG_REQ_PERSIST_PART", "Everything you tell me until you are helped (or you leave %s) will be recorded. If you part %s, your request will be lost." },
     { "HSMSG_REQ_PERSIST_HANDLE", "Everything you tell me until you are helped will be recorded." },
     { "HSMSG_REQ_MAXLEN", "Sorry, but your request has reached the maximum number of lines. Please wait to be assigned to a helper and continue explaining your request to them." },
+    { "HSMSQ_REQ_TEXT_ADDED", "Message from $b%s:$b %s" },
     { "HSMSG_REQ_FOUND_ANOTHER", "Request ID#%lu has been closed. $S detected that you also have request ID#%lu open. If you send $S a message, it will be associated with that request." },
 
 /* Messages that are inserted into request text */
@@ -241,7 +245,7 @@ static const struct message_entry msgtab[] = {
     { "HSMSG_PAGE_HELPER_GONE_2", "Request ID#%lu from $b%s$b (Not authed) $bhas been unassigned$b, as its helper, %s has %s." },
     { "HSMSG_PAGE_HELPER_GONE_3", "Request ID#%lu from an offline user (Account %s) $bhas been unassigned$b, as its helper, %s has %s." },
     { "HSMSG_PAGE_HELPER_GONE_4", "Request ID#%lu from an offline user (No account) $bhas been unassigned$b, as its helper, %s has %s." },
-    { "HSMSG_PAGE_WHINE_HEADER", "$b%u unhandled request(s)$b waiting at least $b%s$b (%u total)" },
+    { "HSMSG_PAGE_WHINE_HEADER", "$b%u unhandled request(s)$b waiting at least $b%s$b (%u unhandled, %u total)" },
     { "HSMSG_PAGE_IDLE_HEADER", "$b%u users$b in %s $bidle at least %s$b:" },
     { "HSMSG_PAGE_EMPTYALERT", "$b%s has no helpers present (%u unhandled request(s))$b" },
     { "HSMSG_PAGE_ONLYTRIALALERT", "$b%s has no full helpers present (%d trial(s) present; %u unhandled request(s))$b" },
@@ -295,10 +299,31 @@ static const struct message_entry msgtab[] = {
     { "HSMSG_STATS_REPORT_2", "Stats report for two weeks ago" },
     { "HSMSG_STATS_REPORT_3", "Stats report for three weeks ago" },
 
+/* Help */
+    { "HSMSG_HELP_COMMAND_ALIAS", "$uAlias for:$u %s" },
+    { "HSMSG_HELP_COMMAND_HEADER", "Command help for: $b%s$b" },
+    { "HSMSG_HELP_COMMAND_UNKNOWN", "No help available for that command." },
+    { "HSMSG_HELP_TOPIC_HEADER", "Help topic: $b%s$b" },
+    { "HSMSG_HELP_DIVIDER", "=---------------------------------------=" },
+    { "HSMSG_HELP_FOOTER", "=------------- End of Help -------------=" },
+    { "HSMSG_COMMAND_BINDING", "%s is a binding of: %s" },
+
+/* Channel [un]suspension */
+    { "HSMSG_ALREADY_SUSPENDED", "$b%s$b is already suspended." },
+    { "HSMSG_NOT_SUSPENDED", "$b%s$b is not suspended." },
+    { "HSMSG_SUSPENDED", "$b$S$b access to $b%s$b has been temporarily suspended." },
+    { "HSMSG_UNSUSPENDED", "$b$S$b access to $b%s$b has been restored." },
+    { "HSMSG_SUSPEND_NODELETE", "$b%s$b is protected from unregistration, and cannot be suspended." },
+    { "HSMSG_USER_SUSPENDED", "$b%s$b's access to $b%s$b has been suspended." },
+    { "HSMSG_USER_UNSUSPENDED", "$b%s$b's access to $b%s$b has been restored." },
+    { "HSMSG_BOT_NON_EXIST", "HelpServ bot %s does not exist." },
+
 /* Responses to user commands */
     { "HSMSG_YOU_BEING_HELPED", "You are already being helped." },
     { "HSMSG_YOU_BEING_HELPED_BY", "You are already being helped by $b%s$b." },
     { "HSMSG_WAIT_STATUS", "You are %d of %d in line; the first person has waited %s." },
+    { "HSMSG_NO_HELPER", "No one is currently helping you, please wait for a helper to pickup your request." },
+    { "HSMSG_HELPER", "You are currently being helped by %s." },
     { NULL, NULL }
 };
 
@@ -516,9 +541,16 @@ struct helpserv_bot {
     unsigned int req_on_join : 1;
     unsigned int auto_voice : 1;
     unsigned int auto_devoice : 1;
+    unsigned int join_total : 1;
+    unsigned int alert_new : 1;
 
     unsigned int helpchan_empty : 1;
 
+    unsigned int suspended : 1;
+    time_t expiry, issued;
+    char *suspender;
+    char *reason;
+
     time_t registered;
     time_t last_active;
     char *registrar;
@@ -581,16 +613,16 @@ void STRUCTNAME##_free(void *data) {\
 }
 
 DECLARE_LIST(helpserv_botlist, struct helpserv_bot *);
-DEFINE_LIST(helpserv_botlist, struct helpserv_bot *);
-DEFINE_LIST_ALLOC(helpserv_botlist);
+DEFINE_LIST(helpserv_botlist, struct helpserv_bot *)
+DEFINE_LIST_ALLOC(helpserv_botlist)
 
 DECLARE_LIST(helpserv_reqlist, struct helpserv_request *);
-DEFINE_LIST(helpserv_reqlist, struct helpserv_request *);
-DEFINE_LIST_ALLOC(helpserv_reqlist);
+DEFINE_LIST(helpserv_reqlist, struct helpserv_request *)
+DEFINE_LIST_ALLOC(helpserv_reqlist)
 
 DECLARE_LIST(helpserv_userlist, struct helpserv_user *);
-DEFINE_LIST(helpserv_userlist, struct helpserv_user *);
-DEFINE_LIST_ALLOC(helpserv_userlist);
+DEFINE_LIST(helpserv_userlist, struct helpserv_user *)
+DEFINE_LIST_ALLOC(helpserv_userlist)
 
 struct helpfile *helpserv_helpfile;
 static struct module *helpserv_module;
@@ -607,10 +639,10 @@ static dict_t helpserv_users_byhand_dict; /* indexed by handle, holds a struct h
 /* This is so that overrides can "speak" from opserv */
 extern struct userNode *opserv;
 
-#define HELPSERV_SYNTAX() helpserv_help(hs, from_opserv, user, argv[0])
+#define HELPSERV_SYNTAX() helpserv_help(hs, from_opserv, user, argv[0], 0)
 #define HELPSERV_FUNC(NAME) int NAME(struct userNode *user, UNUSED_ARG(struct helpserv_bot *hs), int from_opserv, UNUSED_ARG(unsigned int argc), UNUSED_ARG(char *argv[]))
 typedef HELPSERV_FUNC(helpserv_func_t);
-#define HELPSERV_USERCMD(NAME) void NAME(struct helpserv_request *req, UNUSED_ARG(struct userNode *likely_helper), UNUSED_ARG(char *args))
+#define HELPSERV_USERCMD(NAME) int NAME(UNUSED_ARG(struct helpserv_request *req), UNUSED_ARG(struct userNode *likely_helper), UNUSED_ARG(const char *args), UNUSED_ARG(int argc), UNUSED_ARG(char *argv[]), UNUSED_ARG(struct userNode *user), UNUSED_ARG(struct helpserv_bot *hs))
 typedef HELPSERV_USERCMD(helpserv_usercmd_t);
 #define HELPSERV_OPTION(NAME) HELPSERV_FUNC(NAME)
 typedef HELPSERV_OPTION(helpserv_option_func_t);
@@ -623,31 +655,37 @@ static HELPSERV_FUNC(cmd_help);
         return 0; }
 
 /* For messages going to users being helped */
-#define helpserv_msguser(target, format...) send_message_type((from_opserv ? 0 : hs->privmsg_only), (target), (from_opserv ? opserv : hs->helpserv) , ## format)
-#define helpserv_user_reply(format...) send_message_type(req->hs->privmsg_only, req->user, req->hs->helpserv , ## format)
+#if defined(GCC_VARMACROS)
+# define helpserv_msguser(target, ARGS...) send_message_type((from_opserv ? 0 : hs->privmsg_only), (target), (fromopserv ? opserv : hs->helpserv), ARGS)
+# define helpserv_user_reply(ARGS...) send_message_type(req->hs->privmsg_only, req->user, req->hs->helpserv, ARGS)
+/* For messages going to helpers */
+# define helpserv_notice(target, ARGS...) send_message((target), (from_opserv ? opserv : hs->helpserv), ARGS)
+# define helpserv_notify(helper, ARGS...) do { struct userNode *_target; for (_target = (helper)->handle->users; _target; _target = _target->next_authed) { \
+        send_message(_target, (helper)->hs->helpserv, ARGS); \
+    } } while (0)
+# define helpserv_page(TYPE, ARGS...) do { \
+    int msg_type=0; struct chanNode *target=helpserv_get_page_type(hs, (TYPE), &msg_type); \
+    if (target) send_target_message(msg_type, target->name, hs->helpserv, ARGS); \
+    } while (0)
+#elif defined(C99_VARMACROS)
+# define helpserv_msguser(target, ...) send_message_type((from_opserv ? 0 : hs->privmsg_only), (target), (from_opserv ? opserv : hs->helpserv), __VA_ARGS__)
+# define helpserv_user_reply(...) send_message_type(req->hs->privmsg_only, req->user, req->hs->helpserv, __VA_ARGS__)
 /* For messages going to helpers */
-#define helpserv_notice(target, format...) send_message((target), (from_opserv ? opserv : hs->helpserv) , ## format)
-#define helpserv_notify(helper, format...) do { struct userNode *_target; for (_target = (helper)->handle->users; _target; _target = _target->next_authed) { \
-        send_message(_target, (helper)->hs->helpserv, ## format); \
+# define helpserv_notice(target, ...) send_message((target), (from_opserv ? opserv : hs->helpserv), __VA_ARGS__)
+# define helpserv_notify(helper, ...) do { struct userNode *_target; for (_target = (helper)->handle->users; _target; _target = _target->next_authed) { \
+        send_message(_target, (helper)->hs->helpserv, __VA_ARGS__); \
     } } while (0)
+# define helpserv_page(TYPE, ...) do { \
+    int msg_type=0; struct chanNode *target=helpserv_get_page_type(hs, (TYPE), &msg_type); \
+    if (target) send_target_message(msg_type, target->name, hs->helpserv, __VA_ARGS__); \
+    } while (0)
+#endif
 #define helpserv_message(hs, target, id) do { if ((hs)->messages[id]) { \
     if (from_opserv) \
         send_message_type(4, (target), opserv, "%s", (hs)->messages[id]); \
     else \
         send_message_type(4 | hs->privmsg_only, (target), hs->helpserv, "%s", (hs)->messages[id]); \
     } } while (0)
-#define helpserv_page(TYPE, FORMAT...) do { \
-    struct chanNode *target=NULL; int msg_type=0; \
-    target = hs->page_targets[TYPE]; \
-    switch (hs->page_types[TYPE]) { \
-        case PAGE_NOTICE: msg_type = 0; break; \
-        case PAGE_PRIVMSG: msg_type = 1; break; \
-        case PAGE_ONOTICE: msg_type = 2; break; \
-        default: log_module(HS_LOG, LOG_ERROR, "helpserv_page() called but %s has an invalid page type %d.", hs->helpserv->nick, TYPE); \
-        case PAGE_NONE: target = NULL; break; \
-    } \
-    if (target) send_target_message(msg_type, target->name, hs->helpserv, ## FORMAT); \
-    } while (0)
 #define helpserv_get_handle_info(user, text) smart_get_handle_info((from_opserv ? opserv : hs->helpserv) , (user), (text))
 
 struct helpserv_cmd {
@@ -696,10 +734,10 @@ static void helpserv_log_request(struct helpserv_request *req, const char *reaso
 
     assert(req != NULL);
     assert(reason != NULL);
-    if (!(ctx = saxdb_open_context(reqlog_f)))
+    if (!reqlog_f || !(ctx = saxdb_open_context(reqlog_f)))
         return;
     sprintf(key, "%s-" FMT_TIME_T "-%lu", req->hs->helpserv->nick, req->opened, req->id);
-    if ((res = setjmp(ctx->jbuf)) != 0) {
+    if ((res = setjmp(*saxdb_jmp_buf(ctx))) != 0) {
         log_module(HS_LOG, LOG_ERROR, "Unable to log helpserv request: %s.", strerror(res));
     } else {
         saxdb_start_record(ctx, key, 1);
@@ -720,9 +758,29 @@ static void helpserv_log_request(struct helpserv_request *req, const char *reaso
         saxdb_write_string(ctx, KEY_REQUEST_CLOSEREASON, reason);
         saxdb_write_string_list(ctx, KEY_REQUEST_TEXT, req->text);
         saxdb_end_record(ctx);
-        saxdb_close_context(ctx);
-        fflush(reqlog_f);
+        saxdb_close_context(ctx, 0);
+    }
+}
+
+static struct chanNode *helpserv_get_page_type(struct helpserv_bot *hs, enum page_source type, int *msg_type)
+{
+    switch (hs->page_types[type]) {
+        case PAGE_NOTICE:
+            *msg_type = 0;
+            break;
+        case PAGE_PRIVMSG:
+            *msg_type = 1;
+            break;
+        case PAGE_ONOTICE:
+            *msg_type = 2;
+            break;
+        default:
+            log_module(HS_LOG, LOG_ERROR, "helpserv_page() called but %s has an invalid page type %d.", hs->helpserv->nick, type);
+            /* and fall through */
+        case PAGE_NONE:
+            return NULL;
     }
+    return hs->page_targets[type];
 }
 
 /* Searches for a request by number, nick, or account (num|nick|*account).
@@ -821,16 +879,19 @@ static struct helpserv_request * smart_get_request(struct helpserv_bot *hs, stru
 
 static struct helpserv_request * create_request(struct userNode *user, struct helpserv_bot *hs, int from_join) {
     struct helpserv_request *req = calloc(1, sizeof(struct helpserv_request));
-    char lbuf[3][MAX_LINE_SIZE], unh[INTERVALLEN];
+    char lbuf[3][MAX_LINE_SIZE], req_id[INTERVALLEN], abuf[1][MAX_LINE_SIZE];
     struct helpserv_reqlist *reqlist, *hand_reqlist;
+    struct helpserv_user *hs_user;
+    struct userNode *target, *next_un = NULL;
     const unsigned int from_opserv = 0;
-    const char *fmt;
+    const char *fmt, *afmt;
+    dict_iterator_t it;
 
     assert(req);
 
     req->id = ++hs->last_requestid;
-    sprintf(unh, "%lu", req->id);
-    dict_insert(hs->requests, strdup(unh), req);
+    sprintf(req_id, "%lu", req->id);
+    dict_insert(hs->requests, strdup(req_id), req);
 
     if (hs->id_wrap) {
         unsigned long i;
@@ -923,14 +984,29 @@ static struct helpserv_request * create_request(struct userNode *user, struct he
         fmt = user_find_message(user, "HSMSG_REQ_NEW");
         sprintf(lbuf[0], fmt, req->id);
     }
+
     if (req != hs->unhandled) {
-        intervalString(unh, now - hs->unhandled->opened, user->handle_info);
+        intervalString(req_id, now - hs->unhandled->opened, user->handle_info);
         fmt = user_find_message(user, "HSMSG_REQ_UNHANDLED_TIME");
-        sprintf(lbuf[1], fmt, unh);
+        sprintf(lbuf[1], fmt, req_id);
     } else {
         fmt = user_find_message(user, "HSMSG_REQ_NO_UNHANDLED");
-        sprintf(lbuf[1], fmt);
+        sprintf(lbuf[1], "%s", fmt);
+    }
+
+    if (hs->alert_new) {
+        for (it = dict_first(hs->users); it; it = iter_next(it)) {
+            hs_user = iter_data(it);
+
+            afmt = user_find_message(user, "HSMSG_REQ_ALERT");
+            sprintf(abuf[0], afmt, req->id, hs->helpchan->name, hs->helpserv->nick, req->id, hs->helpserv->nick);
+            for (target = hs_user->handle->users; target; target = next_un) {
+                send_message_type(4, target, hs->helpserv, "%s", abuf[0]);
+                next_un = target->next_authed;
+            }
+        }
     }
+
     switch (hs->persist_types[PERSIST_T_REQUEST]) {
         case PERSIST_PART:
             fmt = user_find_message(user, "HSMSG_REQ_PERSIST_PART");
@@ -938,18 +1014,17 @@ static struct helpserv_request * create_request(struct userNode *user, struct he
             break;
         case PERSIST_QUIT:
             fmt = user_find_message(user, "HSMSG_REQ_PERSIST_QUIT");
-            sprintf(lbuf[2], fmt);
+            sprintf(lbuf[2], "%s", fmt);
             break;
         default:
             log_module(HS_LOG, LOG_ERROR, "%s has an invalid req_persist.", hs->helpserv->nick);
         case PERSIST_CLOSE:
             if (user->handle_info) {
                 fmt = user_find_message(user, "HSMSG_REQ_PERSIST_HANDLE");
-                sprintf(lbuf[2], fmt);
             } else {
                 fmt = user_find_message(user, "HSMSG_REQ_PERSIST_QUIT");
-                sprintf(lbuf[2], fmt);
             }
+            sprintf(lbuf[2], "%s", fmt);
             break;
     }
     helpserv_message(hs, user, MSGTYPE_REQ_OPENED);
@@ -967,19 +1042,22 @@ static struct helpserv_request * create_request(struct userNode *user, struct he
 }
 
 /* Handle a message from a user to a HelpServ bot. */
-static void helpserv_usermsg(struct userNode *user, struct helpserv_bot *hs, char *text) {
+static void helpserv_usermsg(struct userNode *user, struct helpserv_bot *hs, const char *text, char *argv[], int argc) {
     const int from_opserv = 0; /* for helpserv_notice */
     struct helpserv_request *req=NULL, *newest=NULL;
     struct helpserv_reqlist *reqlist, *hand_reqlist;
     unsigned int i;
 
+    if(argc < 1)
+       return;
     if ((reqlist = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL))) {
         for (i=0; i < reqlist->used; i++) {
             req = reqlist->list[i];
             if (req->hs != hs)
                 continue;
-            if (!newest || (newest->opened < req->opened))
-                newest = req;
+            /* if (!newest || (newest->opened < req->opened))  ?? XXX: this is a noop.. commenting it out -rubin */
+                                                               /* newest is set to null 9 lines up, and isnt changed between.. */
+            newest = req;
         }
 
         /* If nothing was found, this will set req to NULL */
@@ -1014,13 +1092,30 @@ static void helpserv_usermsg(struct userNode *user, struct helpserv_bot *hs, cha
                 helpserv_msguser(user, "HSMSG_GREET_PRIVMSG_EXISTREQ", req->id);
             }
         }
-    } else {
-        hand_reqlist = NULL;
-    }
+    } 
 
     if (!req) {
-        if (text[0] == helpserv_conf.user_escape) {
-            helpserv_msguser(user, "HSMSG_USERCMD_NO_REQUEST");
+        if (argv[1][0] == helpserv_conf.user_escape) {
+            char *cmdname;
+            helpserv_usercmd_t *usercmd;
+            struct userNode *likely_helper;
+
+            /* Allow HELP user command but no other if no open request,
+               this may change in the future if we make commands that
+               dont need an open request */
+
+            /* Find somebody likely to be the helper */
+            likely_helper = NULL;
+
+            cmdname = argv[1]+1;
+
+            /* Call the user command function */
+            usercmd = dict_find(helpserv_usercmd_dict, cmdname, NULL);
+            if (usercmd && !strcasecmp(cmdname, "HELP"))
+                usercmd(req, likely_helper, NULL, argc, argv, user, hs);
+            else
+                helpserv_msguser(user, "HSMSG_USERCMD_NO_REQUEST");
+
             return;
         }
         if ((hs->persist_types[PERSIST_T_REQUEST] == PERSIST_PART) && !GetUserMode(hs->helpchan, user)) {
@@ -1033,8 +1128,8 @@ static void helpserv_usermsg(struct userNode *user, struct helpserv_bot *hs, cha
             helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_NEW_REQUEST_AUTHED", req->id, user->nick, user->handle_info->handle);
         else
             helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_NEW_REQUEST_UNAUTHED", req->id, user->nick);
-    } else if (text[0] == helpserv_conf.user_escape) {
-        char cmdname[MAXLEN], *space;
+    } else if (argv[1][0] == helpserv_conf.user_escape) {
+        char *cmdname;
         helpserv_usercmd_t *usercmd;
         struct userNode *likely_helper;
 
@@ -1047,17 +1142,12 @@ static void helpserv_usermsg(struct userNode *user, struct helpserv_bot *hs, cha
             if (GetUserMode(hs->helpchan, likely_helper))
                 break;
 
-        /* Parse out command name */
-        space = strchr(text+1, ' ');
-        if (space)
-            strncpy(cmdname, text+1, space-text-1);
-        else
-            strcpy(cmdname, text+1);
+        cmdname = argv[1]+1;
 
         /* Call the user command function */
         usercmd = dict_find(helpserv_usercmd_dict, cmdname, NULL);
         if (usercmd)
-            usercmd(req, likely_helper, space+1);
+            usercmd(req, likely_helper, text, argc, argv, user, hs);
         else
             helpserv_msguser(user, "HSMSG_USERCMD_UNKNOWN", cmdname);
         return;
@@ -1072,7 +1162,7 @@ static void helpserv_usermsg(struct userNode *user, struct helpserv_bot *hs, cha
             if (user->handle_info)
                 helpserv_notify(req->helper, "HSMSG_PAGE_UPD_REQUEST_AUTHED", req->id, user->nick, user->handle_info->handle, timestr, updatestr);
             else
-                helpserv_notify(req->helper, "HSMSG_PAGE_UPD_REQUESTNOT_AUTHED", req->id, user->nick, timestr, updatestr);
+                helpserv_notify(req->helper, "HSMSG_PAGE_UPD_REQUEST_NOT_AUTHED", req->id, user->nick, timestr, updatestr);
         else
             if (user->handle_info)
                 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_UPD_REQUEST_AUTHED", req->id, user->nick, user->handle_info->handle, timestr, updatestr);
@@ -1085,17 +1175,33 @@ static void helpserv_usermsg(struct userNode *user, struct helpserv_bot *hs, cha
 
     req->updated = now;
     if (!hs->req_maxlen || req->text->used < hs->req_maxlen)
+    {
         string_list_append(req->text, strdup(text));
+
+        struct userNode *likely_helper;
+        /* Find somebody likely to be the helper */
+        if (!req->helper)
+            likely_helper = NULL;
+        else if ((likely_helper = req->helper->handle->users) && !likely_helper->next_authed) {
+            /* only one user it could be :> */
+        } else for (likely_helper = req->helper->handle->users; likely_helper; likely_helper = likely_helper->next_authed)
+            if (GetUserMode(hs->helpchan, likely_helper))
+                break;
+
+        if(likely_helper)
+            send_target_message(1, likely_helper->nick, hs->helpserv, "HSMSQ_REQ_TEXT_ADDED", user->nick, text);
+    }
     else
         helpserv_msguser(user, "HSMSG_REQ_MAXLEN");
 }
 
 /* Handle messages direct to a HelpServ bot. */
-static void helpserv_botmsg(struct userNode *user, struct userNode *target, char *text, UNUSED_ARG(int server_qualified)) {
+static void helpserv_botmsg(struct userNode *user, struct userNode *target, const char *text, UNUSED_ARG(int server_qualified)) {
     struct helpserv_bot *hs;
     struct helpserv_cmd *cmd;
-    struct helpserv_user *hs_user;
+    struct helpserv_user *hs_user = NULL;
     char *argv[MAXNUMPARAMS];
+    char tmpline[MAXLEN];
     int argc, argv_shift;
     const int from_opserv = 0; /* for helpserv_notice */
 
@@ -1105,17 +1211,33 @@ static void helpserv_botmsg(struct userNode *user, struct userNode *target, char
 
     hs = dict_find(helpserv_bots_dict, target->nick, NULL);
 
-    /* See if we should listen to their message as a command (helper)
-     * or a help request (user) */
-    if (!user->handle_info || !(hs_user = dict_find(hs->users, user->handle_info->handle, NULL))) {
-        helpserv_usermsg(user, hs, text);
+    
+    /* XXX: For some unknown reason we are shifting +1 on the array and ignoring argv[0]; to avoid someone rightly assuming argv[0] 
+     * was the first argument later on and addressing random memory, were going to make argv[0] null. This whole thing is pretty unacceptable and needs fixed, though.*/
+    argv[0] = NULL;
+    argv_shift = 1;
+    safestrncpy(tmpline, text, sizeof(tmpline));
+    argc = split_line(tmpline, false, ArrayLength(argv)-argv_shift, argv+argv_shift);
+    if (!argc)
+        return;
+
+    /* XXX: why are we duplicating text here, and i don't see it being free'd anywhere.... */
+    /* text = strdup(text); */
+
+    if (user->handle_info)
+        hs_user = dict_find(hs->users, user->handle_info->handle, NULL);
+
+    if (hs->suspended && !IsOper(user)) {
+        helpserv_notice(user, "HSMSG_SUSPENDED", hs->helpserv->nick);
         return;
     }
 
-    argv_shift = 1;
-    argc = split_line(text, false, ArrayLength(argv)-argv_shift, argv+argv_shift);
-    if (!argc)
+    /* See if we should listen to their message as a command (helper)
+     * or a help request (user) */
+    if (!user->handle_info || !hs_user) {
+        helpserv_usermsg(user, hs, text, argv, argc);
         return;
+    }
 
     cmd = dict_find(helpserv_func_dict, argv[argv_shift], NULL);
     if (!cmd) {
@@ -1133,8 +1255,8 @@ static void helpserv_botmsg(struct userNode *user, struct userNode *target, char
     if (!cmd->func) {
         helpserv_notice(user, "HSMSG_INTERNAL_COMMAND", argv[argv_shift]);
     } else if (cmd->func(user, hs, 0, argc, argv+argv_shift)) {
-        unsplit_string(argv+argv_shift, argc, text);
-        log_audit(HS_LOG, LOG_COMMAND, user, hs->helpserv, hs->helpchan->name, 0, text);
+        unsplit_string(argv+argv_shift, argc, tmpline);
+        log_audit(HS_LOG, LOG_COMMAND, user, hs->helpserv, hs->helpchan->name, 0, tmpline);
     }
 }
 
@@ -1183,8 +1305,27 @@ static MODCMD_FUNC(cmd_helpserv) {
     return retval;
 }
 
-static void helpserv_help(struct helpserv_bot *hs, int from_opserv, struct userNode *user, const char *topic) {
-    send_help(user, (from_opserv ? opserv : hs->helpserv), helpserv_helpfile, topic);
+static void helpserv_help(struct helpserv_bot *hs, int from_opserv, struct userNode *user, const char *topic, int from_user) {
+    char cmdname[MAXLEN];
+    unsigned int nn;
+
+    /* If there is no topic show the index */
+    if (!topic && from_user)
+        topic = "<userindex>";
+    else if (!topic && !from_user)
+        topic = "<index>";
+
+    /* make heading str (uppercase) */
+    for (nn=0; topic[nn]; nn++)
+            cmdname[nn] = toupper(topic[nn]);
+    cmdname[nn] = 0;
+
+    send_message(user, (from_opserv ? opserv : hs->helpserv), "HSMSG_HELP_TOPIC_HEADER", cmdname);
+    send_message(user, (from_opserv ? opserv : hs->helpserv), "HSMSG_HELP_DIVIDER");
+    if (!send_help(user, (from_opserv ? opserv : hs->helpserv), helpserv_helpfile, topic))
+        send_message(user, (from_opserv ? opserv : hs->helpserv), "MSG_TOPIC_UNKNOWN");
+    send_message(user, (from_opserv ? opserv : hs->helpserv), "HSMSG_HELP_FOOTER");
+
 }
 
 static int append_entry(const char *key, UNUSED_ARG(void *data), void *extra) {
@@ -1231,10 +1372,10 @@ static HELPSERV_USERCMD(usercmd_wait) {
             helpserv_user_reply("HSMSG_YOU_BEING_HELPED_BY", likely_helper->nick);
         else
             helpserv_user_reply("HSMSG_YOU_BEING_HELPED");
-        return;
+        return 0;
     }
 
-    for (other = req->hs->unhandled, pos = -1, count = 0; 
+    for (other = req->hs->unhandled, pos = -1, count = 0;
          other;
          other = other->next_unhandled, ++count) {
         if (other == req)
@@ -1243,6 +1384,30 @@ static HELPSERV_USERCMD(usercmd_wait) {
     assert(pos >= 0);
     intervalString(buf, now - req->hs->unhandled->opened, req->user->handle_info);
     helpserv_user_reply("HSMSG_WAIT_STATUS", pos+1, count, buf);
+
+    return 0;
+}
+
+static HELPSERV_USERCMD(usercmd_helper) {
+    if (req->helper) {
+        if (likely_helper)
+            helpserv_user_reply("HSMSG_HELPER", likely_helper->nick);
+    } else
+        helpserv_user_reply("HSMSG_NO_HELPER");
+
+    return 0;
+}
+
+static HELPSERV_USERCMD(usercmd_help) {
+    const char *topic;
+
+    if (argc < 2)
+        topic = NULL;
+    else
+        topic = unsplit_string(argv+2, argc-1, NULL);
+    helpserv_help(hs, 0, user, topic, 1);
+
+    return 0;
 }
 
 static HELPSERV_FUNC(cmd_help) {
@@ -1252,7 +1417,7 @@ static HELPSERV_FUNC(cmd_help) {
         topic = NULL;
     else
         topic = unsplit_string(argv+1, argc-1, NULL);
-    helpserv_help(hs, from_opserv, user, topic);
+    helpserv_help(hs, from_opserv, user, topic, 0);
 
     return 1;
 }
@@ -1392,7 +1557,7 @@ static int show_helper_range(struct userNode *user, struct helpserv_bot *hs, int
     struct helpfile_table tbl;
     struct helpserv_user *hs_user;
     dict_iterator_t it;
-    enum helpserv_level last_level;
+/*    enum helpserv_level last_level; Zoot style */
     unsigned int ii;
 
     users.used = 0;
@@ -1413,7 +1578,8 @@ static int show_helper_range(struct userNode *user, struct helpserv_bot *hs, int
     }
     qsort(users.list, users.used, sizeof(users.list[0]), helpserv_user_comp);
     switch (user->handle_info->userlist_style) {
-    case HI_STYLE_DEF:
+    default:
+    case HI_STYLE_NORMAL:
         tbl.length = users.used + 1;
         tbl.width = 3;
         tbl.flags = TABLE_NO_FREE;
@@ -1431,6 +1597,7 @@ static int show_helper_range(struct userNode *user, struct helpserv_bot *hs, int
         }
         table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
         break;
+    /*
     case HI_STYLE_ZOOT: default:
         last_level = HlNone;
         tbl.length = 0;
@@ -1454,6 +1621,7 @@ static int show_helper_range(struct userNode *user, struct helpserv_bot *hs, int
             helpserv_notice(user, "HSMSG_USERLIST_ZOOT_LVL", hs->helpserv->nick, helpserv_level_names[last_level]);
             table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
         }
+        */
     }
     return 1;
 }
@@ -1550,7 +1718,7 @@ static void free_request(void *data) {
 
     /* Logging */
     if (shutting_down && (req->hs->persist_types[PERSIST_T_REQUEST] != PERSIST_CLOSE || !req->handle)) {
-        helpserv_log_request(req, "srvx shutdown");
+        helpserv_log_request(req, "X3 shutdown");
     }
 
     /* Clean up from the unhandled queue */
@@ -1684,6 +1852,94 @@ static HELPSERV_FUNC(cmd_close) {
     return 1;
 }
 
+static HELPSERV_USERCMD(usercmd_close) {
+    struct helpserv_request *newest=NULL;
+    struct helpserv_reqlist *nick_list, *hand_list;
+    char close_reason[MAXLEN], reqnum[12];
+    struct userNode *req_user=NULL;
+    unsigned long old_req;
+    unsigned int i;
+    int num_requests=0, from_opserv=0;
+
+    REQUIRE_PARMS(1);
+
+    sprintf(reqnum, "%lu", req->id);
+
+    if (num_requests > 1) {
+        helpserv_notice(user, "HSMSG_REQ_FOUNDMANY");
+        return 0;
+    }
+
+    helpserv_notice(user, "HSMSG_REQ_CLOSED", req->id);
+
+    if (req->user) {
+        req_user = req->user;
+        helpserv_message(hs, req->user, MSGTYPE_REQ_CLOSED);
+        if (req->handle)
+            helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_CLOSE_REQUEST_1", req->id, req->user->nick, req->handle->handle, user->nick);
+        else
+            helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_CLOSE_REQUEST_2", req->id, req->user->nick, user->nick);
+    } else {
+        if (req->handle)
+            helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_CLOSE_REQUEST_3", req->id, req->handle->handle, user->nick);
+        else
+            helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_CLOSE_REQUEST_4", req->id, user->nick);
+    }
+
+    /* Set these to keep track of the lists after the request is gone, but
+     * not if free_request() will helpserv_reqlist_free() them. */
+    nick_list = req->parent_nick_list;
+    if (nick_list && (nick_list->used == 1))
+        nick_list = NULL;
+    hand_list = req->parent_hand_list;
+    if (hand_list && (hand_list->used == 1))
+        hand_list = NULL;
+    old_req = req->id;
+
+    if (argc >= 1) {
+        if (user->handle_info)
+            snprintf(close_reason, MAXLEN, "Closed by %s: %s", user->handle_info->handle, unsplit_string(argv+1, argc-1, NULL));
+        else
+            snprintf(close_reason, MAXLEN, "Closed by %s: %s", user->nick, unsplit_string(argv+1, argc-1, NULL));
+    } else {
+        if (user->handle_info)
+            sprintf(close_reason, "Closed by %s", user->nick);
+        else
+            sprintf(close_reason, "Closed by %s", user->handle_info->handle);
+    }
+    helpserv_log_request(req, close_reason);
+    dict_remove(hs->requests, reqnum);
+
+    /* Look for other requests associated with them */
+    if (nick_list) {
+        for (i=0; i < nick_list->used; i++) {
+            req = nick_list->list[i];
+
+            if (req->hs != hs)
+                continue;
+            if (!newest || (newest->opened < req->opened))
+                newest = req;
+        }
+
+        if (newest)
+            helpserv_msguser(newest->user, "HSMSG_REQ_FOUND_ANOTHER", old_req, newest->id);
+    }
+
+    if (req_user && hs->auto_devoice) {
+        struct modeNode *mn = GetUserMode(hs->helpchan, req_user);
+        if ((!newest || !newest->helper) && mn && (mn->modes & MODE_VOICE)) {
+            struct mod_chanmode change;
+            mod_chanmode_init(&change);
+            change.argc = 1;
+            change.args[0].mode = MODE_REMOVE | MODE_VOICE;
+            change.args[0].u.member = mn;
+            mod_chanmode_announce(hs->helpserv, hs->helpchan, &change);
+        }
+    }
+
+    return 0;
+}
+
 static HELPSERV_FUNC(cmd_list) {
     dict_iterator_t it;
     int searchtype;
@@ -1917,6 +2173,18 @@ static HELPSERV_FUNC(cmd_show) {
     return 1;
 }
 
+static HELPSERV_USERCMD(usercmd_show) {
+    int num_requests=0;
+    int from_opserv=0;
+
+    if (num_requests > 1)
+        helpserv_notice(user, "HSMSG_REQ_FOUNDMANY");
+
+    helpserv_notice(user, "HSMSG_REQ_INFO_1", req->id);
+    helpserv_show(from_opserv, hs, user, req);
+    return 0;
+}
+
 static HELPSERV_FUNC(cmd_pickup) {
     struct helpserv_request *req;
     struct helpserv_user *hs_user=GetHSUser(hs, user->handle_info);
@@ -2197,7 +2465,7 @@ static HELPSERV_FUNC(cmd_move) {
     REQUIRE_PARMS(2);
 
     if (is_valid_nick(argv[1])) {
-        char *newnick = argv[1], oldnick[NICKLEN], reason[MAXLEN];
+        char *newnick = argv[1], oldnick[NICKLEN];
 
         strcpy(oldnick, hs->helpserv->nick);
 
@@ -2212,8 +2480,8 @@ static HELPSERV_FUNC(cmd_move) {
 
         helpserv_notice(user, "HSMSG_RENAMED", oldnick, newnick);
 
-        snprintf(reason, MAXLEN, "HelpServ bot %s (in %s) renamed to %s by %s.", oldnick, hs->helpchan->name, newnick, user->nick);
-        global_message(MESSAGE_RECIPIENT_OPERS, reason);
+        global_message_args(MESSAGE_RECIPIENT_OPERS, "HSMSG_BOT_RENAMED", oldnick,
+                            hs->helpchan->name, newnick, user->nick);
 
         return 1;
     } else if (IsChannelName(argv[1])) {
@@ -2263,8 +2531,8 @@ static HELPSERV_FUNC(cmd_move) {
         }
         helpserv_botlist_append(botlist, hs);
 
-        snprintf(reason, MAXLEN, "HelpServ %s (%s) moved to %s by %s.", hs->helpserv->nick, oldchan, newchan, user->nick);
-        global_message(MESSAGE_RECIPIENT_OPERS, reason);
+        global_message_args(MESSAGE_RECIPIENT_OPERS, "HSMSG_BOT_MOVED", hs->helpserv->nick,
+                            oldchan, newchan, user->nick);
 
         return 1;
     } else {
@@ -2420,7 +2688,7 @@ static void run_whine_interval(void *data) {
                 tbl.contents[i][3] = strdup(unh_time);
             }
 
-            helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_WHINE_HEADER", reqlist.used, strwhinedelay, queuesize);
+            helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_WHINE_HEADER", reqlist.used, strwhinedelay, queuesize, dict_size(hs->requests));
             table_send(hs->helpserv, hs->page_targets[PGSRC_ALERT]->name, 0, page_type_funcs[hs->page_types[PGSRC_ALERT]], tbl);
 
             for (i=1; i <= reqlist.used; i++) {
@@ -2428,7 +2696,7 @@ static void run_whine_interval(void *data) {
                 free((char *)tbl.contents[i][3]);
             }
 #else
-            helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_WHINE_HEADER", reqlist.used, strwhinedelay, queuesize);
+            helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_WHINE_HEADER", reqlist.used, strwhinedelay, queuesize, dict_size(hs->requests));
 #endif
         }
 
@@ -2599,7 +2867,7 @@ static struct helpserv_bot *register_helpserv(const char *nick, const char *help
      * it's a harmless default */
     hs = calloc(1, sizeof(struct helpserv_bot));
 
-    if (!(hs->helpserv = AddService(nick, "+iok", helpserv_conf.description, NULL))) {
+    if (!(hs->helpserv = AddLocalUser(nick, nick, NULL, helpserv_conf.description, NULL))) {
         free(hs);
         return NULL;
     }
@@ -2609,7 +2877,7 @@ static struct helpserv_bot *register_helpserv(const char *nick, const char *help
     if (!(hs->helpchan = GetChannel(help_channel))) {
         hs->helpchan = AddChannel(help_channel, now, NULL, NULL, NULL);
         AddChannelUser(hs->helpserv, hs->helpchan)->modes |= MODE_CHANOP;
-    } else {
+    } else if (!hs->suspended) {
         struct mod_chanmode change;
         mod_chanmode_init(&change);
         change.argc = 1;
@@ -2640,7 +2908,7 @@ static struct helpserv_bot *register_helpserv(const char *nick, const char *help
 }
 
 static HELPSERV_FUNC(cmd_register) {
-    char *nick, *helpchan, reason[MAXLEN];
+    char *nick, *helpchan;
     struct handle_info *handle;
 
     REQUIRE_PARMS(4);
@@ -2676,9 +2944,9 @@ static HELPSERV_FUNC(cmd_register) {
 
     helpserv_notice(user, "HSMSG_REG_SUCCESS", handle->handle, nick);
 
-    snprintf(reason, MAXLEN, "HelpServ %s (%s) registered to %s by %s.", nick, hs->helpchan->name, handle->handle, user->nick);
     /* Not sent to helpers, since they can't register HelpServ */
-    global_message(MESSAGE_RECIPIENT_OPERS, reason);
+    global_message_args(MESSAGE_RECIPIENT_OPERS, "HSMSG_BOT_REGISTERED", nick,
+                        hs->helpchan->name, handle->handle, user->nick);
     return 1;
 }
 
@@ -2703,11 +2971,35 @@ static void helpserv_free_bot(void *data) {
     free(data);
 }
 
+static void helpserv_expire_suspension(void *data) {
+    struct helpserv_bot *hs = data;
+    struct chanNode *channel;
+    struct mod_chanmode *change;
+
+    channel = hs->helpchan;
+    hs->suspended = 0;
+    hs->expiry = 0;
+    hs->issued = 0;
+    hs->reason = NULL;
+    hs->suspender = NULL;
+
+    change = mod_chanmode_alloc(1);
+    change->argc = 1;
+    change->args[0].mode = MODE_CHANOP;
+    change->args[0].u.member = AddChannelUser(hs->helpserv, channel);
+
+    mod_chanmode_announce(hs->helpserv, channel, change);
+    mod_chanmode_free(change);
+}
+
 static void helpserv_unregister(struct helpserv_bot *bot, const char *quit_fmt, const char *global_fmt, const char *actor) {
     char reason[MAXLEN], channame[CHANNELLEN], botname[NICKLEN];
     struct helpserv_botlist *botlist;
     size_t len;
 
+    if (bot->suspended && bot->expiry)
+        timeq_del(bot->expiry, helpserv_expire_suspension, bot, 0);
+
     botlist = dict_find(helpserv_bots_bychan_dict, bot->helpchan->name, NULL);
     helpserv_botlist_remove(botlist, bot);
     if (!botlist->used)
@@ -2719,8 +3011,7 @@ static void helpserv_unregister(struct helpserv_bot *bot, const char *quit_fmt,
     snprintf(reason, sizeof(reason), quit_fmt, actor);
     DelUser(bot->helpserv, NULL, 1, reason);
     dict_remove(helpserv_bots_dict, botname);
-    snprintf(reason, sizeof(reason), global_fmt, botname, channame, actor);
-    global_message(MESSAGE_RECIPIENT_OPERS, reason);
+    global_message_args(MESSAGE_RECIPIENT_OPERS, global_fmt, botname, channame, actor);
 }
 
 static HELPSERV_FUNC(cmd_unregister) {
@@ -2732,10 +3023,76 @@ static HELPSERV_FUNC(cmd_unregister) {
         log_audit(HS_LOG, LOG_COMMAND, user, hs->helpserv, hs->helpchan->name, 0, "unregister CONFIRM");
     }
 
-    helpserv_unregister(hs, "Unregistered by %s", "HelpServ %s (%s) unregistered by %s.", user->nick);
+    helpserv_unregister(hs, "Unregistered by %s", "HSMSG_BOT_UNREGISTERED", user->nick);
     return from_opserv;
 }
 
+static HELPSERV_FUNC(cmd_suspend) {
+    char reason[MAXLEN];
+    struct helpserv_bot *hsb;
+    time_t expiry, duration;
+
+    REQUIRE_PARMS(3);
+
+    if(!strcmp(argv[2], "0"))
+        expiry = 0;
+    else if((duration = ParseInterval(argv[2])))
+        expiry = now + duration;
+    else
+    {
+        helpserv_notice(user, "MSG_INVALID_DURATION", argv[1]);
+        return 0;
+    }
+
+    hsb = dict_find(helpserv_bots_dict, argv[1], NULL);
+    if (!hsb) {
+        helpserv_notice(user, "HSMSG_BOT_NON_EXIST", argv[1]);
+        return 0;
+    }
+
+    unsplit_string(argv + 3, argc - 3, reason);
+
+    hsb->suspended = 1;
+    hsb->issued = now;
+    hsb->expiry = expiry;
+    hsb->reason = strdup(reason);
+    hsb->suspender = strdup(user->handle_info->handle);
+
+    if(hsb->expiry)
+        timeq_add(hsb->expiry, helpserv_expire_suspension, hsb);
+
+    DelChannelUser(hsb->helpserv, hsb->helpchan, hsb->reason, 0);
+    helpserv_notice(user, "HSMSG_SUSPENDED", hsb->helpchan->name);
+    global_message_args(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, "HSMSG_SUSPENDED_BY",
+                        hsb->helpchan->name, hsb->suspender);
+
+   return 0;
+}
+
+static HELPSERV_FUNC(cmd_unsuspend) {
+    struct helpserv_bot *hsb;
+
+    hsb = dict_find(helpserv_bots_dict, argv[1], NULL);
+    if (!hsb) {
+        helpserv_notice(user, "HSMSG_BOT_NON_EXIST", argv[1]);
+        return 0;
+    }
+
+    if(!hsb->suspended)
+    {
+        helpserv_notice(user, "HSMSG_NOT_SUSPENDED", hsb->helpchan->name);
+        return 0;
+    }
+
+    /* Expire the suspension and join ChanServ to the channel. */
+    timeq_del(hsb->expiry, helpserv_expire_suspension, hsb, 0);
+    helpserv_expire_suspension(hsb);
+    helpserv_notice(user, "HSMSG_UNSUSPENDED", hsb->helpchan->name);
+    global_message_args(MESSAGE_RECIPIENT_OPERS|MESSAGE_RECIPIENT_HELPERS, "HSMSG_UNSUSPENDED_BY",
+                        hsb->helpchan->name, user->handle_info->handle);
+    return 1;
+}
+
 static HELPSERV_FUNC(cmd_expire) {
     struct helpserv_botlist victims;
     struct helpserv_bot *bot;
@@ -2748,7 +3105,7 @@ static HELPSERV_FUNC(cmd_expire) {
         next = iter_next(it);
         if ((unsigned int)(now - bot->last_active) < helpserv_conf.expire_age)
             continue;
-        helpserv_unregister(bot, "Registration expired due to inactivity", "HelpServ %s (%s) expired at request of %s.", user->nick);
+        helpserv_unregister(bot, "Registration expired due to inactivity", "HSMSG_BOT_EXPIRED", user->nick);
         count++;
     }
     helpserv_notice(user, "HSMSG_EXPIRATION_DONE", count);
@@ -3166,6 +3523,14 @@ static HELPSERV_OPTION(opt_auto_devoice) {
     OPTION_BINARY(hs->auto_devoice, "HSMSG_SET_AUTODEVOICE");
 }
 
+static HELPSERV_OPTION(opt_join_total) {
+    OPTION_BINARY(hs->join_total, "HSMSG_SET_JOINTOTAL");
+}
+
+static HELPSERV_OPTION(opt_alert_new) {
+    OPTION_BINARY(hs->alert_new, "HSMSG_SET_ALERTNEW");
+}
+
 static HELPSERV_FUNC(cmd_set) {
     helpserv_option_func_t *opt;
 
@@ -3179,7 +3544,7 @@ static HELPSERV_FUNC(cmd_set) {
             opt_empty_interval, opt_stale_delay, opt_request_persistence,
             opt_helper_persistence, opt_notification, opt_id_wrap,
             opt_req_maxlen, opt_privmsg_only, opt_req_on_join, opt_auto_voice,
-            opt_auto_devoice
+            opt_auto_devoice, opt_join_total, opt_alert_new
         };
 
         helpserv_notice(user, "HSMSG_QUEUE_OPTIONS");
@@ -3209,6 +3574,56 @@ static HELPSERV_FUNC(cmd_set) {
     return opt(user, hs, from_opserv, argc-2, argv+2);
 }
 
+const char *
+get_helpserv_id(const char *nick, struct userNode *user) {
+    char id[MAX_LINE_SIZE];
+    int tid = 0;
+    struct helpserv_bot *hs;
+    struct userNode *target;
+    dict_iterator_t it;
+    struct helpserv_request *req=NULL, *newest=NULL;
+    struct helpserv_reqlist *reqlist;
+    unsigned int i;
+
+    if (!IsChannelName(nick))
+        target = GetUserN(nick);
+    else {
+        sprintf(id, "%s", "$i");
+        return strdup(id);
+    }
+
+    for (it=dict_first(helpserv_bots_dict); it; it=iter_next(it)) {
+        hs = iter_data(it);
+        if (strcasecmp(user->nick, hs->helpserv->nick))
+            continue;
+
+        if ((reqlist = dict_find(helpserv_reqs_bynick_dict, target->nick, NULL))) {
+            for (i=0; i < reqlist->used; i++) {
+                req = reqlist->list[i];
+                if (req->hs != hs)
+                    continue;
+                if (!newest || (newest->opened < req->opened))
+                    newest = req;
+            }
+
+            /* If nothing was found, this will set req to NULL */
+            req = newest;
+        }
+
+        if (req) {
+            tid = req->id;
+            break;
+        }
+    }
+
+    if (tid)
+        sprintf(id, "ID#%lu", req->id);
+    else
+        sprintf(id, "%s", "$i");
+
+    return strdup(id);
+}
+
 static int user_write_helper(const char *key, void *data, void *extra) {
     struct helpserv_user *hs_user = data;
     struct saxdb_context *ctx = extra;
@@ -3501,8 +3916,18 @@ helpserv_bot_write(const char *key, void *data, void *extra) {
     saxdb_write_int(ctx, KEY_REQ_ON_JOIN, hs->req_on_join);
     saxdb_write_int(ctx, KEY_AUTO_VOICE, hs->auto_voice);
     saxdb_write_int(ctx, KEY_AUTO_DEVOICE, hs->auto_devoice);
+    saxdb_write_int(ctx, KEY_JOIN_TOTAL, hs->join_total);
+    saxdb_write_int(ctx, KEY_ALERT_NEW, hs->alert_new);
     saxdb_write_int(ctx, KEY_LAST_ACTIVE, hs->last_active);
 
+    if (hs->suspended) {
+        saxdb_write_int(ctx, KEY_SUSPENDED, hs->suspended);
+        saxdb_write_int(ctx, KEY_EXPIRY, hs->expiry);
+        saxdb_write_int(ctx, KEY_ISSUED, hs->issued);
+        saxdb_write_string(ctx, KEY_SUSPENDER, hs->suspender);
+        saxdb_write_string(ctx, KEY_REASON, hs->reason);
+    }
+
     /* End bot record */
     saxdb_end_record(ctx);
     return 0;
@@ -3609,15 +4034,35 @@ static int helpserv_bot_read(const char *key, void *data, UNUSED_ARG(void *extra
     hs->auto_voice = str ? enabled_string(str) : 0;
     str = database_get_data(GET_RECORD_OBJECT(br), KEY_AUTO_DEVOICE, RECDB_QSTRING);
     hs->auto_devoice = str ? enabled_string(str) : 0;
+    str = database_get_data(GET_RECORD_OBJECT(br), KEY_JOIN_TOTAL, RECDB_QSTRING);
+    hs->join_total = str ? enabled_string(str) : 0;
+    str = database_get_data(GET_RECORD_OBJECT(br), KEY_ALERT_NEW, RECDB_QSTRING);
+    hs->alert_new = str ? enabled_string(str) : 0;
     str = database_get_data(GET_RECORD_OBJECT(br), KEY_LAST_ACTIVE, RECDB_QSTRING);
     hs->last_active = str ? atoi(str) : now;
 
+    str = database_get_data(GET_RECORD_OBJECT(br), KEY_SUSPENDED, RECDB_QSTRING);
+    hs->suspended = str ? atoi(str) : 0;
+    if (hs->suspended) {
+        str = database_get_data(GET_RECORD_OBJECT(br), KEY_EXPIRY, RECDB_QSTRING);
+        hs->expiry = str ? atoi(str) : 0;
+        str = database_get_data(GET_RECORD_OBJECT(br), KEY_ISSUED, RECDB_QSTRING);
+        hs->issued = str ? atoi(str) : 0;
+        str = database_get_data(GET_RECORD_OBJECT(br), KEY_SUSPENDER, RECDB_QSTRING);
+        hs->suspender = str ? str : 0;
+        str = database_get_data(GET_RECORD_OBJECT(br), KEY_REASON, RECDB_QSTRING);
+        hs->reason = str ? str : 0;
+    }
+
     dict_foreach(users, user_read_helper, hs);
 
     requests = database_get_data(GET_RECORD_OBJECT(br), KEY_REQUESTS, RECDB_OBJECT);
     if (requests)
         dict_foreach(requests, request_read_helper, hs);
 
+    if(hs->suspended && hs->expiry)
+        timeq_add(hs->expiry, helpserv_expire_suspension, hs);
+
     return 0;
 }
 
@@ -3648,7 +4093,7 @@ static void helpserv_conf_read(void) {
     helpserv_conf.db_backup_frequency = str ? ParseInterval(str) : 7200;
 
     str = database_get_data(conf_node, "description", RECDB_QSTRING);
-    helpserv_conf.description = str;
+    helpserv_conf.description = str ? str : "Help Queue Manager";
 
     str = database_get_data(conf_node, "reqlogfile", RECDB_QSTRING);
     if (str && strlen(str))
@@ -3672,10 +4117,10 @@ static void helpserv_conf_read(void) {
 }
 
 static struct helpserv_cmd *
-helpserv_define_func(const char *name, helpserv_func_t *func, enum helpserv_level access, long flags) {
+helpserv_define_func(const char *name, helpserv_func_t *func, enum helpserv_level level, long flags) {
     struct helpserv_cmd *cmd = calloc(1, sizeof(struct helpserv_cmd));
 
-    cmd->access = access;
+    cmd->access = level;
     cmd->weight = 1.0;
     cmd->func = func;
     cmd->flags = flags;
@@ -3685,7 +4130,7 @@ helpserv_define_func(const char *name, helpserv_func_t *func, enum helpserv_leve
 }
 
 /* Drop requests that persist until part when a user leaves the chan */
-static void handle_part(struct modeNode *mn, UNUSED_ARG(const char *reason)) {
+static void handle_part(struct modeNode *mn, UNUSED_ARG(const char *reason), UNUSED_ARG(void *extra)) {
     struct helpserv_botlist *botlist;
     struct helpserv_userlist *userlist;
     const int from_opserv = 0; /* for helpserv_notice */
@@ -3744,9 +4189,9 @@ static void handle_part(struct modeNode *mn, UNUSED_ARG(const char *reason)) {
 
                 if ((hs->persist_types[PERSIST_T_HELPER] == PERSIST_PART)
                     && (req->helper == hs_user)) {
-                    char reason[CHANNELLEN + 8];
-                    sprintf(reason, "parted %s", mn->channel->name);
-                    helpserv_page_helper_gone(hs, req, reason);
+                    char our_reason[CHANNELLEN + 8];
+                    sprintf(our_reason, "parted %s", mn->channel->name);
+                    helpserv_page_helper_gone(hs, req, our_reason);
                 }
             }
 
@@ -3781,7 +4226,7 @@ static void handle_part(struct modeNode *mn, UNUSED_ARG(const char *reason)) {
  *
  * Unassign requests where req->helper persists until the helper parts or
  * quits. */
-static void handle_quit(struct userNode *user, UNUSED_ARG(struct userNode *killer), UNUSED_ARG(const char *why)) {
+static void handle_quit(struct userNode *user, UNUSED_ARG(struct userNode *killer), UNUSED_ARG(const char *why), UNUSED_ARG(void *extra)) {
     struct helpserv_reqlist *reqlist;
     struct helpserv_userlist *userlist;
     unsigned int i, n;
@@ -3929,7 +4374,7 @@ static void associate_requests_bychan(struct chanNode *chan, struct userNode *us
 /* Greet users upon joining a helpserv channel (if greeting is set) and set
  * req->user to the user joining for all requests owned by the user's handle
  * (if any) with a req->user == NULL */
-static int handle_join(struct modeNode *mNode) {
+static int handle_join(struct modeNode *mNode, UNUSED_ARG(void *extra)) {
     struct userNode *user = mNode->user;
     struct chanNode *chan = mNode->channel;
     struct helpserv_botlist *botlist;
@@ -3952,6 +4397,18 @@ static int handle_join(struct modeNode *mNode) {
                 if (!hs_user->join_time)
                     hs_user->join_time = now;
 
+                if (hs->join_total) {
+                    if (hs_user->level >= HlHelper) {
+                        unsigned int total;
+                        struct helpserv_request *req;
+
+                        for (req = hs->unhandled, total=0; req; req = req->next_unhandled, total++) ;
+
+                        if (total > 0)
+                            helpserv_notice(user, "HSMSG_REQUESTS_OPEN", total, hs->helpserv->nick, hs->helpserv->nick);
+                    }
+                }
+
                 if (hs_user->level >= HlHelper && hs->intervals[INTERVAL_EMPTY_INTERVAL] && hs->helpchan_empty) {
                     hs->helpchan_empty = 0;
                     timeq_del(0, run_empty_interval, hs, TIMEQ_IGNORE_WHEN);
@@ -3988,7 +4445,7 @@ static int handle_join(struct modeNode *mNode) {
 }
 
 /* Update helpserv_reqs_bynick_dict upon nick change */
-static void handle_nickchange(struct userNode *user, const char *old_nick) {
+static void handle_nickchange(struct userNode *user, const char *old_nick, UNUSED_ARG(void *extra)) {
     struct helpserv_reqlist *reqlist;
     unsigned int i;
 
@@ -4008,7 +4465,7 @@ static void handle_nickchange(struct userNode *user, const char *old_nick) {
 }
 
 /* Also update helpserv_reqs_byhand_dict upon handle rename */
-static void handle_nickserv_rename(struct handle_info *handle, const char *old_handle) {
+static void handle_nickserv_rename(struct handle_info *handle, const char *old_handle, UNUSED_ARG(void *extra)) {
     struct helpserv_reqlist *reqlist;
     struct helpserv_userlist *userlist;
     unsigned int i;
@@ -4061,7 +4518,7 @@ static void handle_nickserv_rename(struct handle_info *handle, const char *old_h
  *      have req->user set).
  * - In either of the above cases, if a user is on a bot's userlist and has
  *   requests assigned to them, it will give them a list. */
-static void handle_nickserv_auth(struct userNode *user, struct handle_info *old_handle) {
+static void handle_nickserv_auth(struct userNode *user, struct handle_info *old_handle, UNUSED_ARG(void* extra)) {
     struct helpserv_reqlist *reqlist, *dellist=NULL, *hand_reqlist, *oldhand_reqlist;
     struct helpserv_userlist *userlist;
     unsigned int i, j;
@@ -4228,7 +4685,7 @@ static void handle_nickserv_auth(struct userNode *user, struct handle_info *old_
  *
  * Also, remove the user from all bots that it has access in.
  * helpserv_del_user() will take care of unassigning the requests. */
-static void handle_nickserv_unreg(struct userNode *user, struct handle_info *handle) {
+static void handle_nickserv_unreg(struct userNode *user, struct handle_info *handle, UNUSED_ARG(void *extra)) {
     struct helpserv_reqlist *hand_reqlist;
     struct helpserv_userlist *userlist;
     unsigned int i, n;
@@ -4311,7 +4768,7 @@ static void handle_nickserv_unreg(struct userNode *user, struct handle_info *han
     dict_remove(helpserv_reqs_byhand_dict, handle->handle);
 }
 
-static void handle_nickserv_merge(struct userNode *user, struct handle_info *handle_to, struct handle_info *handle_from) {
+static void handle_nickserv_merge(struct userNode *user, struct handle_info *handle_to, struct handle_info *handle_from, UNUSED_ARG(void *extra)) {
     struct helpserv_reqlist *reqlist_from, *reqlist_to;
     unsigned int i;
 
@@ -4343,7 +4800,7 @@ static void handle_nickserv_merge(struct userNode *user, struct handle_info *han
     }
 }
 
-static void handle_nickserv_allowauth(struct userNode *user, struct userNode *target, struct handle_info *handle) {
+static void handle_nickserv_allowauth(struct userNode *user, struct userNode *target, struct handle_info *handle, UNUSED_ARG(void *extra)) {
     struct helpserv_reqlist *reqlist;
     unsigned int i;
 
@@ -4362,7 +4819,7 @@ static void handle_nickserv_allowauth(struct userNode *user, struct userNode *ta
     }
 }
 
-static void handle_nickserv_failpw(struct userNode *user, struct handle_info *handle) {
+static void handle_nickserv_failpw(struct userNode *user, struct handle_info *handle, UNUSED_ARG(void *extra)) {
     struct helpserv_reqlist *reqlist;
     unsigned int i;
 
@@ -4440,10 +4897,10 @@ helpserv_define_option(const char *name, helpserv_option_func_t *func) {
     dict_insert(helpserv_option_dict, name, func);
 }
 
-static void helpserv_db_cleanup(void) {
+static void helpserv_db_cleanup(UNUSED_ARG(void *extra)) {
     shutting_down=1;
-    unreg_part_func(handle_part);
-    unreg_del_user_func(handle_quit);
+    unreg_part_func(handle_part, NULL);
+    unreg_del_user_func(handle_quit, NULL);
     close_helpfile(helpserv_helpfile);
     dict_delete(helpserv_func_dict);
     dict_delete(helpserv_option_dict);
@@ -4495,6 +4952,8 @@ int helpserv_init() {
     helpserv_define_func("MOVE", cmd_move, HlOper, CMD_FROM_OPSERV_ONLY|CMD_NEED_BOT);
     helpserv_define_func("BOTS", cmd_bots, HlOper, CMD_FROM_OPSERV_ONLY|CMD_IGNORE_EVENT);
     helpserv_define_func("EXPIRE", cmd_expire, HlOper, CMD_FROM_OPSERV_ONLY);
+    helpserv_define_func("SUSPEND", cmd_suspend, HlOper, CMD_FROM_OPSERV_ONLY);
+    helpserv_define_func("UNSUSPEND", cmd_unsuspend, HlOper, CMD_FROM_OPSERV_ONLY);
     helpserv_define_func("WEEKSTART", cmd_weekstart, HlTrial, CMD_NEED_BOT);
 
     helpserv_option_dict = dict_new();
@@ -4523,8 +4982,14 @@ int helpserv_init() {
     helpserv_define_option("REQONJOIN", opt_req_on_join);
     helpserv_define_option("AUTOVOICE", opt_auto_voice);
     helpserv_define_option("AUTODEVOICE", opt_auto_devoice);
+    helpserv_define_option("JOINTOTAL", opt_join_total);
+    helpserv_define_option("ALERTNEW", opt_alert_new);
 
     helpserv_usercmd_dict = dict_new();
+    dict_insert(helpserv_usercmd_dict, "CLOSEREQ", usercmd_close);
+    dict_insert(helpserv_usercmd_dict, "HELP", usercmd_help);
+    dict_insert(helpserv_usercmd_dict, "HELPER", usercmd_helper);
+    dict_insert(helpserv_usercmd_dict, "SHOWREQ", usercmd_show);
     dict_insert(helpserv_usercmd_dict, "WAIT", usercmd_wait);
 
     helpserv_bots_dict = dict_new();
@@ -4553,19 +5018,19 @@ int helpserv_init() {
     }
     timeq_add(helpserv_next_stats(now), helpserv_timed_run_stats, NULL);
 
-    reg_join_func(handle_join);
-    reg_part_func(handle_part); /* also deals with kick */
-    reg_nick_change_func(handle_nickchange);
-    reg_del_user_func(handle_quit);
+    reg_join_func(handle_join, NULL);
+    reg_part_func(handle_part, NULL); /* also deals with kick */
+    reg_nick_change_func(handle_nickchange, NULL);
+    reg_del_user_func(handle_quit, NULL);
 
-    reg_auth_func(handle_nickserv_auth);
-    reg_handle_rename_func(handle_nickserv_rename);
-    reg_unreg_func(handle_nickserv_unreg);
-    reg_allowauth_func(handle_nickserv_allowauth);
-    reg_failpw_func(handle_nickserv_failpw);
-    reg_handle_merge_func(handle_nickserv_merge);
+    reg_auth_func(handle_nickserv_auth, NULL);
+    reg_handle_rename_func(handle_nickserv_rename, NULL);
+    reg_unreg_func(handle_nickserv_unreg, NULL);
+    reg_allowauth_func(handle_nickserv_allowauth, NULL);
+    reg_failpw_func(handle_nickserv_failpw, NULL);
+    reg_handle_merge_func(handle_nickserv_merge, NULL);
 
-    reg_exit_func(helpserv_db_cleanup);
+    reg_exit_func(helpserv_db_cleanup, NULL);
 
     helpserv_module = module_register("helpserv", HS_LOG, HELPSERV_HELPFILE_NAME, helpserv_expand_variable);
     modcmd_register(helpserv_module, "helpserv", cmd_helpserv, 1, MODCMD_REQUIRE_AUTHED|MODCMD_NO_LOG|MODCMD_NO_DEFAULT_BIND, "level", "800", NULL);