X-Git-Url: https://jfr.im/git/irc/evilnet/x3.git/blobdiff_plain/ceafd592b7c084e0857902f0dd4132db97b2abf7..8536ac6b661fa261bad7de981045401f514fb6b7:/src/mod-helpserv.c diff --git a/src/mod-helpserv.c b/src/mod-helpserv.c index 5007045..5726a8e 100644 --- a/src/mod-helpserv.c +++ b/src/mod-helpserv.c @@ -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, @@ -18,21 +18,13 @@ * 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 = ""; + else if (!topic && !from_user) + topic = ""; + + /* 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; } @@ -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; } @@ -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);