fixed multiple compiler warnings
[srvx.git] / src / mod-helpserv.c
1 /* mod-helpserv.c - Support Helper assistant service
2  * Copyright 2002-2003, 2006 srvx Development Team
3  *
4  * This file is part of srvx.
5  *
6  * srvx is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with srvx; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
19  */
20
21 /* Wishlist for helpserv.c
22  * - Make HelpServ send unassigned request count to helpers as they join the
23  *   channel
24  * - FAQ responses
25  * - Get cmd_statsreport to sort by time and show requests closed
26  * - .. then make statsreport show weighted combination of time and requests closed :)
27  * - Something for users to use a subset of commands... like closing their
28  *   own request(s), viewing what they've sent so far, and finding their
29  *   helper's nick.
30  * - It would be nice to have some variable expansions for the custom
31  *   request open/close/etc messages. ${request/id}, etc
32  * - Allow manipulation of the persist types on a per-request basis... so
33  *   if it's normally set to close requests after a user quits, but there
34  *   is a long-term issue, then that single request can remain
35  * - Bot suspension
36  */
37
38 #include "conf.h"
39 #include "global.h"
40 #include "modcmd.h"
41 #include "nickserv.h"
42 #include "opserv.h"
43 #include "saxdb.h"
44 #include "timeq.h"
45
46 #define HELPSERV_CONF_NAME "modules/helpserv"
47 #define HELPSERV_HELPFILE_NAME "mod-helpserv.help"
48 const char *helpserv_module_deps[] = { NULL };
49
50 /* db entries */
51 #define KEY_BOTS "bots"
52 #define KEY_LAST_STATS_UPDATE "last_stats_update"
53 #define KEY_NICK "nick"
54 #define KEY_DB_BADCHANS "badchans"
55 #define KEY_HELP_CHANNEL "help_channel"
56 #define KEY_PUBLIC_CHANNEL "public_channel"
57 #define KEY_PAGE_DEST "page_dest"
58 #define KEY_CMDWORD "cmdword"
59 #define KEY_PERSIST_LENGTH "persist_length"
60 #define KEY_REGISTERED "registered"
61 #define KEY_REGISTRAR "registrar"
62 #define KEY_IDWRAP "id_wrap"
63 #define KEY_REQ_MAXLEN "req_maxlen"
64 #define KEY_LAST_REQUESTID "last_requestid"
65 #define KEY_HELPERS "users"
66 #define KEY_HELPER_LEVEL "level"
67 #define KEY_HELPER_HELPMODE "helpmode"
68 #define KEY_HELPER_WEEKSTART "weekstart"
69 #define KEY_HELPER_STATS "stats"
70 #define KEY_HELPER_STATS_TIME "time"
71 #define KEY_HELPER_STATS_PICKUP "picked_up"
72 #define KEY_HELPER_STATS_CLOSE "closed"
73 #define KEY_HELPER_STATS_REASSIGNFROM "reassign_from"
74 #define KEY_HELPER_STATS_REASSIGNTO "reassign_to"
75 #define KEY_REQUESTS "requests"
76 #define KEY_REQUEST_HELPER "helper"
77 #define KEY_REQUEST_ASSIGNED "assigned"
78 #define KEY_REQUEST_HANDLE "handle"
79 #define KEY_REQUEST_TEXT "text"
80 #define KEY_REQUEST_OPENED "opened"
81 #define KEY_REQUEST_NICK "nick"
82 #define KEY_REQUEST_USERHOST "userhost"
83 #define KEY_REQUEST_CLOSED "closed"
84 #define KEY_REQUEST_CLOSEREASON "closereason"
85 #define KEY_NOTIFICATION "notification"
86 #define KEY_PRIVMSG_ONLY "privmsg_only"
87 #define KEY_REQ_ON_JOIN "req_on_join"
88 #define KEY_AUTO_VOICE "auto_voice"
89 #define KEY_AUTO_JOIN "auto_join"
90 #define KEY_AUTO_DEVOICE "auto_devoice"
91 #define KEY_LAST_ACTIVE "last_active"
92
93 /* General */
94 #define HSFMT_TIME               "%a, %d %b %Y %H:%M:%S %Z"
95 static const struct message_entry msgtab[] = {
96     { "HSMSG_READHELP_SUCCESS", "Read HelpServ help database in %lu.%03lu seconds." },
97     { "HSMSG_INVALID_BOT", "This command requires a valid HelpServ bot name." },
98     { "HSMSG_ILLEGAL_CHANNEL", "$b%s$b is an illegal channel; cannot use it." },
99     { "HSMSG_INTERNAL_COMMAND", "$b%s$b appears to be an internal HelpServ command, sorry." },
100     { "HSMSG_NOT_IN_USERLIST", "%s lacks access to $b%s$b." },
101     { "HSMSG_LEVEL_TOO_LOW", "You lack access to this command." },
102     { "HSMSG_OPER_CMD", "This command can only be executed via $O." },
103     { "HSMSG_WTF_WHO_ARE_YOU", "$bUnable to find you on the %s userlist.$b This is a bug. Please report it." },
104     { "HSMSG_NO_USE_OPSERV", "This command cannot be used via $O. If you really need to use it, add yourself to the userlist." },
105     { "HSMSG_OPSERV_NEED_USER", "To use this command via $O, you must supply a user to target." },
106     { "HSMSG_PAGE_REQUEST", "Page from $b%s$b: $b%s$b" },
107     { "HSMSG_BAD_REQ_TYPE", "I don't know how to list $b%s$b requests." },
108
109 /* Greetings */
110     { "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." },
111     { "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." },
112
113 /* User Management */
114     { "HSMSG_CANNOT_ADD", "You do not have permission to add users." },
115     { "HSMSG_CANNOT_DEL", "You do not have permission to delete users." },
116     { "HSMSG_CANNOT_CLVL", "You do not have permission to modify users' access." },
117     { "HSMSG_NO_SELF_CLVL", "You cannot change your own access." },
118     { "HSMSG_NO_BUMP_ACCESS", "You cannot give users the same or more access than yourself." },
119     { "HSMSG_NO_TRANSFER_SELF", "You cannot give ownership to your own account." },
120     { "HSMSG_ADDED_USER", "Added new $b%s$b %s to the user list." },
121     { "HSMSG_DELETED_USER", "Deleted $b%s$b %s from the user list." },
122     { "HSMSG_USER_EXISTS", "$b%s$b is already on the user list." },
123     { "HSMSG_INVALID_ACCESS", "$b%s$b is an invalid access level." },
124     { "HSMSG_CHANGED_ACCESS", "%s now has $b%s$b access." },
125     { "HSMSG_EXPIRATION_DONE", "%d eligible HelpServ bots have retired." },
126     { "HSMSG_BAD_WEEKDAY", "I do not know which day of the week $b%s$b is." },
127     { "HSMSG_WEEK_STARTS", "$b%s$b's weeks start on $b%s$b." },
128     { "HSMSG_MODSTATS_BAD_FIELD", "The specified field does not exist." },
129     { "HSMSG_MODSTATS_BAD_WEEK", "The specified week is invalid." },
130     { "HSMSG_MODSTATS_NEGATIVE", "This modification would result in a negative value." },
131     { "HSMSG_MODSTATS_SUCCESS", "$b%s$b's stats have been modified successfully." },
132
133 /* Registration */
134     { "HSMSG_ILLEGAL_NICK", "$b%s$b is an illegal nick; cannot use it." },
135     { "HSMSG_NICK_EXISTS", "The nick %s is in use by someone else." },
136     { "HSMSG_REG_SUCCESS", "%s now has ownership of bot %s." },
137     { "HSMSG_NEED_UNREG_CONFIRM", "To unregister this bot, you must /msg $S unregister CONFIRM" },
138     { "HSMSG_ERROR_ADDING_SERVICE", "Error creating new user $b%s$b." },
139
140 /* Rename */
141     { "HSMSG_RENAMED", "%s has been renamed to $b%s$b." },
142     { "HSMSG_MOVE_SAME_CHANNEL", "You cannot move %s to the same channel it is on." },
143     { "HSMSG_INVALID_MOVE", "$b%s$b is not a valid nick or channel name." },
144     { "HSMSG_NEED_GIVEOWNERSHIP_CONFIRM", "To transfer ownership of this bot, you must /msg $S giveownership newowner CONFIRM" },
145     { "HSMSG_MULTIPLE_OWNERS", "There is more than one owner of %s; please use other commands to change ownership." },
146     { "HSMSG_NO_TRANSFER_SELF", "You cannot give ownership to your own account." },
147     { "HSMSG_OWNERSHIP_GIVEN", "Ownership of $b%s$b has been transferred to account $b%s$b." },
148
149 /* Queue settings */
150     { "HSMSG_INVALID_OPTION", "$b%s$b is not a valid option." },
151     { "HSMSG_QUEUE_OPTIONS", "HelpServ Queue Options:" },
152     { "HSMSG_SET_COMMAND_TYPE",   "$bPageType        $b %s" },
153     { "HSMSG_SET_ALERT_TYPE",     "$bAlertPageType   $b %s" },
154     { "HSMSG_SET_STATUS_TYPE",    "$bStatusPageType  $b %s" },
155     { "HSMSG_SET_COMMAND_TARGET", "$bPageTarget      $b %s" },
156     { "HSMSG_SET_ALERT_TARGET",   "$bAlertPageTarget $b %s" },
157     { "HSMSG_SET_STATUS_TARGET",  "$bStatusPageTarget$b %s" },
158     { "HSMSG_SET_GREETING",       "$bGreeting        $b %s" },
159     { "HSMSG_SET_REQOPENED",      "$bReqOpened       $b %s" },
160     { "HSMSG_SET_REQASSIGNED",    "$bReqAssigned     $b %s" },
161     { "HSMSG_SET_REQCLOSED",      "$bReqClosed       $b %s" },
162     { "HSMSG_SET_IDLEDELAY",      "$bIdleDelay       $b %s" },
163     { "HSMSG_SET_WHINEDELAY",     "$bWhineDelay      $b %s" },
164     { "HSMSG_SET_WHINEINTERVAL",  "$bWhineInterval   $b %s" },
165     { "HSMSG_SET_EMPTYINTERVAL",  "$bEmptyInterval   $b %s" },
166     { "HSMSG_SET_STALEDELAY",     "$bStaleDelay      $b %s" },
167     { "HSMSG_SET_REQPERSIST",     "$bReqPersist      $b %s" },
168     { "HSMSG_SET_HELPERPERSIST",  "$bHelperPersist   $b %s" },
169     { "HSMSG_SET_NOTIFICATION",   "$bNotification    $b %s" },
170     { "HSMSG_SET_IDWRAP",         "$bIDWrap          $b %d" },
171     { "HSMSG_SET_REQMAXLEN",      "$bReqMaxLen       $b %d" },
172     { "HSMSG_SET_PRIVMSGONLY",    "$bPrivmsgOnly     $b %s" },
173     { "HSMSG_SET_REQONJOIN",      "$bReqOnJoin       $b %s" },
174     { "HSMSG_SET_AUTOVOICE",      "$bAutoVoice       $b %s" },
175     { "HSMSG_SET_AUTOJOIN",       "$bAutoJoin        $b %s" },
176     { "HSMSG_SET_AUTODEVOICE",    "$bAutoDevoice     $b %s" },
177                                 { "HSMSG_SET_PUBLICCHAN",     "$bPublicChan      $b %s" },
178     { "HSMSG_PAGE_NOTICE", "notice" },
179     { "HSMSG_PAGE_PRIVMSG", "privmsg" },
180     { "HSMSG_PAGE_ONOTICE", "onotice" },
181     { "HSMSG_LENGTH_PART", "part" },
182     { "HSMSG_LENGTH_QUIT", "quit" },
183     { "HSMSG_LENGTH_CLOSE", "close" },
184     { "HSMSG_NOTIFY_DROP", "ReqDrop" },
185     { "HSMSG_NOTIFY_USERCHANGES", "UserChanges" },
186     { "HSMSG_NOTIFY_ACCOUNTCHANGES", "AccountChanges" },
187     { "HSMSG_INVALID_INTERVAL", "Sorry, %s must be at least %s." },
188     { "HSMSG_0_DISABLED", "0 (Disabled)" },
189     { "HSMSG_NEED_MANAGER", "Only managers or higher can do this." },
190     { "HSMSG_SET_NEED_OPER", "This option can only be set by an oper." },
191
192 /* Requests */
193     { "HSMSG_REQ_INVALID", "$b%s$b is not a valid request ID, or there are no requests for that nick or account." },
194     { "HSMSG_REQ_NOT_YOURS_ASSIGNED_TO", "Request $b%lu$b is not assigned to you; it is currently assigned to %s." },
195     { "HSMSG_REQ_NOT_YOURS_UNASSIGNED", "Request $b%lu$b is not assigned to you; it is currently unassigned." },
196     { "HSMSG_REQ_FOUNDMANY", "The user you entered had multiple requests. The oldest one is being used." },
197     { "HSMSG_REQ_CLOSED", "Request $b%lu$b has been closed." },
198     { "HSMSG_REQ_NO_UNASSIGNED", "There are no unassigned requests." },
199     { "HSMSG_USERCMD_NO_REQUEST", "You must have an open request to use a user command." },
200     { "HSMSG_USERCMD_UNKNOWN", "I do not know the user command $b%s$b." },
201     { "HSMSG_REQ_YOU_NOT_IN_HELPCHAN_OPEN", "You cannot open this request as you are not in %s." },
202     { "HSMSG_REQ_YOU_NOT_IN_HELPCHAN", "You cannot be assigned this request as you are not in %s." },
203     { "HSMSG_REQ_HIM_NOT_IN_HELPCHAN", "%s cannot be assigned this request as they are not in %s." },
204     { "HSMSG_REQ_SELF_NOT_IN_OVERRIDE", "You are being assigned this request even though you are not in %s, because the restriction was overridden (you are a manager or owner). If you join and then part, this request will be marked unassigned." },
205     { "HSMSG_REQ_YOU_NOT_IN_OVERRIDE", "Note: You are being assigned this request even though you are not in %s, because the restriction was overridden by a manager or owner. If you join and then part, this request will be marked unassigned." },
206     { "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." },
207     { "HSMSG_REQ_ASSIGNED_YOU", "You have been assigned request ID#%lu:" },
208     { "HSMSG_REQ_REASSIGNED", "You have been assigned request ID#%lu (reassigned from %s):" },
209     { "HSMSG_REQ_INFO_1", "Request ID#%lu:" },
210     { "HSMSG_REQ_INFO_2a", " - Nick %s / Account %s" },
211     { "HSMSG_REQ_INFO_2b", " - Nick %s / Not authed" },
212     { "HSMSG_REQ_INFO_2c", " - Online somewhere / Account %s" },
213     { "HSMSG_REQ_INFO_2d", " - Not online / Account %s" },
214     { "HSMSG_REQ_INFO_2e", " - Not online / No account" },
215     { "HSMSG_REQ_INFO_3", " - Opened at %s (%s ago)" },
216     { "HSMSG_REQ_INFO_4", " - Message:" },
217     { "HSMSG_REQ_INFO_MESSAGE", "   %s" },
218     { "HSMSG_REQ_ASSIGNED", "Your helper for request ID#%lu is %s (Current nick: %s)" },
219     { "HSMSG_REQ_ASSIGNED_AGAIN", "Your helper for request ID#%lu has been changed to %s (Current nick: %s)" },
220     { "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." },
221     { "HSMSG_REQ_NEW", "Your message has been recorded and assigned request ID#%lu. A helper should contact you shortly." },
222     { "HSMSG_REQ_NEWONJOIN", "Welcome to %s. You have been assigned request ID#%lu. A helper should contact you shortly." },
223     { "HSMSG_REQ_UNHANDLED_TIME", "The oldest unhandled request has been waiting for %s." },
224     { "HSMSG_REQ_NO_UNHANDLED", "There are no other unhandled requests." },
225     { "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." },
226     { "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." },
227     { "HSMSG_REQ_PERSIST_HANDLE", "Everything you tell me until you are helped will be recorded." },
228     { "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." },
229     { "HSMSQ_REQ_TEXT_ADDED", "Message from $b%s:$b %s" },
230     { "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." },
231
232 /* Messages that are inserted into request text */
233     { "HSMSG_REQMSG_NOTE_ADDED", "Your note for request ID#%lu has been recorded." },
234
235 /* Automatically generated page messages */
236     { "HSMSG_PAGE_NEW_REQUEST_AUTHED", "New request (ID#%lu) from $b%s$b (Account %s)" },
237     { "HSMSG_PAGE_NEW_REQUEST_UNAUTHED", "New request (ID#%lu) from $b%s$b (Not logged in)" },
238     { "HSMSG_PAGE_UPD_REQUEST_AUTHED", "Request ID#%lu has been updated by $b%s$b (Account %s). Request was initially opened at %s, and was last updated %s ago." },
239     { "HSMSG_PAGE_UPD_REQUEST_NOT_AUTHED", "Request ID#%lu has been updated by $b%s$b (not authed). Request was initially opened at %s, and was last updated %s ago." },
240     { "HSMSG_PAGE_CLOSE_REQUEST_1", "Request ID#%lu from $b%s$b (Account %s) has been closed by %s." },
241     { "HSMSG_PAGE_CLOSE_REQUEST_2", "Request ID#%lu from $b%s$b (Not authed) has been closed by %s." },
242     { "HSMSG_PAGE_CLOSE_REQUEST_3", "Request ID#%lu from an offline user (Account %s) has been closed by %s." },
243     { "HSMSG_PAGE_CLOSE_REQUEST_4", "Request ID#%lu from an offline user (no account) has been closed by %s." },
244     { "HSMSG_PAGE_ASSIGN_REQUEST_1", "Request ID#%lu from $b%s$b (Account %s) has been assigned to %s." },
245     { "HSMSG_PAGE_ASSIGN_REQUEST_2", "Request ID#%lu from $b%s$b (Not authed) has been assigned to %s." },
246     { "HSMSG_PAGE_ASSIGN_REQUEST_3", "Request ID#%lu from an offline user (Account %s) has been assigned to %s." },
247     { "HSMSG_PAGE_ASSIGN_REQUEST_4", "Request ID#%lu from an offline user (no account) has been assigned to %s." },
248     /* The last %s is still an I18N lose.  Blame whoever decided to overload it so much. */
249     { "HSMSG_PAGE_HELPER_GONE_1", "Request ID#%lu from $b%s$b (Account %s) $bhas been unassigned$b, as its helper, %s has %s." },
250     { "HSMSG_PAGE_HELPER_GONE_2", "Request ID#%lu from $b%s$b (Not authed) $bhas been unassigned$b, as its helper, %s has %s." },
251     { "HSMSG_PAGE_HELPER_GONE_3", "Request ID#%lu from an offline user (Account %s) $bhas been unassigned$b, as its helper, %s has %s." },
252     { "HSMSG_PAGE_HELPER_GONE_4", "Request ID#%lu from an offline user (No account) $bhas been unassigned$b, as its helper, %s has %s." },
253     { "HSMSG_PAGE_WHINE_HEADER", "$b%u unhandled request(s)$b waiting at least $b%s$b (%u unhandled, %u total)" },
254     { "HSMSG_PAGE_IDLE_HEADER", "$b%u users$b in %s $bidle at least %s$b:" },
255     { "HSMSG_PAGE_EMPTYALERT", "$b%s has no helpers present (%u unhandled request(s))$b" },
256     { "HSMSG_PAGE_ONLYTRIALALERT", "$b%s has no full helpers present (%d trial(s) present; %u unhandled request(s))$b" },
257     { "HSMSG_PAGE_FIRSTEMPTYALERT", "$b%s has no helpers present because %s has left (%u unhandled request(s))$b" },
258     { "HSMSG_PAGE_FIRSTONLYTRIALALERT", "$b%s has no full helpers present because %s has left (%d trial(s) present; %u unhandled request(s))$b" },
259     { "HSMSG_PAGE_EMPTYNOMORE", "%s has joined %s; cancelling the \"no helpers present\" alert" },
260
261 /* Notification messages */
262     { "HSMSG_NOTIFY_USER_QUIT", "The user for request ID#%lu, $b%s$b, has disconnected." },
263     { "HSMSG_NOTIFY_USER_MOVE", "The account for request ID#%lu, $b%s$b has been unregistered. It has been associated with user $b%s$b." },
264     { "HSMSG_NOTIFY_USER_NICK", "The user for request ID#%lu has changed their nick from $b%s$b to $b%s$b." },
265     { "HSMSG_NOTIFY_USER_FOUND", "The user for request ID#%lu is now online using nick $b%s$b." },
266     { "HSMSG_NOTIFY_HAND_RENAME", "The account for request ID#%lu has been renamed from $b%s$b to $b%s$b." },
267     { "HSMSG_NOTIFY_HAND_MOVE", "The account for request ID#%lu has been changed to $b%s$b from $b%s$b." },
268     { "HSMSG_NOTIFY_HAND_STUCK", "The user for request ID#%lu, $b%s$b, has re-authenticated to account $b%s$b from $b%s$b, and the request remained associated with the old handle." },
269     { "HSMSG_NOTIFY_HAND_AUTH", "The user for request ID#%lu, $b%s$b, has authenticated to account $b%s$b." },
270     { "HSMSG_NOTIFY_HAND_UNREG", "The account for request ID#%lu, $b%s$b, has been unregistered by $b%s$b." },
271     { "HSMSG_NOTIFY_HAND_MERGE", "The account for request ID#%lu, $b%s$b, has been merged with $b%s$b by %s." },
272     { "HSMSG_NOTIFY_ALLOWAUTH", "The user for request ID#%lu, $b%s$b, has been permitted by %s to authenticate to account $b%s$b without hostmask checking." },
273     { "HSMSG_NOTIFY_UNALLOWAUTH", "The user for request ID#%lu, $b%s$b, has had their ability to authenticate without hostmask checking revoked by %s." },
274     { "HSMSG_NOTIFY_FAILPW", "The user for request ID#%lu, $b%s$b, has attempted to authenticate to account $b%s$b, but used an incorrect password." },
275     { "HSMSG_NOTIFY_REQ_DROP_PART", "Request ID#%lu has been $bdropped$b because %s left the help channel." },
276     { "HSMSG_NOTIFY_REQ_DROP_QUIT", "Request ID#%lu has been $bdropped$b because %s quit IRC." },
277     { "HSMSG_NOTIFY_REQ_DROP_UNREGGED", "Request ID#%lu (account %s) has been $bdropped$b because the account was unregistered." },
278
279 /* Presence and request-related messages */
280     { "HSMSG_REQ_DROPPED_PART", "You have left $b%s$b. Your help request (ID#%lu) has been deleted." },
281     { "HSMSG_REQ_WARN_UNREG", "The account you were authenticated to ($b%s$b) has been unregistered. Therefore unless you register a new handle, or authenticate to another one, if you disconnect, your HelpServ $S (%s) request ID#%lu will be lost." },
282     { "HSMSG_REQ_DROPPED_UNREG", "By unregistering the account $b%s$b, HelpServ $S (%s) request ID#%lu was dropped, as there was nobody online to associate the request with." },
283     { "HSMSG_REQ_ASSIGNED_UNREG", "As the account $b%s$b was unregistered, HelpServ $S (%s) request ID#%lu was associated with you, as you were authenticated to that account. If you disconnect, then this request will be lost forever." },
284     { "HSMSG_REQ_AUTH_STUCK", "By authenticating to account $b%s$b, HelpServ $S (%s) request ID#%lu remained with the previous account $b%s$b (because the request will remain until closed). This means that if you send $S a message, it $uwill not$u be associated with this request." },
285     { "HSMSG_REQ_AUTH_MOVED", "By authenticating to account $b%s$b, HelpServ $S (%s) request ID#%lu ceased to be associated with your previously authenticated account ($b%s$b), and was transferred to your new account (because the request will only remain until you %s). This means that if you send $S a message, it $uwill$u be associated with this request." },
286
287 /* Lists */
288     { "HSMSG_BOTLIST_HEADER", "$bCurrent HelpServ bots:$b" },
289     { "HSMSG_USERLIST_HEADER", "$b%s users:$b" },
290     { "HSMSG_USERLIST_ZOOT_LVL", "%s $b%ss$b:" },
291     { "HSMSG_REQLIST_AUTH", "You are currently assigned these requests:" },
292     { "HSMSG_REQ_LIST_TOP_UNASSIGNED", "Listing $ball unassigned$b requests (%d in list)." },
293     { "HSMSG_REQ_LIST_TOP_ASSIGNED", "Listing $ball assigned$b requests (%d in list)." },
294     { "HSMSG_REQ_LIST_TOP_YOUR", "Listing $byour$b requests (%d in list)." },
295     { "HSMSG_REQ_LIST_TOP_ALL", "Listing $ball$b requests (%d in list)." },
296     { "HSMSG_REQ_LIST_NONE", "There are no matching requests." },
297     { "HSMSG_STATS_TOP", "Stats for %s user $b%s$b (week starts %s):" },
298     { "HSMSG_STATS_TIME", "$uTime spent helping in %s:$u" },
299     { "HSMSG_STATS_REQS", "$uRequest activity statistics:$u" },
300
301 /* Status report headers */
302     { "HSMSG_STATS_REPORT_0", "Stats report for current week" },
303     { "HSMSG_STATS_REPORT_1", "Stats report for one week ago" },
304     { "HSMSG_STATS_REPORT_2", "Stats report for two weeks ago" },
305     { "HSMSG_STATS_REPORT_3", "Stats report for three weeks ago" },
306
307 /* Responses to user commands */
308     { "HSMSG_YOU_BEING_HELPED", "You are already being helped." },
309     { "HSMSG_YOU_BEING_HELPED_BY", "You are already being helped by $b%s$b." },
310     { "HSMSG_WAIT_STATUS", "You are %d of %d in line; the first person has waited %s." },
311     { NULL, NULL }
312 };
313
314 enum helpserv_level {
315     HlNone,
316     HlTrial,
317     HlHelper,
318     HlManager,
319     HlOwner,
320     HlOper,
321     HlCount
322 };
323
324 static const char *helpserv_level_names[] = {
325     "None",
326     "Trial",
327     "Helper",
328     "Manager",
329     "Owner",
330     "Oper",
331     NULL
332 };
333
334 enum page_type {
335     PAGE_NONE,
336     PAGE_NOTICE,
337     PAGE_PRIVMSG,
338     PAGE_ONOTICE,
339     PAGE_COUNT
340 };
341
342 static const struct {
343     char *db_name;
344     char *print_name;
345     irc_send_func func;
346 } page_types[] = {
347     { "none", "MSG_NONE", NULL },
348     { "notice", "HSMSG_PAGE_NOTICE", irc_notice },
349     { "privmsg", "HSMSG_PAGE_PRIVMSG", irc_privmsg },
350     { "onotice", "HSMSG_PAGE_ONOTICE", irc_wallchops },
351     { NULL, NULL, NULL }
352 };
353
354 enum page_source {
355     PGSRC_COMMAND,
356     PGSRC_ALERT,
357     PGSRC_STATUS,
358     PGSRC_COUNT
359 };
360
361 static const struct {
362     char *db_name;
363     char *print_target;
364     char *print_type;
365 } page_sources[] = {
366     { "command", "HSMSG_SET_COMMAND_TARGET", "HSMSG_SET_COMMAND_TYPE" },
367     { "alert", "HSMSG_SET_ALERT_TARGET", "HSMSG_SET_ALERT_TYPE" },
368     { "status", "HSMSG_SET_STATUS_TARGET", "HSMSG_SET_STATUS_TYPE" },
369     { NULL, NULL, NULL }
370 };
371
372 enum message_type {
373     MSGTYPE_GREETING,
374     MSGTYPE_REQ_OPENED,
375     MSGTYPE_REQ_ASSIGNED,
376     MSGTYPE_REQ_CLOSED,
377     MSGTYPE_REQ_DROPPED,
378     MSGTYPE_COUNT
379 };
380
381 static const struct {
382     char *db_name;
383     char *print_name;
384 } message_types[] = {
385     { "greeting", "HSMSG_SET_GREETING" },
386     { "reqopened", "HSMSG_SET_REQOPENED" },
387     { "reqassigned", "HSMSG_SET_REQASSIGNED" },
388     { "reqclosed", "HSMSG_SET_REQCLOSED" },
389     { "reqdropped", "HSMSG_SET_REQDROPPED" },
390     { NULL, NULL }
391 };
392
393 enum interval_type {
394     INTERVAL_IDLE_DELAY,
395     INTERVAL_WHINE_DELAY,
396     INTERVAL_WHINE_INTERVAL,
397     INTERVAL_EMPTY_INTERVAL,
398     INTERVAL_STALE_DELAY,
399     INTERVAL_COUNT
400 };
401
402 static const struct {
403     char *db_name;
404     char *print_name;
405 } interval_types[] = {
406     { "idledelay", "HSMSG_SET_IDLEDELAY" },
407     { "whinedelay", "HSMSG_SET_WHINEDELAY" },
408     { "whineinterval", "HSMSG_SET_WHINEINTERVAL" },
409     { "emptyinterval", "HSMSG_SET_EMPTYINTERVAL" },
410     { "staledelay", "HSMSG_SET_STALEDELAY" },
411     { NULL, NULL }
412 };
413
414 enum persistence_type {
415     PERSIST_T_REQUEST,
416     PERSIST_T_HELPER,
417     PERSIST_T_COUNT
418 };
419
420 static const struct {
421     char *db_name;
422     char *print_name;
423 } persistence_types[] = {
424     { "reqpersist", "HSMSG_SET_REQPERSIST" },
425     { "helperpersist", "HSMSG_SET_HELPERPERSIST" },
426     { NULL, NULL }
427 };
428
429 enum persistence_length {
430     PERSIST_PART,
431     PERSIST_QUIT,
432     PERSIST_CLOSE,
433     PERSIST_COUNT
434 };
435
436 static const struct {
437     char *db_name;
438     char *print_name;
439 } persistence_lengths[] = {
440     { "part", "HSMSG_LENGTH_PART" },
441     { "quit", "HSMSG_LENGTH_QUIT" },
442     { "close", "HSMSG_LENGTH_CLOSE" },
443     { NULL, NULL }
444 };
445
446 enum notification_type {
447     NOTIFY_NONE,
448     NOTIFY_DROP,
449     NOTIFY_USER,
450     NOTIFY_HANDLE,
451     NOTIFY_COUNT
452 };
453
454 static const struct {
455     char *db_name;
456     char *print_name;
457 } notification_types[] = {
458     { "none", "MSG_NONE" },
459     { "reqdrop", "HSMSG_NOTIFY_DROP" },
460     { "userchanges", "HSMSG_NOTIFY_USERCHANGES" },
461     { "accountchanges", "HSMSG_NOTIFY_ACCOUNTCHANGES" },
462     { NULL, NULL }
463 };
464
465 static const char *weekday_names[] = {
466     "Sunday",
467     "Monday",
468     "Tuesday",
469     "Wednesday",
470     "Thursday",
471     "Friday",
472     "Saturday",
473     NULL
474 };
475
476 static const char *statsreport_week[] = {
477     "HSMSG_STATS_REPORT_0",
478     "HSMSG_STATS_REPORT_1",
479     "HSMSG_STATS_REPORT_2",
480     "HSMSG_STATS_REPORT_3"
481 };
482
483 static struct {
484     const char *description;
485     const char *reqlogfile;
486     unsigned long db_backup_frequency;
487     unsigned int expire_age;
488     unsigned long modstats_level;
489     char user_escape;
490 } helpserv_conf;
491
492 static unsigned long last_stats_update;
493 static int shutting_down;
494 static FILE *reqlog_f;
495 static struct log_type *HS_LOG;
496
497 #define CMD_NEED_BOT            0x001
498 #define CMD_NOT_OVERRIDE        0x002
499 #define CMD_FROM_OPSERV_ONLY    0x004
500 #define CMD_IGNORE_EVENT        0x008
501 #define CMD_NEVER_FROM_OPSERV   0x010
502
503 struct helpserv_bot {
504     struct userNode *helpserv;
505
506     struct chanNode *helpchan;
507     struct chanNode *publicchan;
508
509     struct chanNode *page_targets[PGSRC_COUNT];
510     enum page_type page_types[PGSRC_COUNT];
511     char *messages[MSGTYPE_COUNT];
512     unsigned long intervals[INTERVAL_COUNT];
513     enum notification_type notify;
514
515     /* This is a default; it can be changed on a per-request basis */
516     enum persistence_length persist_lengths[PERSIST_T_COUNT];
517
518     dict_t users; /* indexed by handle */
519
520     struct helpserv_request *unhandled; /* linked list of unhandled requests */
521     dict_t requests; /* indexed by request id */
522     unsigned long last_requestid;
523     unsigned long id_wrap;
524     unsigned long req_maxlen; /* Maxmimum request length in lines */
525
526     unsigned int privmsg_only : 1;
527     unsigned int req_on_join : 1;
528     unsigned int auto_voice : 1;
529     unsigned int auto_join : 1;
530     unsigned int auto_devoice : 1;
531
532     unsigned int helpchan_empty : 1;
533
534     unsigned long registered;
535     unsigned long last_active;
536     char *registrar;
537 };
538
539 struct helpserv_user {
540     struct handle_info *handle;
541     struct helpserv_bot *hs;
542     unsigned int help_mode : 1;
543     unsigned int week_start : 3;
544     enum helpserv_level level;
545     /* statistics */
546     unsigned long join_time; /* when they joined, or 0 if not in channel */
547     /* [0] through [3] are n weeks ago, [4] is the total of everything before that */
548     unsigned int time_per_week[5]; /* how long they've were in the channel the past 4 weeks */
549     unsigned int picked_up[5]; /* how many requests they have picked up */
550     unsigned int closed[5]; /* how many requests they have closed */
551     unsigned int reassigned_from[5]; /* how many requests reassigned from them to others */
552     unsigned int reassigned_to[5]; /* how many requests reassigned from others to them */
553 };
554
555 struct helpserv_request {
556     struct helpserv_user *helper;
557     struct helpserv_bot *hs;
558     struct string_list *text;
559     struct helpserv_request *next_unhandled;
560
561     struct helpserv_reqlist *parent_nick_list;
562     struct helpserv_reqlist *parent_hand_list;
563
564     /* One, but not both, of "user" and "handle" may be NULL,
565      * depending on what we know about the user.
566      *
567      * If persist == PERSIST_CLOSE when the user quits, then it
568      * switches to handle instead of user... and stays that way (it's
569      * possible to have >1 nick per handle, so you can't really decide
570      * to reassign a userNode to it unless they send another message
571      * to HelpServ).
572      */
573     struct userNode *user;
574     struct handle_info *handle;
575
576     unsigned long id;
577     unsigned long opened;
578     unsigned long assigned;
579     unsigned long updated;
580 };
581
582 #define DEFINE_LIST_ALLOC(STRUCTNAME) \
583 struct STRUCTNAME * STRUCTNAME##_alloc() {\
584     struct STRUCTNAME *newlist; \
585     newlist = malloc(sizeof(struct STRUCTNAME)); \
586     STRUCTNAME##_init(newlist); \
587     return newlist; \
588 }\
589 void STRUCTNAME##_free(void *data) {\
590     struct STRUCTNAME *list = data; /* void * to let dict_set_free_data use it */ \
591     STRUCTNAME##_clean(list); \
592     free(list); \
593 }
594
595 DECLARE_LIST(helpserv_botlist, struct helpserv_bot *);
596 DEFINE_LIST(helpserv_botlist, struct helpserv_bot *)
597 DEFINE_LIST_ALLOC(helpserv_botlist)
598
599 DECLARE_LIST(helpserv_reqlist, struct helpserv_request *);
600 DEFINE_LIST(helpserv_reqlist, struct helpserv_request *)
601 DEFINE_LIST_ALLOC(helpserv_reqlist)
602
603 DECLARE_LIST(helpserv_userlist, struct helpserv_user *);
604 DEFINE_LIST(helpserv_userlist, struct helpserv_user *)
605 DEFINE_LIST_ALLOC(helpserv_userlist)
606
607 struct helpfile *helpserv_helpfile;
608 static struct module *helpserv_module;
609 static dict_t helpserv_func_dict;
610 static dict_t helpserv_usercmd_dict; /* contains helpserv_usercmd_t */
611 static dict_t helpserv_option_dict;
612 static dict_t helpserv_bots_dict; /* indexed by nick */
613 static dict_t helpserv_bots_bychan_dict; /* indexed by chan, holds a struct helpserv_botlist */
614 /* QUESTION: why are these outside of any helpserv_bot struct? */
615 static dict_t helpserv_reqs_bynick_dict; /* indexed by nick, holds a struct helpserv_reqlist */
616 static dict_t helpserv_reqs_byhand_dict; /* indexed by handle, holds a struct helpserv_reqlist */
617 static dict_t helpserv_users_byhand_dict; /* indexed by handle, holds a struct helpserv_userlist */
618
619 /* This is so that overrides can "speak" from opserv */
620 extern struct userNode *opserv;
621
622 #define HELPSERV_SYNTAX() helpserv_help(hs, from_opserv, user, argv[0])
623 #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[]))
624 typedef HELPSERV_FUNC(helpserv_func_t);
625 #define HELPSERV_USERCMD(NAME) void NAME(struct helpserv_request *req, UNUSED_ARG(struct userNode *likely_helper), UNUSED_ARG(char *args))
626 typedef HELPSERV_USERCMD(helpserv_usercmd_t);
627 #define HELPSERV_OPTION(NAME) HELPSERV_FUNC(NAME)
628 typedef HELPSERV_OPTION(helpserv_option_func_t);
629
630 static HELPSERV_FUNC(cmd_help);
631
632 #define REQUIRE_PARMS(N) if (argc < N) { \
633         helpserv_notice(user, "MSG_MISSING_PARAMS", argv[0]); \
634         HELPSERV_SYNTAX(); \
635         return 0; }
636
637 /* For messages going to users being helped */
638 #if defined(GCC_VARMACROS)
639 # define helpserv_msguser(target, ARGS...) send_message_type((from_opserv ? 0 : hs->privmsg_only), (target), (from_opserv ? opserv : hs->helpserv), ARGS)
640 # define helpserv_user_reply(ARGS...) send_message_type(req->hs->privmsg_only, req->user, req->hs->helpserv, ARGS)
641 /* For messages going to helpers */
642 # define helpserv_notice(target, ARGS...) send_message((target), (from_opserv ? opserv : hs->helpserv), ARGS)
643 # define helpserv_notify(helper, ARGS...) do { struct userNode *_target; for (_target = (helper)->handle->users; _target; _target = _target->next_authed) { \
644         if(!_target->next_authed || GetUserMode(helper->hs->helpchan, _target)) {\
645           send_message(_target, (helper)->hs->helpserv, ARGS); \
646           break; \
647         } \
648     } } while (0)
649 # define helpserv_page(TYPE, ARGS...) do { \
650     int msg_type=0; struct chanNode *target=helpserv_get_page_type(hs, (TYPE), &msg_type); \
651     if (target) send_target_message(msg_type, target->name, hs->helpserv, ARGS); \
652     } while (0)
653 #elif defined(C99_VARMACROS)
654 # define helpserv_msguser(target, ...) send_message_type((from_opserv ? 0 : hs->privmsg_only), (target), (from_opserv ? opserv : hs->helpserv), __VA_ARGS__)
655 # define helpserv_user_reply(...) send_message_type(req->hs->privmsg_only, req->user, req->hs->helpserv, __VA_ARGS__)
656 /* For messages going to helpers */
657 # define helpserv_notice(target, ...) send_message((target), (from_opserv ? opserv : hs->helpserv), __VA_ARGS__)
658 # define helpserv_notify(helper, ...) do { struct userNode *_target; for (_target = (helper)->handle->users; _target; _target = _target->next_authed) { \
659         if(!_target->next_authed || GetUserMode(helper->hs->helpchan, _target)) {\
660           send_message(_target, (helper)->hs->helpserv, __VA_ARGS__); \
661           break; \
662         } \
663     } } while (0)
664 # define helpserv_page(TYPE, ...) do { \
665     int msg_type=0; struct chanNode *target=helpserv_get_page_type(hs, (TYPE), &msg_type); \
666     if (target) send_target_message(msg_type, target->name, hs->helpserv, __VA_ARGS__); \
667     } while (0)
668 #endif
669 #define helpserv_message(hs, target, id) do { if ((hs)->messages[id]) { \
670     if (from_opserv) \
671         send_message_type(4, (target), opserv, "%s", (hs)->messages[id]); \
672     else \
673         send_message_type(4 | hs->privmsg_only, (target), hs->helpserv, "%s", (hs)->messages[id]); \
674     } } while (0)
675 #define helpserv_get_handle_info(user, text) smart_get_handle_info((from_opserv ? opserv : hs->helpserv) , (user), (text))
676
677 struct helpserv_cmd {
678     enum helpserv_level access;
679     helpserv_func_t *func;
680     double weight;
681     long flags;
682 };
683
684 static void run_empty_interval(void *data);
685
686 static void helpserv_interval(char *output, unsigned long interval) {
687     int num_hours = interval / 3600;
688     int num_minutes = (interval % 3600) / 60;
689     sprintf(output, "%u hour%s, %u minute%s", num_hours, num_hours == 1 ? "" : "s", num_minutes, num_minutes == 1 ? "" : "s");
690 }
691
692 static const char * helpserv_level2str(enum helpserv_level level) {
693     if (level <= HlOper) {
694         return helpserv_level_names[level];
695     } else {
696         log_module(HS_LOG, LOG_ERROR, "helpserv_level2str receieved invalid level %d.", level);
697         return "Error";
698     }
699 }
700
701 static enum helpserv_level helpserv_str2level(const char *msg) {
702     enum helpserv_level nn;
703     for (nn=HlNone; nn<=HlOper; nn++) {
704         if (!irccasecmp(msg, helpserv_level_names[nn]))
705             return nn;
706     }
707     log_module(HS_LOG, LOG_ERROR, "helpserv_str2level received invalid level %s.", msg);
708     return HlNone; /* Error */
709 }
710
711 static struct helpserv_user *GetHSUser(struct helpserv_bot *hs, struct handle_info *hi) {
712     return dict_find(hs->users, hi->handle, NULL);
713 }
714
715 static void helpserv_log_request(struct helpserv_request *req, const char *reason) {
716     char key[27+NICKLEN];
717     char userhost[USERLEN+HOSTLEN+2];
718     struct saxdb_context *ctx;
719     int res;
720
721     assert(req != NULL);
722     assert(reason != NULL);
723     if (!reqlog_f || !(ctx = saxdb_open_context(reqlog_f)))
724         return;
725     sprintf(key, "%s-%lu-%lu", req->hs->helpserv->nick, (unsigned long)req->opened, req->id);
726     if ((res = setjmp(*saxdb_jmp_buf(ctx))) != 0) {
727         log_module(HS_LOG, LOG_ERROR, "Unable to log helpserv request: %s.", strerror(res));
728     } else {
729         saxdb_start_record(ctx, key, 1);
730         if (req->helper) {
731             saxdb_write_string(ctx, KEY_REQUEST_HELPER, req->helper->handle->handle);
732             saxdb_write_int(ctx, KEY_REQUEST_ASSIGNED, req->assigned);
733         }
734         if (req->handle) {
735             saxdb_write_string(ctx, KEY_REQUEST_HANDLE, req->handle->handle);
736         }
737         if (req->user) {
738             saxdb_write_string(ctx, KEY_REQUEST_NICK, req->user->nick);
739             sprintf(userhost, "%s@%s", req->user->ident, req->user->hostname);
740             saxdb_write_string(ctx, KEY_REQUEST_USERHOST, userhost);
741         }
742         saxdb_write_int(ctx, KEY_REQUEST_OPENED, req->opened);
743         saxdb_write_int(ctx, KEY_REQUEST_CLOSED, now);
744         saxdb_write_string(ctx, KEY_REQUEST_CLOSEREASON, reason);
745         saxdb_write_string_list(ctx, KEY_REQUEST_TEXT, req->text);
746         saxdb_end_record(ctx);
747         saxdb_close_context(ctx, 0);
748     }
749 }
750
751 static struct chanNode *helpserv_get_page_type(struct helpserv_bot *hs, enum page_source type, int *msg_type)
752 {
753     switch (hs->page_types[type]) {
754         case PAGE_NOTICE:
755             *msg_type = 0;
756             break;
757         case PAGE_PRIVMSG:
758             *msg_type = 1;
759             break;
760         case PAGE_ONOTICE:
761             *msg_type = 2;
762             break;
763         default:
764             log_module(HS_LOG, LOG_ERROR, "helpserv_page() called but %s has an invalid page type %d.", hs->helpserv->nick, type);
765             /* and fall through */
766         case PAGE_NONE:
767             return NULL;
768     }
769     return hs->page_targets[type];
770 }
771
772 /* Searches for a request by number, nick, or account (num|nick|*account).
773  * As there can potentially be >1 match, it takes a reqlist. The return
774  * value is the "best" request found (explained in the comment block below).
775  *
776  * If num_results is not NULL, it is set to the number of potentially matching
777  * requests.
778  * If hs_user is not NULL, requests assigned to this helper will be given
779  * preference (oldest assigned, falling back to oldest if there are none).
780  */
781 static struct helpserv_request * smart_get_request(struct helpserv_bot *hs, struct helpserv_user *hs_user, const char *needle, int *num_results) {
782     struct helpserv_reqlist *reqlist, resultlist;
783     struct helpserv_request *req, *oldest=NULL, *oldest_assigned=NULL;
784     struct userNode *user;
785     unsigned int i;
786
787     if (num_results)
788         *num_results = 0;
789
790     if (*needle == '*') {
791         /* This test (handle) requires the least processing, so it's first */
792         if (!(reqlist = dict_find(helpserv_reqs_byhand_dict, needle+1, NULL)))
793             return NULL;
794         helpserv_reqlist_init(&resultlist);
795         for (i=0; i < reqlist->used; i++) {
796             req = reqlist->list[i];
797             if (req->hs == hs) {
798                 helpserv_reqlist_append(&resultlist, req);
799                 if (num_results)
800                     (*num_results)++;
801             }
802         }
803     } else if (!needle[strspn(needle, "0123456789")]) {
804         /* The string is 100% numeric - a request id */
805         if (!(req = dict_find(hs->requests, needle, NULL)))
806             return NULL;
807         if (num_results)
808             *num_results = 1;
809         return req;
810     } else if ((user = GetUserH(needle))) {
811         /* And finally, search by nick */
812         if (!(reqlist = dict_find(helpserv_reqs_bynick_dict, needle, NULL)))
813             return NULL;
814         helpserv_reqlist_init(&resultlist);
815
816         for (i=0; i < reqlist->used; i++) {
817             req = reqlist->list[i];
818             if (req->hs == hs) {
819                 helpserv_reqlist_append(&resultlist, req);
820                 if (num_results)
821                     (*num_results)++;
822             }
823         }
824         /* If the nick didn't have anything, try their handle */
825         if (!resultlist.used && user->handle_info) {
826             char star_handle[NICKSERV_HANDLE_LEN+2];
827
828             helpserv_reqlist_clean(&resultlist);
829             sprintf(star_handle, "*%s", user->handle_info->handle);
830
831             return smart_get_request(hs, hs_user, star_handle, num_results);
832         }
833     } else {
834         return NULL;
835     }
836
837     if (resultlist.used == 0) {
838         helpserv_reqlist_clean(&resultlist);
839         return NULL;
840     } else if (resultlist.used == 1) {
841         req = resultlist.list[0];
842         helpserv_reqlist_clean(&resultlist);
843         return req;
844     }
845
846     /* In case there is >1 request returned, use the oldest one assigned to
847      * the helper executing the command. Otherwise, use the oldest request.
848      * This may not be the intended result for cmd_pickup (first unhandled
849      * request may be better), or cmd_reassign (first handled request), but
850      * it's close enough, and there really aren't supposed to be multiple
851      * requests per person anyway; they're just side effects of authing. */
852
853     for (i=0; i < resultlist.used; i++) {
854         req = resultlist.list[i];
855         if (!oldest || req->opened < oldest->opened)
856             oldest = req;
857         if (hs_user && (!oldest_assigned || (req->helper == hs_user && req->opened < oldest_assigned->opened)))
858             oldest_assigned = req;
859     }
860
861     helpserv_reqlist_clean(&resultlist);
862
863     return oldest_assigned ? oldest_assigned : oldest;
864 }
865
866 static struct helpserv_request * create_request(struct userNode *user, struct helpserv_bot *hs, int from_join) {
867     struct helpserv_request *req = calloc(1, sizeof(struct helpserv_request));
868     char lbuf[3][MAX_LINE_SIZE], req_id[INTERVALLEN];
869     struct helpserv_reqlist *reqlist, *hand_reqlist;
870     const unsigned int from_opserv = 0;
871     const char *fmt;
872
873     assert(req);
874
875     req->id = ++hs->last_requestid;
876     sprintf(req_id, "%lu", req->id);
877     dict_insert(hs->requests, strdup(req_id), req);
878
879     if (hs->id_wrap) {
880         unsigned long i;
881         char buf[12];
882         if (hs->last_requestid < hs->id_wrap) {
883             for (i=hs->last_requestid; i < hs->id_wrap; i++) {
884                 sprintf(buf, "%lu", i);
885                 if (!dict_find(hs->requests, buf, NULL)) {
886                     hs->last_requestid = i-1;
887                     break;
888                 }
889             }
890         }
891         if (hs->last_requestid >= hs->id_wrap) {
892             for (i=1; i < hs->id_wrap; i++) {
893                 sprintf(buf, "%lu", i);
894                 if (!dict_find(hs->requests, buf, NULL)) {
895                     hs->last_requestid = i-1;
896                     break;
897                 }
898             }
899             if (i >= hs->id_wrap) {
900                 log_module(HS_LOG, LOG_INFO, "%s has more requests than its id_wrap.", hs->helpserv->nick);
901             }
902         }
903     }
904
905     req->hs = hs;
906     req->helper = NULL;
907     req->text = alloc_string_list(4);
908     req->user = user;
909     req->handle = user->handle_info;
910     if (from_join && self->burst) {
911         extern unsigned long burst_begin;
912         /* We need to keep all the requests during a burst join together,
913          * even if the burst takes more than 1 second. ircu seems to burst
914          * in reverse-join order. */
915         req->opened = burst_begin;
916     } else {
917         req->opened = now;
918     }
919     req->updated = now;
920
921     if (!hs->unhandled) {
922         hs->unhandled = req;
923         req->next_unhandled = NULL;
924     } else if (self->burst && hs->unhandled->opened >= req->opened) {
925         req->next_unhandled = hs->unhandled;
926         hs->unhandled = req;
927     } else if (self->burst) {
928         struct helpserv_request *unh;
929         /* Add the request to the beginning of the set of requests with
930          * req->opened having the same value. This makes reqonjoin create
931          * requests in the correct order while bursting. Note that this
932          * does not correct request ids, so they will be in reverse order
933          * though "/msg botname next" will work properly. */
934         for (unh = hs->unhandled; unh->next_unhandled && unh->next_unhandled->opened < req->opened; unh = unh->next_unhandled) ;
935         req->next_unhandled = unh->next_unhandled;
936         unh->next_unhandled = req;
937     } else {
938         struct helpserv_request *unh;
939         /* Add to the end */
940         for (unh = hs->unhandled; unh->next_unhandled; unh = unh->next_unhandled) ;
941         req->next_unhandled = NULL;
942         unh->next_unhandled = req;
943     }
944
945     if (!(reqlist = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL))) {
946         reqlist = helpserv_reqlist_alloc();
947         dict_insert(helpserv_reqs_bynick_dict, user->nick, reqlist);
948     }
949     req->parent_nick_list = reqlist;
950     helpserv_reqlist_append(reqlist, req);
951
952     if (user->handle_info) {
953         if (!(hand_reqlist = dict_find(helpserv_reqs_byhand_dict, user->handle_info->handle, NULL))) {
954             hand_reqlist = helpserv_reqlist_alloc();
955             dict_insert(helpserv_reqs_byhand_dict, user->handle_info->handle, hand_reqlist);
956         }
957         req->parent_hand_list = hand_reqlist;
958         helpserv_reqlist_append(hand_reqlist, req);
959     } else {
960         req->parent_hand_list = NULL;
961     }
962
963     if (from_join) {
964         fmt = user_find_message(user, "HSMSG_REQ_NEWONJOIN");
965         sprintf(lbuf[0], fmt, hs->helpchan->name, req->id);
966     } else {
967         fmt = user_find_message(user, "HSMSG_REQ_NEW");
968         sprintf(lbuf[0], fmt, req->id);
969     }
970     if (req != hs->unhandled) {
971         intervalString(req_id, now - hs->unhandled->opened, user->handle_info);
972         fmt = user_find_message(user, "HSMSG_REQ_UNHANDLED_TIME");
973         sprintf(lbuf[1], fmt, req_id);
974     } else {
975         fmt = user_find_message(user, "HSMSG_REQ_NO_UNHANDLED");
976         sprintf(lbuf[1], "%s", fmt);
977     }
978     switch (hs->persist_lengths[PERSIST_T_REQUEST]) {
979         case PERSIST_PART:
980             fmt = user_find_message(user, "HSMSG_REQ_PERSIST_PART");
981             sprintf(lbuf[2], fmt, hs->helpchan->name, hs->helpchan->name);
982             break;
983         case PERSIST_QUIT:
984             fmt = user_find_message(user, "HSMSG_REQ_PERSIST_QUIT");
985             sprintf(lbuf[2], "%s", fmt);
986             break;
987         default:
988             log_module(HS_LOG, LOG_ERROR, "%s has an invalid req_persist.", hs->helpserv->nick);
989         case PERSIST_CLOSE:
990             if (user->handle_info) {
991                 fmt = user_find_message(user, "HSMSG_REQ_PERSIST_HANDLE");
992             } else {
993                 fmt = user_find_message(user, "HSMSG_REQ_PERSIST_QUIT");
994             }
995             sprintf(lbuf[2], "%s", fmt);
996             break;
997     }
998     helpserv_message(hs, user, MSGTYPE_REQ_OPENED);
999     if (from_opserv)
1000         send_message_type(4, user, opserv, "%s %s %s", lbuf[0], lbuf[1], lbuf[2]);
1001     else
1002         send_message_type(4, user, hs->helpserv, "%s %s %s", lbuf[0], lbuf[1], lbuf[2]);
1003
1004     if (hs->req_on_join && req == hs->unhandled && hs->helpchan_empty && !user->uplink->burst) {
1005         timeq_del(0, run_empty_interval, hs, TIMEQ_IGNORE_WHEN);
1006         run_empty_interval(hs);
1007     }
1008
1009     return req;
1010 }
1011
1012 /* Handle a message from a user to a HelpServ bot. */
1013 static void helpserv_usermsg(struct userNode *user, struct helpserv_bot *hs, const char *text) {
1014     const int from_opserv = 0; /* for helpserv_notice */
1015     struct helpserv_request *req=NULL, *newest=NULL;
1016     struct helpserv_reqlist *reqlist, *hand_reqlist;
1017     unsigned int i;
1018
1019     if ((reqlist = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL))) {
1020         for (i=0; i < reqlist->used; i++) {
1021             req = reqlist->list[i];
1022             if (req->hs != hs)
1023                 continue;
1024             if (!newest || (newest->opened < req->opened))
1025                 newest = req;
1026         }
1027
1028         /* If nothing was found, this will set req to NULL */
1029         req = newest;
1030     }
1031
1032     if (user->handle_info) {
1033         hand_reqlist = dict_find(helpserv_reqs_byhand_dict, user->handle_info->handle, NULL);
1034         if (!req && hand_reqlist) {
1035             /* Most recent first again */
1036             for (i=0; i < hand_reqlist->used; i++) {
1037                 req = hand_reqlist->list[i];
1038                 if ((req->hs != hs) || req->user)
1039                     continue;
1040                 if (!newest || (newest->opened < req->opened))
1041                     newest = req;
1042             }
1043             req = newest;
1044
1045             if (req) {
1046                 req->user = user;
1047                 if (!reqlist) {
1048                     reqlist = helpserv_reqlist_alloc();
1049                     dict_insert(helpserv_reqs_bynick_dict, user->nick, reqlist);
1050                 }
1051                 req->parent_nick_list = reqlist;
1052                 helpserv_reqlist_append(reqlist, req);
1053
1054                 if (req->helper && (hs->notify >= NOTIFY_USER))
1055                     helpserv_notify(req->helper, "HSMSG_NOTIFY_USER_FOUND", req->id, user->nick);
1056
1057                 helpserv_msguser(user, "HSMSG_GREET_PRIVMSG_EXISTREQ", req->id);
1058             }
1059         }
1060     } else {
1061         hand_reqlist = NULL;
1062     }
1063
1064     if (!req) {
1065         if (text[0] == helpserv_conf.user_escape) {
1066             helpserv_msguser(user, "HSMSG_USERCMD_NO_REQUEST");
1067             return;
1068         }
1069         if ((hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_PART) && !GetUserMode(hs->helpchan, user) && (!hs->publicchan || (hs->publicchan && !GetUserMode(hs->publicchan, user)))) {
1070              if(hs->publicchan)
1071                 helpserv_msguser(user, "HSMSG_REQ_YOU_NOT_IN_HELPCHAN_OPEN", hs->publicchan->name);
1072              else
1073                 helpserv_msguser(user, "HSMSG_REQ_YOU_NOT_IN_HELPCHAN_OPEN", hs->helpchan->name);
1074             return;
1075         }
1076
1077         req = create_request(user, hs, 0);
1078         if (user->handle_info)
1079             helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_NEW_REQUEST_AUTHED", req->id, user->nick, user->handle_info->handle);
1080         else
1081             helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_NEW_REQUEST_UNAUTHED", req->id, user->nick);
1082     } else if (text[0] == helpserv_conf.user_escape) {
1083         char cmdname[MAXLEN], *space;
1084         helpserv_usercmd_t *usercmd;
1085         struct userNode *likely_helper;
1086
1087         /* Find somebody likely to be the helper */
1088         if (!req->helper)
1089             likely_helper = NULL;
1090         else if ((likely_helper = req->helper->handle->users) && !likely_helper->next_authed) {
1091             /* only one user it could be :> */
1092         } else for (likely_helper = req->helper->handle->users; likely_helper; likely_helper = likely_helper->next_authed)
1093             if (GetUserMode(hs->helpchan, likely_helper))
1094                 break;
1095
1096         /* Parse out command name */
1097         space = strchr(text+1, ' ');
1098         if (space)
1099             strncpy(cmdname, text+1, space-text-1);
1100         else
1101             strcpy(cmdname, text+1);
1102
1103         /* Call the user command function */
1104         usercmd = dict_find(helpserv_usercmd_dict, cmdname, NULL);
1105         if (usercmd)
1106             usercmd(req, likely_helper, space+1);
1107         else
1108             helpserv_msguser(user, "HSMSG_USERCMD_UNKNOWN", cmdname);
1109         return;
1110     } else if (hs->intervals[INTERVAL_STALE_DELAY]
1111                && (req->updated < now - hs->intervals[INTERVAL_STALE_DELAY])
1112                && (!hs->req_maxlen || req->text->used < hs->req_maxlen)) {
1113         char buf[MAX_LINE_SIZE], updatestr[INTERVALLEN], timestr[MAX_LINE_SIZE];
1114         time_t feh;
1115
1116         feh = req->opened;
1117         strftime(timestr, MAX_LINE_SIZE, HSFMT_TIME, localtime(&feh));
1118         intervalString(updatestr, now - req->updated, user->handle_info);
1119         if (req->helper && (hs->notify >= NOTIFY_USER))
1120             if (user->handle_info)
1121                 helpserv_notify(req->helper, "HSMSG_PAGE_UPD_REQUEST_AUTHED", req->id, user->nick, user->handle_info->handle, timestr, updatestr);
1122             else
1123                 helpserv_notify(req->helper, "HSMSG_PAGE_UPD_REQUEST_NOT_AUTHED", req->id, user->nick, timestr, updatestr);
1124         else
1125             if (user->handle_info)
1126                 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_UPD_REQUEST_AUTHED", req->id, user->nick, user->handle_info->handle, timestr, updatestr);
1127             else
1128                 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_UPD_REQUEST_NOT_AUTHED", req->id, user->nick, timestr, updatestr);
1129         feh = now;
1130         strftime(timestr, MAX_LINE_SIZE, HSFMT_TIME, localtime(&feh));
1131         snprintf(buf, MAX_LINE_SIZE, "[Stale request updated at %s]", timestr);
1132         string_list_append(req->text, strdup(buf));
1133     }
1134
1135     req->updated = now;
1136     if (!hs->req_maxlen || req->text->used < hs->req_maxlen) {
1137         struct userNode *likely_helper;
1138
1139         string_list_append(req->text, strdup(text));
1140         /* Find somebody likely to be the helper */
1141         if (!req->helper)
1142             likely_helper = NULL;
1143         else if ((likely_helper = req->helper->handle->users) && !likely_helper->next_authed) {
1144             /* only one user it could be :> */
1145         } else for (likely_helper = req->helper->handle->users; likely_helper; likely_helper = likely_helper->next_authed)
1146             if (GetUserMode(hs->helpchan, likely_helper))
1147                 break;
1148
1149         if(likely_helper)
1150             send_target_message(1, likely_helper->nick, hs->helpserv, "HSMSQ_REQ_TEXT_ADDED", user->nick, text);
1151
1152     } else
1153         helpserv_msguser(user, "HSMSG_REQ_MAXLEN");
1154 }
1155
1156 /* Handle messages direct to a HelpServ bot. */
1157 static void helpserv_botmsg(struct userNode *user, struct userNode *target, const char *text, UNUSED_ARG(int server_qualified)) {
1158     struct helpserv_bot *hs;
1159     struct helpserv_cmd *cmd;
1160     struct helpserv_user *hs_user;
1161     char *argv[MAXNUMPARAMS];
1162     char tmpline[MAXLEN];
1163     int argc, argv_shift;
1164     const int from_opserv = 0; /* for helpserv_notice */
1165
1166     /* Ignore things consisting of empty lines or from ourselves */
1167     if (!*text || IsLocal(user))
1168         return;
1169
1170     hs = dict_find(helpserv_bots_dict, target->nick, NULL);
1171
1172     /* See if we should listen to their message as a command (helper)
1173      * or a help request (user) */
1174     if (!user->handle_info || !(hs_user = dict_find(hs->users, user->handle_info->handle, NULL))) {
1175         helpserv_usermsg(user, hs, text);
1176         return;
1177     }
1178
1179     argv_shift = 1;
1180     safestrncpy(tmpline, text, sizeof(tmpline));
1181     argc = split_line(tmpline, false, ArrayLength(argv)-argv_shift, argv+argv_shift);
1182     if (!argc)
1183         return;
1184
1185     cmd = dict_find(helpserv_func_dict, argv[argv_shift], NULL);
1186     if (!cmd) {
1187         helpserv_notice(user, "MSG_COMMAND_UNKNOWN", argv[argv_shift]);
1188         return;
1189     }
1190     if (cmd->flags & CMD_FROM_OPSERV_ONLY) {
1191         helpserv_notice(user, "HSMSG_OPER_CMD");
1192         return;
1193     }
1194     if (cmd->access > hs_user->level) {
1195         helpserv_notice(user, "HSMSG_LEVEL_TOO_LOW");
1196         return;
1197     }
1198     if (!cmd->func) {
1199         helpserv_notice(user, "HSMSG_INTERNAL_COMMAND", argv[argv_shift]);
1200     } else if (cmd->func(user, hs, 0, argc, argv+argv_shift)) {
1201         unsplit_string(argv+argv_shift, argc, tmpline);
1202         log_audit(HS_LOG, LOG_COMMAND, user, hs->helpserv, hs->helpchan->name, 0, tmpline);
1203     }
1204 }
1205
1206 /* Handle a control command from an IRC operator */
1207 static MODCMD_FUNC(cmd_helpserv) {
1208     struct helpserv_bot *hs = NULL;
1209     struct helpserv_cmd *subcmd;
1210     const int from_opserv = 1; /* for helpserv_notice */
1211     char botnick[NICKLEN+1]; /* in case command is unregister */
1212     int retval;
1213
1214     if (argc < 2) {
1215         send_help(user, opserv, helpserv_helpfile, NULL);
1216         return 0;
1217     }
1218
1219     if (!(subcmd = dict_find(helpserv_func_dict, argv[1], NULL))) {
1220         helpserv_notice(user, "MSG_COMMAND_UNKNOWN", argv[1]);
1221         return 0;
1222     }
1223
1224     if (!subcmd->func) {
1225         helpserv_notice(user, "HSMSG_INTERNAL_COMMAND", argv[1]);
1226         return 0;
1227     }
1228
1229     if ((subcmd->flags & CMD_NEED_BOT) && ((argc < 3) || !(hs = dict_find(helpserv_bots_dict, argv[2], NULL)))) {
1230         helpserv_notice(user, "HSMSG_INVALID_BOT");
1231         return 0;
1232     }
1233
1234     if (subcmd->flags & CMD_NEVER_FROM_OPSERV) {
1235         helpserv_notice(user, "HSMSG_NO_USE_OPSERV");
1236         return 0;
1237     }
1238
1239     if (hs) {
1240         argv[2] = argv[1];
1241         strcpy(botnick, hs->helpserv->nick);
1242         retval = subcmd->func(user, hs, 1, argc-2, argv+2);
1243     } else {
1244         strcpy(botnick, "No bot");
1245         retval = subcmd->func(user, hs, 1, argc-1, argv+1);
1246     }
1247
1248     return retval;
1249 }
1250
1251 static void helpserv_help(struct helpserv_bot *hs, int from_opserv, struct userNode *user, const char *topic) {
1252     send_help(user, (from_opserv ? opserv : hs->helpserv), helpserv_helpfile, topic);
1253 }
1254
1255 static int append_entry(const char *key, UNUSED_ARG(void *data), void *extra) {
1256     struct helpfile_expansion *exp = extra;
1257     int row;
1258
1259     row = exp->value.table.length++;
1260     exp->value.table.contents[row] = calloc(1, sizeof(char*));
1261     exp->value.table.contents[row][0] = key;
1262     return 0;
1263 }
1264
1265 static struct helpfile_expansion helpserv_expand_variable(const char *variable) {
1266     struct helpfile_expansion exp;
1267
1268     if (!irccasecmp(variable, "index")) {
1269         exp.type = HF_TABLE;
1270         exp.value.table.length = 1;
1271         exp.value.table.width = 1;
1272         exp.value.table.flags = TABLE_REPEAT_ROWS;
1273         exp.value.table.contents = calloc(dict_size(helpserv_func_dict)+1, sizeof(char**));
1274         exp.value.table.contents[0] = calloc(1, sizeof(char*));
1275         exp.value.table.contents[0][0] = "Commands:";
1276         dict_foreach(helpserv_func_dict, append_entry, &exp);
1277         return exp;
1278     }
1279
1280     exp.type = HF_STRING;
1281     exp.value.str = NULL;
1282     return exp;
1283 }
1284
1285 static void helpserv_helpfile_read(void) {
1286     helpserv_helpfile = open_helpfile(HELPSERV_HELPFILE_NAME, helpserv_expand_variable);
1287 }
1288
1289 static HELPSERV_USERCMD(usercmd_wait) {
1290     struct helpserv_request *other;
1291     int pos, count;
1292     char buf[INTERVALLEN];
1293
1294     if (req->helper) {
1295         if (likely_helper)
1296             helpserv_user_reply("HSMSG_YOU_BEING_HELPED_BY", likely_helper->nick);
1297         else
1298             helpserv_user_reply("HSMSG_YOU_BEING_HELPED");
1299         return;
1300     }
1301
1302     for (other = req->hs->unhandled, pos = -1, count = 0;
1303          other;
1304          other = other->next_unhandled, ++count) {
1305         if (other == req)
1306             pos = count;
1307     }
1308     assert(pos >= 0);
1309     intervalString(buf, now - req->hs->unhandled->opened, req->user->handle_info);
1310     helpserv_user_reply("HSMSG_WAIT_STATUS", pos+1, count, buf);
1311 }
1312
1313 static HELPSERV_FUNC(cmd_help) {
1314     const char *topic;
1315
1316     if (argc < 2)
1317         topic = NULL;
1318     else
1319         topic = unsplit_string(argv+1, argc-1, NULL);
1320     helpserv_help(hs, from_opserv, user, topic);
1321
1322     return 1;
1323 }
1324
1325 static HELPSERV_FUNC(cmd_readhelp) {
1326     struct timeval start, stop;
1327     struct helpfile *old_helpfile = helpserv_helpfile;
1328
1329     gettimeofday(&start, NULL);
1330     helpserv_helpfile_read();
1331     if (helpserv_helpfile) {
1332         close_helpfile(old_helpfile);
1333     } else {
1334         helpserv_helpfile = old_helpfile;
1335     }
1336     gettimeofday(&stop, NULL);
1337     stop.tv_sec -= start.tv_sec;
1338     stop.tv_usec -= start.tv_usec;
1339     if (stop.tv_usec < 0) {
1340         stop.tv_sec -= 1;
1341         stop.tv_usec += 1000000;
1342     }
1343     helpserv_notice(user, "HSMSG_READHELP_SUCCESS", (unsigned long)stop.tv_sec, (unsigned long)stop.tv_usec/1000);
1344
1345     return 1;
1346 }
1347
1348 static struct helpserv_user * helpserv_add_user(struct helpserv_bot *hs, struct handle_info *handle, enum helpserv_level level) {
1349     struct helpserv_user *hs_user;
1350     struct helpserv_userlist *userlist;
1351
1352     hs_user = calloc(1, sizeof(struct helpserv_user));
1353     hs_user->handle = handle;
1354     hs_user->hs = hs;
1355     hs_user->help_mode = 0;
1356     hs_user->level = level;
1357     hs_user->join_time = find_handle_in_channel(hs->helpchan, handle, NULL) ? now : 0;
1358     dict_insert(hs->users, handle->handle, hs_user);
1359
1360     if (!(userlist = dict_find(helpserv_users_byhand_dict, handle->handle, NULL))) {
1361         userlist = helpserv_userlist_alloc();
1362         dict_insert(helpserv_users_byhand_dict, handle->handle, userlist);
1363     }
1364     helpserv_userlist_append(userlist, hs_user);
1365
1366     return hs_user;
1367 }
1368
1369 static void helpserv_del_user(struct helpserv_bot *hs, struct helpserv_user *hs_user) {
1370     dict_remove(hs->users, hs_user->handle->handle);
1371 }
1372
1373 static int cmd_add_user(struct helpserv_bot *hs, int from_opserv, struct userNode *user, enum helpserv_level level, int argc, char *argv[]) {
1374     struct helpserv_user *actor;
1375     struct handle_info *handle;
1376
1377     REQUIRE_PARMS(2);
1378
1379     if (!from_opserv) {
1380         actor = GetHSUser(hs, user->handle_info);
1381         if (actor->level < HlManager) {
1382             helpserv_notice(user, "HSMSG_CANNOT_ADD");
1383             return 0;
1384         }
1385     } else {
1386         actor = NULL;
1387     }
1388
1389     if (!(handle = helpserv_get_handle_info(user, argv[1])))
1390         return 0;
1391
1392     if (GetHSUser(hs, handle)) {
1393         helpserv_notice(user, "HSMSG_USER_EXISTS", handle->handle);
1394         return 0;
1395     }
1396
1397     if (!(from_opserv) && actor && (actor->level <= level)) {
1398         helpserv_notice(user, "HSMSG_NO_BUMP_ACCESS");
1399         return 0;
1400     }
1401
1402     helpserv_add_user(hs, handle, level);
1403
1404     helpserv_notice(user, "HSMSG_ADDED_USER", helpserv_level2str(level), handle->handle);
1405     return 1;
1406 }
1407
1408 static HELPSERV_FUNC(cmd_deluser) {
1409     struct helpserv_user *actor=NULL, *victim;
1410     struct handle_info *handle;
1411     enum helpserv_level level;
1412
1413     REQUIRE_PARMS(2);
1414
1415     if (!from_opserv) {
1416         actor = GetHSUser(hs, user->handle_info);
1417         if (actor->level < HlManager) {
1418             helpserv_notice(user, "HSMSG_CANNOT_DEL");
1419             return 0;
1420         }
1421     }
1422
1423     if (!(handle = helpserv_get_handle_info(user, argv[1])))
1424         return 0;
1425
1426     if (!(victim = GetHSUser(hs, handle))) {
1427         helpserv_notice(user, "HSMSG_NOT_IN_USERLIST", handle->handle, hs->helpserv->nick);
1428         return 0;
1429     }
1430
1431     if (!from_opserv && actor && (actor->level <= victim->level)) {
1432         helpserv_notice(user, "MSG_USER_OUTRANKED", victim->handle->handle);
1433         return 0;
1434     }
1435
1436     level = victim->level;
1437     helpserv_del_user(hs, victim);
1438     helpserv_notice(user, "HSMSG_DELETED_USER", helpserv_level2str(level), handle->handle);
1439     return 1;
1440 }
1441
1442 static int
1443 helpserv_user_comp(const void *arg_a, const void *arg_b)
1444 {
1445     const struct helpserv_user *a = *(struct helpserv_user**)arg_a;
1446     const struct helpserv_user *b = *(struct helpserv_user**)arg_b;
1447     int res;
1448     if (a->level != b->level)
1449         res = b->level - a->level;
1450     else
1451         res = irccasecmp(a->handle->handle, b->handle->handle);
1452     return res;
1453 }
1454
1455 static int show_helper_range(struct userNode *user, struct helpserv_bot *hs, int from_opserv, enum helpserv_level min_lvl, enum helpserv_level max_lvl) {
1456     struct helpserv_userlist users;
1457     struct helpfile_table tbl;
1458     struct helpserv_user *hs_user;
1459     dict_iterator_t it;
1460     enum helpserv_level last_level;
1461     unsigned int ii;
1462
1463     users.used = 0;
1464     users.size = dict_size(hs->users);
1465     users.list = alloca(users.size*sizeof(hs->users[0]));
1466     helpserv_notice(user, "HSMSG_USERLIST_HEADER", hs->helpserv->nick);
1467     for (it = dict_first(hs->users); it; it = iter_next(it)) {
1468         hs_user = iter_data(it);
1469         if (hs_user->level < min_lvl)
1470             continue;
1471         if (hs_user->level > max_lvl)
1472             continue;
1473         users.list[users.used++] = hs_user;
1474     }
1475     if (!users.used) {
1476         helpserv_notice(user, "MSG_NONE");
1477         return 1;
1478     }
1479     qsort(users.list, users.used, sizeof(users.list[0]), helpserv_user_comp);
1480     switch (user->handle_info->userlist_style) {
1481     case HI_STYLE_DEF:
1482         tbl.length = users.used + 1;
1483         tbl.width = 3;
1484         tbl.flags = TABLE_NO_FREE;
1485         tbl.contents = alloca(tbl.length * sizeof(tbl.contents[0]));
1486         tbl.contents[0] = alloca(tbl.width * sizeof(tbl.contents[0][0]));
1487         tbl.contents[0][0] = "Level";
1488         tbl.contents[0][1] = "Handle";
1489         tbl.contents[0][2] = "WeekStart";
1490         for (ii = 0; ii < users.used; ) {
1491             hs_user = users.list[ii++];
1492             tbl.contents[ii] = alloca(tbl.width * sizeof(tbl.contents[0][0]));
1493             tbl.contents[ii][0] = helpserv_level_names[hs_user->level];
1494             tbl.contents[ii][1] = hs_user->handle->handle;
1495             tbl.contents[ii][2] = weekday_names[hs_user->week_start];
1496         }
1497         table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
1498         break;
1499     case HI_STYLE_ZOOT: default:
1500         last_level = HlNone;
1501         tbl.length = 0;
1502         tbl.width = 1;
1503         tbl.flags = TABLE_NO_FREE | TABLE_REPEAT_ROWS | TABLE_NO_HEADERS;
1504         tbl.contents = alloca(users.used * sizeof(tbl.contents[0]));
1505         for (ii = 0; ii < users.used; ) {
1506             hs_user = users.list[ii++];
1507             if (hs_user->level != last_level) {
1508                 if (tbl.length) {
1509                     helpserv_notice(user, "HSMSG_USERLIST_ZOOT_LVL", hs->helpserv->nick, helpserv_level_names[last_level]);
1510                     table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
1511                     tbl.length = 0;
1512                 }
1513                 last_level = hs_user->level;
1514             }
1515             tbl.contents[tbl.length] = alloca(tbl.width * sizeof(tbl.contents[0][0]));
1516             tbl.contents[tbl.length++][0] = hs_user->handle->handle;
1517         }
1518         if (tbl.length) {
1519             helpserv_notice(user, "HSMSG_USERLIST_ZOOT_LVL", hs->helpserv->nick, helpserv_level_names[last_level]);
1520             table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
1521         }
1522     }
1523     return 1;
1524 }
1525
1526 static HELPSERV_FUNC(cmd_helpers) {
1527     return show_helper_range(user, hs, from_opserv, HlTrial, HlOwner);
1528 }
1529
1530 static HELPSERV_FUNC(cmd_wlist) {
1531     return show_helper_range(user, hs, from_opserv, HlOwner, HlOwner);
1532 }
1533
1534 static HELPSERV_FUNC(cmd_mlist) {
1535     return show_helper_range(user, hs, from_opserv, HlManager, HlManager);
1536 }
1537
1538 static HELPSERV_FUNC(cmd_hlist) {
1539     return show_helper_range(user, hs, from_opserv, HlHelper, HlHelper);
1540 }
1541
1542 static HELPSERV_FUNC(cmd_tlist) {
1543     return show_helper_range(user, hs, from_opserv, HlTrial, HlTrial);
1544 }
1545
1546 static HELPSERV_FUNC(cmd_addowner) {
1547     return cmd_add_user(hs, from_opserv, user, HlOwner, argc, argv);
1548 }
1549
1550 static HELPSERV_FUNC(cmd_addmanager) {
1551     return cmd_add_user(hs, from_opserv, user, HlManager, argc, argv);
1552 }
1553
1554 static HELPSERV_FUNC(cmd_addhelper) {
1555     return cmd_add_user(hs, from_opserv, user, HlHelper, argc, argv);
1556 }
1557
1558 static HELPSERV_FUNC(cmd_addtrial) {
1559     return cmd_add_user(hs, from_opserv, user, HlTrial, argc, argv);
1560 }
1561
1562 static HELPSERV_FUNC(cmd_clvl) {
1563     struct helpserv_user *actor=NULL, *victim;
1564     struct handle_info *handle;
1565     enum helpserv_level level;
1566
1567     REQUIRE_PARMS(3);
1568
1569     if (!from_opserv) {
1570         actor = GetHSUser(hs, user->handle_info);
1571         if (actor->level < HlManager) {
1572             helpserv_notice(user, "HSMSG_CANNOT_CLVL");
1573             return 0;
1574         }
1575     }
1576
1577     if (!(handle = helpserv_get_handle_info(user, argv[1])))
1578         return 0;
1579
1580     if (!(victim = GetHSUser(hs, handle))) {
1581         helpserv_notice(user, "HSMSG_NOT_IN_USERLIST", handle->handle, hs->helpserv->nick);
1582         return 0;
1583     }
1584
1585     if (((level = helpserv_str2level(argv[2])) == HlNone) || level == HlOper) {
1586         helpserv_notice(user, "HSMSG_INVALID_ACCESS", argv[2]);
1587         return 0;
1588     }
1589
1590     if (!(from_opserv) && actor) {
1591         if (actor == victim) {
1592             helpserv_notice(user, "HSMSG_NO_SELF_CLVL");
1593             return 0;
1594         }
1595
1596         if (actor->level <= victim->level) {
1597             helpserv_notice(user, "MSG_USER_OUTRANKED", victim->handle->handle);
1598             return 0;
1599         }
1600
1601         if (level >= actor->level) {
1602             helpserv_notice(user, "HSMSG_NO_BUMP_ACCESS");
1603             return 0;
1604         }
1605     }
1606
1607     victim->level = level;
1608     helpserv_notice(user, "HSMSG_CHANGED_ACCESS", handle->handle, helpserv_level2str(level));
1609
1610     return 1;
1611 }
1612
1613 static void free_request(void *data) {
1614     struct helpserv_request *req = data;
1615
1616     /* Logging */
1617     if (shutting_down && (req->hs->persist_lengths[PERSIST_T_REQUEST] != PERSIST_CLOSE || !req->handle)) {
1618         helpserv_log_request(req, "srvx shutdown");
1619     }
1620
1621     /* Clean up from the unhandled queue */
1622     if (req->hs->unhandled) {
1623         if (req->hs->unhandled == req) {
1624             req->hs->unhandled = req->next_unhandled;
1625         } else {
1626             struct helpserv_request *uh;
1627             for (uh=req->hs->unhandled; uh->next_unhandled && (uh->next_unhandled != req); uh = uh->next_unhandled);
1628             if (uh->next_unhandled) {
1629                 uh->next_unhandled = req->next_unhandled;
1630             }
1631         }
1632     }
1633
1634     /* Clean up the lists */
1635     if (req->parent_nick_list) {
1636         if (req->parent_nick_list->used == 1) {
1637             dict_remove(helpserv_reqs_bynick_dict, req->user->nick);
1638         } else {
1639             helpserv_reqlist_remove(req->parent_nick_list, req);
1640         }
1641     }
1642     if (req->parent_hand_list) {
1643         if (req->parent_hand_list->used == 1) {
1644             dict_remove(helpserv_reqs_byhand_dict, req->handle->handle);
1645         } else {
1646             helpserv_reqlist_remove(req->parent_hand_list, req);
1647         }
1648     }
1649
1650     free_string_list(req->text);
1651     free(req);
1652 }
1653
1654 static HELPSERV_FUNC(cmd_close) {
1655     struct helpserv_request *req, *newest=NULL;
1656     struct helpserv_reqlist *nick_list, *hand_list;
1657     struct helpserv_user *hs_user=GetHSUser(hs, user->handle_info);
1658     struct userNode *req_user=NULL;
1659     char close_reason[MAXLEN], reqnum[12];
1660     unsigned long old_req;
1661     unsigned int i;
1662     int num_requests=0;
1663
1664     REQUIRE_PARMS(2);
1665
1666     assert(hs_user);
1667
1668     if (!(req = smart_get_request(hs, hs_user, argv[1], &num_requests))) {
1669         helpserv_notice(user, "HSMSG_REQ_INVALID", argv[1]);
1670         return 0;
1671     }
1672
1673     sprintf(reqnum, "%lu", req->id);
1674
1675     if (num_requests > 1)
1676         helpserv_notice(user, "HSMSG_REQ_FOUNDMANY");
1677
1678     if (hs_user->level < HlManager && req->helper != hs_user) {
1679         if (req->helper)
1680             helpserv_notice(user, "HSMSG_REQ_NOT_YOURS_ASSIGNED_TO", req->id, req->helper->handle->handle);
1681         else
1682             helpserv_notice(user, "HSMSG_REQ_NOT_YOURS_UNASSIGNED", req->id);
1683         return 0;
1684     }
1685
1686     helpserv_notice(user, "HSMSG_REQ_CLOSED", req->id);
1687     if (req->user) {
1688         req_user = req->user;
1689         helpserv_message(hs, req->user, MSGTYPE_REQ_CLOSED);
1690         if (req->handle)
1691             helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_CLOSE_REQUEST_1", req->id, req->user->nick, req->handle->handle, user->nick);
1692         else
1693             helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_CLOSE_REQUEST_2", req->id, req->user->nick, user->nick);
1694     } else {
1695         if (req->handle)
1696             helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_CLOSE_REQUEST_3", req->id, req->handle->handle, user->nick);
1697         else
1698             helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_CLOSE_REQUEST_4", req->id, user->nick);
1699     }
1700
1701     hs_user->closed[0]++;
1702     hs_user->closed[4]++;
1703
1704     /* Set these to keep track of the lists after the request is gone, but
1705      * not if free_request() will helpserv_reqlist_free() them. */
1706     nick_list = req->parent_nick_list;
1707     if (nick_list && (nick_list->used == 1))
1708         nick_list = NULL;
1709     hand_list = req->parent_hand_list;
1710     if (hand_list && (hand_list->used == 1))
1711         hand_list = NULL;
1712     old_req = req->id;
1713
1714     if (argc >= 3) {
1715         snprintf(close_reason, MAXLEN, "Closed by %s: %s", user->handle_info->handle, unsplit_string(argv+2, argc-2, NULL));
1716     } else {
1717         sprintf(close_reason, "Closed by %s", user->handle_info->handle);
1718     }
1719     helpserv_log_request(req, close_reason);
1720     dict_remove(hs->requests, reqnum);
1721
1722     /* Look for other requests associated with them */
1723     if (nick_list) {
1724         for (i=0; i < nick_list->used; i++) {
1725             req = nick_list->list[i];
1726
1727             if (req->hs != hs)
1728                 continue;
1729             if (!newest || (newest->opened < req->opened))
1730                 newest = req;
1731         }
1732
1733         if (newest)
1734             helpserv_msguser(newest->user, "HSMSG_REQ_FOUND_ANOTHER", old_req, newest->id);
1735     }
1736
1737     if (req_user && hs->auto_devoice) {
1738         struct modeNode *mn = GetUserMode(hs->helpchan, req_user);
1739         if ((!newest || !newest->helper) && mn && (mn->modes & MODE_VOICE)) {
1740             struct mod_chanmode change;
1741             mod_chanmode_init(&change);
1742             change.argc = 1;
1743             change.args[0].mode = MODE_REMOVE | MODE_VOICE;
1744             change.args[0].u.member = mn;
1745             mod_chanmode_announce(hs->helpserv, hs->helpchan, &change);
1746         }
1747     }
1748
1749     return 1;
1750 }
1751
1752 static HELPSERV_FUNC(cmd_list) {
1753     dict_iterator_t it;
1754     int searchtype;
1755     struct helpfile_table tbl;
1756     unsigned int line, total;
1757     struct helpserv_request *req;
1758
1759     if ((argc < 2) || !irccasecmp(argv[1], "unassigned")) {
1760         for (req = hs->unhandled, total=0; req; req = req->next_unhandled, total++) ;
1761         helpserv_notice(user, "HSMSG_REQ_LIST_TOP_UNASSIGNED", total);
1762         searchtype = 1; /* Unassigned */
1763     } else if (!irccasecmp(argv[1], "assigned")) {
1764         for (req = hs->unhandled, total=dict_size(hs->requests); req; req = req->next_unhandled, total--) ;
1765         helpserv_notice(user, "HSMSG_REQ_LIST_TOP_ASSIGNED", total);
1766         searchtype = 2; /* Assigned */
1767     } else if (!irccasecmp(argv[1], "me")) {
1768         for (total = 0, it = dict_first(hs->requests); it; it = iter_next(it)) {
1769             req = iter_data(it);
1770             if (req->helper && (req->helper->handle == user->handle_info))
1771                 total++;
1772         }
1773         helpserv_notice(user, "HSMSG_REQ_LIST_TOP_YOUR", total);
1774         searchtype = 4;
1775     } else if (!irccasecmp(argv[1], "all")) {
1776         total = dict_size(hs->requests);
1777         helpserv_notice(user, "HSMSG_REQ_LIST_TOP_ALL", total);
1778         searchtype = 3; /* All */
1779     } else {
1780         helpserv_notice(user, "HSMSG_BAD_REQ_TYPE", argv[1]);
1781         return 0;
1782     }
1783
1784     if (!total) {
1785         helpserv_notice(user, "HSMSG_REQ_LIST_NONE");
1786         return 1;
1787     }
1788
1789     tbl.length = total+1;
1790     tbl.width = 5;
1791     tbl.flags = TABLE_NO_FREE;
1792     tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
1793     tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
1794     tbl.contents[0][0] = "ID#";
1795     tbl.contents[0][1] = "User";
1796     tbl.contents[0][2] = "Helper";
1797     tbl.contents[0][3] = "Time open";
1798     tbl.contents[0][4] = "User status";
1799
1800     for (it=dict_first(hs->requests), line=0; it; it=iter_next(it)) {
1801         char opentime[INTERVALLEN], reqid[12], username[NICKLEN+2];
1802
1803         req = iter_data(it);
1804
1805         switch (searchtype) {
1806         case 1:
1807             if (req->helper)
1808                 continue;
1809             break;
1810         case 2:
1811             if (!req->helper)
1812                 continue;
1813             break;
1814         case 3:
1815         default:
1816             break;
1817         case 4:
1818             if (!req->helper || (req->helper->handle != user->handle_info))
1819                 continue;
1820             break;
1821         }
1822
1823         line++;
1824
1825         tbl.contents[line] = alloca(tbl.width * sizeof(**tbl.contents));
1826         sprintf(reqid, "%lu", req->id);
1827         tbl.contents[line][0] = strdup(reqid);
1828         if (req->user) {
1829             strcpy(username, req->user->nick);
1830         } else {
1831             username[0] = '*';
1832             strcpy(username+1, req->handle->handle);
1833         }
1834         tbl.contents[line][1] = strdup(username);
1835         tbl.contents[line][2] = req->helper ? req->helper->handle->handle : "(Unassigned)";
1836         intervalString(opentime, now - req->opened, user->handle_info);
1837         tbl.contents[line][3] = strdup(opentime);
1838         tbl.contents[line][4] = ((req->user || req->handle->users) ? "Online" : "Offline");
1839     }
1840
1841     table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
1842
1843     for (; line > 0; line--) {
1844         free((char *)tbl.contents[line][0]);
1845         free((char *)tbl.contents[line][1]);
1846         free((char *)tbl.contents[line][3]);
1847     }
1848
1849     return 1;
1850 }
1851
1852 static void helpserv_show(int from_opserv, struct helpserv_bot *hs, struct userNode *user, struct helpserv_request *req) {
1853     unsigned int nn;
1854     char buf[MAX_LINE_SIZE];
1855     char buf2[INTERVALLEN];
1856     time_t feh;
1857
1858     if (req->user)
1859         if (req->handle)
1860             helpserv_notice(user, "HSMSG_REQ_INFO_2a", req->user->nick, req->handle->handle);
1861         else
1862             helpserv_notice(user, "HSMSG_REQ_INFO_2b", req->user->nick);
1863     else if (req->handle)
1864         if (req->handle->users)
1865             helpserv_notice(user, "HSMSG_REQ_INFO_2c", req->handle->handle);
1866         else
1867             helpserv_notice(user, "HSMSG_REQ_INFO_2d", req->handle->handle);
1868     else
1869         helpserv_notice(user, "HSMSG_REQ_INFO_2e");
1870     feh = req->opened;
1871     strftime(buf, MAX_LINE_SIZE, HSFMT_TIME, localtime(&feh));
1872     intervalString(buf2, now - req->opened, user->handle_info);
1873     helpserv_notice(user, "HSMSG_REQ_INFO_3", buf, buf2);
1874     helpserv_notice(user, "HSMSG_REQ_INFO_4");
1875     for (nn=0; nn < req->text->used; nn++)
1876         helpserv_notice(user, "HSMSG_REQ_INFO_MESSAGE", req->text->list[nn]);
1877 }
1878
1879 /* actor is the one who executed the command... it should == user except from
1880  * cmd_assign */
1881 static int helpserv_assign(int from_opserv, struct helpserv_bot *hs, struct userNode *user, struct userNode *actor, struct helpserv_request *req) {
1882     struct helpserv_request *req2;
1883     struct helpserv_user *old_helper;
1884
1885     if (!user->handle_info)
1886         return 0;
1887     if ((hs->persist_lengths[PERSIST_T_HELPER] == PERSIST_PART) && !GetUserMode(hs->helpchan, user)) {
1888         struct helpserv_user *hsuser_actor = GetHSUser(hs, actor->handle_info);
1889         if (hsuser_actor->level < HlManager) {
1890             helpserv_notice(user, "HSMSG_REQ_YOU_NOT_IN_HELPCHAN", hs->helpchan->name);
1891             return 0;
1892         } else if (user != actor) {
1893             helpserv_notice(user, "HSMSG_REQ_YOU_NOT_IN_OVERRIDE", hs->helpchan->name);
1894             helpserv_notice(actor, "HSMSG_REQ_HIM_NOT_IN_OVERRIDE", user->nick, hs->helpchan->name);
1895         } else
1896             helpserv_notice(user, "HSMSG_REQ_SELF_NOT_IN_OVERRIDE", hs->helpchan->name);
1897     }
1898
1899     hs->last_active = now;
1900     if ((old_helper = req->helper)) {
1901         /* don't need to remove from the unhandled queue */
1902     } else if (hs->unhandled == req) {
1903         hs->unhandled = req->next_unhandled;
1904     } else for (req2 = hs->unhandled; req2; req2 = req2->next_unhandled) {
1905         if (req2->next_unhandled == req) {
1906             req2->next_unhandled = req->next_unhandled;
1907             break;
1908         }
1909     }
1910     req->next_unhandled = NULL;
1911     req->helper = GetHSUser(hs, user->handle_info);
1912     assert(req->helper);
1913     req->assigned = now;
1914     
1915     if (req->user && hs->auto_join) {
1916         irc_svsjoin(hs->helpserv,req->user,hs->helpchan);
1917     }
1918     
1919     if (old_helper) {
1920         helpserv_notice(user, "HSMSG_REQ_REASSIGNED", req->id, old_helper->handle->handle);
1921         req->helper->reassigned_to[0]++;
1922         req->helper->reassigned_to[4]++;
1923         old_helper->reassigned_from[0]++;
1924         old_helper->reassigned_from[4]++;
1925     } else {
1926         helpserv_notice(user, "HSMSG_REQ_ASSIGNED_YOU", req->id);
1927         req->helper->picked_up[0]++;
1928         req->helper->picked_up[4]++;
1929     }
1930     helpserv_show(from_opserv, hs, user, req);
1931     if (req->user) {
1932         helpserv_message(hs, req->user, MSGTYPE_REQ_ASSIGNED);
1933         if (old_helper) {
1934             helpserv_msguser(req->user, "HSMSG_REQ_ASSIGNED_AGAIN", req->id, user->handle_info->handle, user->nick);
1935         } else {
1936             helpserv_msguser(req->user, "HSMSG_REQ_ASSIGNED", req->id, user->handle_info->handle, user->nick);
1937         }
1938         if (req->handle)
1939             helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_ASSIGN_REQUEST_1", req->id, req->user->nick, req->handle->handle, user->nick);
1940         else
1941             helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_ASSIGN_REQUEST_2", req->id, req->user->nick, user->nick);
1942     } else {
1943         if (req->handle)
1944             helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_ASSIGN_REQUEST_3", req->id, req->handle->handle, user->nick);
1945         else
1946             helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_ASSIGN_REQUEST_4", req->id, user->nick);
1947     }
1948
1949     if (req->user && hs->auto_voice) {
1950         struct mod_chanmode change;
1951         mod_chanmode_init(&change);
1952         change.argc = 1;
1953         change.args[0].mode = MODE_VOICE;
1954         if ((change.args[0].u.member = GetUserMode(hs->helpchan, req->user)))
1955             mod_chanmode_announce(hs->helpserv, hs->helpchan, &change);
1956     }
1957     
1958     return 1;
1959 }
1960
1961 static HELPSERV_FUNC(cmd_next) {
1962     struct helpserv_request *req;
1963
1964     if (!(req = hs->unhandled)) {
1965         helpserv_notice(user, "HSMSG_REQ_NO_UNASSIGNED");
1966         return 0;
1967     }
1968     return helpserv_assign(from_opserv, hs, user, user, req);
1969 }
1970
1971 static HELPSERV_FUNC(cmd_show) {
1972     struct helpserv_request *req;
1973     struct helpserv_user *hs_user=GetHSUser(hs, user->handle_info);
1974     int num_requests=0;
1975
1976     REQUIRE_PARMS(2);
1977
1978     if (!(req = smart_get_request(hs, hs_user, argv[1], &num_requests))) {
1979         helpserv_notice(user, "HSMSG_REQ_INVALID", argv[1]);
1980         return 0;
1981     }
1982
1983     if (num_requests > 1)
1984         helpserv_notice(user, "HSMSG_REQ_FOUNDMANY");
1985
1986     helpserv_notice(user, "HSMSG_REQ_INFO_1", req->id);
1987     helpserv_show(from_opserv, hs, user, req);
1988     return 1;
1989 }
1990
1991 static HELPSERV_FUNC(cmd_pickup) {
1992     struct helpserv_request *req;
1993     struct helpserv_user *hs_user=GetHSUser(hs, user->handle_info);
1994     int num_requests=0;
1995
1996     REQUIRE_PARMS(2);
1997
1998     assert(hs_user);
1999
2000     if (!(req = smart_get_request(hs, hs_user, argv[1], &num_requests))) {
2001         helpserv_notice(user, "HSMSG_REQ_INVALID", argv[1]);
2002         return 0;
2003     }
2004
2005     if (num_requests > 1)
2006         helpserv_notice(user, "HSMSG_REQ_FOUNDMANY");
2007
2008     return helpserv_assign(from_opserv, hs, user, user, req);
2009 }
2010
2011 static HELPSERV_FUNC(cmd_reassign) {
2012     struct helpserv_request *req;
2013     struct userNode *targetuser;
2014     struct helpserv_user *target;
2015     struct helpserv_user *hs_user=GetHSUser(hs, user->handle_info);
2016     int num_requests=0;
2017
2018     REQUIRE_PARMS(3);
2019
2020     assert(hs_user);
2021
2022     if (!(req = smart_get_request(hs, hs_user, argv[1], &num_requests))) {
2023         helpserv_notice(user, "HSMSG_REQ_INVALID", argv[1]);
2024         return 0;
2025     }
2026
2027     if (num_requests > 1)
2028         helpserv_notice(user, "HSMSG_REQ_FOUNDMANY");
2029
2030     if (!(targetuser = GetUserH(argv[2]))) {
2031         helpserv_notice(user, "MSG_NICK_UNKNOWN", argv[2]);
2032         return 0;
2033     }
2034
2035     if (!targetuser->handle_info) {
2036         helpserv_notice(user, "MSG_USER_AUTHENTICATE", targetuser->nick);
2037         return 0;
2038     }
2039
2040     if (!(target = GetHSUser(hs, targetuser->handle_info))) {
2041         helpserv_notice(user, "HSMSG_NOT_IN_USERLIST", targetuser->nick, hs->helpserv->nick);
2042         return 0;
2043     }
2044
2045     if ((hs->persist_lengths[PERSIST_T_HELPER] == PERSIST_PART) && !GetUserMode(hs->helpchan, user) && (hs_user->level < HlManager)) {
2046         helpserv_notice(user, "HSMSG_REQ_HIM_NOT_IN_HELPCHAN", targetuser->nick, hs->helpchan->name);
2047         return 0;
2048     }
2049
2050     helpserv_assign(from_opserv, hs, targetuser, user, req);
2051     return 1;
2052 }
2053
2054 static HELPSERV_FUNC(cmd_addnote) {
2055     char text[MAX_LINE_SIZE], timestr[MAX_LINE_SIZE], *note;
2056     struct helpserv_request *req;
2057     struct helpserv_user *hs_user=GetHSUser(hs, user->handle_info);
2058     int num_requests=0;
2059     time_t feh;
2060
2061     REQUIRE_PARMS(3);
2062
2063     if (!(req = smart_get_request(hs, hs_user, argv[1], &num_requests))) {
2064         helpserv_notice(user, "HSMSG_REQ_INVALID", argv[1]);
2065         return 0;
2066     }
2067
2068     if (num_requests > 1)
2069         helpserv_notice(user, "HSMSG_REQ_FOUNDMANY");
2070
2071     note = unsplit_string(argv+2, argc-2, NULL);
2072
2073     feh = now;
2074     strftime(timestr, MAX_LINE_SIZE, HSFMT_TIME, localtime(&feh));
2075     snprintf(text, MAX_LINE_SIZE, "[Helper note at %s]:", timestr);
2076     string_list_append(req->text, strdup(text));
2077     snprintf(text, MAX_LINE_SIZE, "  <%s> %s", user->handle_info->handle, note);
2078     string_list_append(req->text, strdup(text));
2079
2080     helpserv_notice(user, "HSMSG_REQMSG_NOTE_ADDED", req->id);
2081
2082     return 1;
2083 }
2084
2085 static HELPSERV_FUNC(cmd_page) {
2086     REQUIRE_PARMS(2);
2087
2088     helpserv_page(PGSRC_COMMAND, "HSMSG_PAGE_REQUEST", user->nick, unsplit_string(argv+1, argc-1, NULL));
2089
2090     return 1;
2091 }
2092
2093 static HELPSERV_FUNC(cmd_stats) {
2094     struct helpserv_user *target, *hs_user;
2095     struct handle_info *target_handle;
2096     struct helpfile_table tbl;
2097     int i;
2098     char intervalstr[INTERVALLEN], buf[16];
2099
2100     hs_user = from_opserv ? NULL : GetHSUser(hs, user->handle_info);
2101
2102     if (argc > 1) {
2103         if (!from_opserv && (hs_user->level < HlManager)) {
2104             helpserv_notice(user, "HSMSG_NEED_MANAGER");
2105             return 0;
2106         }
2107
2108         if (!(target_handle = helpserv_get_handle_info(user, argv[1]))) {
2109             return 0;
2110         }
2111
2112         if (!(target = GetHSUser(hs, target_handle))) {
2113             helpserv_notice(user, "HSMSG_NOT_IN_USERLIST", target_handle->handle, hs->helpserv->nick);
2114             return 0;
2115         }
2116     } else {
2117         if (from_opserv) {
2118             helpserv_notice(user, "HSMSG_OPSERV_NEED_USER");
2119             return 0;
2120         }
2121         target = hs_user;
2122     }
2123
2124     helpserv_notice(user, "HSMSG_STATS_TOP", hs->helpserv->nick, target->handle->handle, weekday_names[target->week_start]);
2125
2126     tbl.length = 6;
2127     tbl.width = 2;
2128     tbl.flags = TABLE_NO_FREE;
2129     tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
2130     tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
2131     tbl.contents[0][0] = "";
2132     tbl.contents[0][1] = "Recorded time";
2133     for (i=0; i < 5; i++) {
2134         unsigned int week_time = target->time_per_week[i];
2135         tbl.contents[i+1] = alloca(tbl.width * sizeof(**tbl.contents));
2136         if ((i == 0 || i == 4) && target->join_time)
2137             week_time += now - target->join_time;
2138         helpserv_interval(intervalstr, week_time);
2139         tbl.contents[i+1][1] = strdup(intervalstr);
2140     }
2141     tbl.contents[1][0] = "This week";
2142     tbl.contents[2][0] = "Last week";
2143     tbl.contents[3][0] = "2 weeks ago";
2144     tbl.contents[4][0] = "3 weeks ago";
2145     tbl.contents[5][0] = "Total";
2146
2147     helpserv_notice(user, "HSMSG_STATS_TIME", hs->helpchan->name);
2148     table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
2149
2150     for (i=1; i <= 5; i++)
2151         free((char *)tbl.contents[i][1]);
2152
2153     tbl.length = 5;
2154     tbl.width = 4;
2155     tbl.flags = TABLE_NO_FREE;
2156     tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
2157     tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
2158     tbl.contents[1] = alloca(tbl.width * sizeof(**tbl.contents));
2159     tbl.contents[2] = alloca(tbl.width * sizeof(**tbl.contents));
2160     tbl.contents[3] = alloca(tbl.width * sizeof(**tbl.contents));
2161     tbl.contents[4] = alloca(tbl.width * sizeof(**tbl.contents));
2162     tbl.contents[0][0] = "Category";
2163     tbl.contents[0][1] = "This week";
2164     tbl.contents[0][2] = "Last week";
2165     tbl.contents[0][3] = "Total";
2166
2167     tbl.contents[1][0] = "Requests picked up";
2168     for (i=0; i < 3; i++) {
2169         sprintf(buf, "%u", target->picked_up[(i == 2 ? 4 : i)]);
2170         tbl.contents[1][i+1] = strdup(buf);
2171     }
2172     tbl.contents[2][0] = "Requests closed";
2173     for (i=0; i < 3; i++) {
2174         sprintf(buf, "%u", target->closed[(i == 2 ? 4 : i)]);
2175         tbl.contents[2][i+1] = strdup(buf);
2176     }
2177     tbl.contents[3][0] = "Reassigned from";
2178     for (i=0; i < 3; i++) {
2179         sprintf(buf, "%u", target->reassigned_from[(i == 2 ? 4 : i)]);
2180         tbl.contents[3][i+1] = strdup(buf);
2181     }
2182     tbl.contents[4][0] = "Reassigned to";
2183     for (i=0; i < 3; i++) {
2184         sprintf(buf, "%u", target->reassigned_to[(i == 2 ? 4 : i)]);
2185         tbl.contents[4][i+1] = strdup(buf);
2186     }
2187
2188     helpserv_notice(user, "HSMSG_STATS_REQS");
2189     table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
2190
2191     for (i=1; i < 5; i++) {
2192         free((char *)tbl.contents[i][1]);
2193         free((char *)tbl.contents[i][2]);
2194         free((char *)tbl.contents[i][3]);
2195     }
2196
2197     return 1;
2198 }
2199
2200 static HELPSERV_FUNC(cmd_statsreport) {
2201     int use_privmsg=1;
2202     struct helpfile_table tbl;
2203     dict_iterator_t it;
2204     unsigned int line, i;
2205     struct userNode *srcbot = from_opserv ? opserv : hs->helpserv;
2206
2207     if ((argc > 1) && !irccasecmp(argv[1], "NOTICE"))
2208         use_privmsg = 0;
2209
2210     tbl.length = dict_size(hs->users)+1;
2211     tbl.width = 3;
2212     tbl.flags = TABLE_NO_FREE;
2213     tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
2214     tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
2215     tbl.contents[0][0] = "Account";
2216     tbl.contents[0][1] = "Requests";
2217     tbl.contents[0][2] = "Time helping";
2218
2219     for (it=dict_first(hs->users), line=0; it; it=iter_next(it)) {
2220         struct helpserv_user *hs_user=iter_data(it);
2221
2222         tbl.contents[++line] = alloca(tbl.width * sizeof(**tbl.contents));
2223         tbl.contents[line][0] = hs_user->handle->handle;
2224         tbl.contents[line][1] = malloc(12);
2225         tbl.contents[line][2] = malloc(32); /* A bit more than needed */
2226     }
2227
2228     /* 4 to 1 instead of 3 to 0 because it's unsigned */
2229     for (i=4; i > 0; i--) {
2230         for (it=dict_first(hs->users), line=0; it; it=iter_next(it)) {
2231             struct helpserv_user *hs_user = iter_data(it);
2232             /* Time */
2233             unsigned int week_time = hs_user->time_per_week[i-1];
2234             if ((i==1) && hs_user->join_time)
2235                 week_time += now - hs_user->join_time;
2236             helpserv_interval((char *)tbl.contents[++line][2], week_time);
2237
2238             /* Requests */
2239             sprintf((char *)tbl.contents[line][1], "%u", hs_user->picked_up[i-1]+hs_user->reassigned_to[i-1]);
2240         }
2241         send_target_message(use_privmsg, user->nick, srcbot, statsreport_week[i-1]);
2242         table_send(srcbot, user->nick, 0, (use_privmsg ? irc_privmsg : irc_notice), tbl);
2243     }
2244
2245     for (line=1; line <= dict_size(hs->users); line++) {
2246         free((char *)tbl.contents[line][1]);
2247         free((char *)tbl.contents[line][2]);
2248     }
2249
2250     return 1;
2251 }
2252
2253 static int
2254 helpserv_in_channel(struct helpserv_bot *hs, struct chanNode *channel) {
2255     enum page_source pgsrc;
2256     if (channel == hs->helpchan)
2257         return 1;
2258     for (pgsrc=0; pgsrc<PGSRC_COUNT; pgsrc++)
2259         if (channel == hs->page_targets[pgsrc])
2260             return 1;
2261     return 0;
2262 }
2263
2264 static HELPSERV_FUNC(cmd_move) {
2265     if (!hs) {
2266         helpserv_notice(user, "HSMSG_INVALID_BOT");
2267         return 0;
2268     }
2269
2270     REQUIRE_PARMS(2);
2271
2272     if (is_valid_nick(argv[1])) {
2273         char *newnick = argv[1], oldnick[NICKLEN], reason[MAXLEN];
2274
2275         strcpy(oldnick, hs->helpserv->nick);
2276
2277         if (GetUserH(newnick)) {
2278             helpserv_notice(user, "HSMSG_NICK_EXISTS", newnick);
2279             return 0;
2280         }
2281
2282         dict_remove2(helpserv_bots_dict, hs->helpserv->nick, 1);
2283         NickChange(hs->helpserv, newnick, 0);
2284         dict_insert(helpserv_bots_dict, hs->helpserv->nick, hs);
2285
2286         helpserv_notice(user, "HSMSG_RENAMED", oldnick, newnick);
2287
2288         snprintf(reason, MAXLEN, "HelpServ bot %s (in %s) renamed to %s by %s.", oldnick, hs->helpchan->name, newnick, user->nick);
2289         global_message(MESSAGE_RECIPIENT_OPERS, reason);
2290
2291         return 1;
2292     } else if (IsChannelName(argv[1])) {
2293         struct chanNode *old_helpchan = hs->helpchan;
2294         char *newchan = argv[1], oldchan[CHANNELLEN], reason[MAXLEN];
2295         struct helpserv_botlist *botlist;
2296
2297         strcpy(oldchan, hs->helpchan->name);
2298
2299         if (!irccasecmp(oldchan, newchan)) {
2300             helpserv_notice(user, "HSMSG_MOVE_SAME_CHANNEL", hs->helpserv->nick);
2301             return 0;
2302         }
2303
2304         if (opserv_bad_channel(newchan)) {
2305             helpserv_notice(user, "HSMSG_ILLEGAL_CHANNEL", newchan);
2306             return 0;
2307         }
2308
2309         botlist = dict_find(helpserv_bots_bychan_dict, hs->helpchan->name, NULL);
2310         helpserv_botlist_remove(botlist, hs);
2311         if (botlist->used == 0) {
2312             dict_remove(helpserv_bots_bychan_dict, hs->helpchan->name);
2313         }
2314
2315         hs->helpchan = NULL;
2316         if (!helpserv_in_channel(hs, old_helpchan)) {
2317             snprintf(reason, MAXLEN, "Moved to %s by %s.", newchan, user->nick);
2318             DelChannelUser(hs->helpserv, old_helpchan, reason, 0);
2319         }
2320
2321         if (!(hs->helpchan = GetChannel(newchan))) {
2322             hs->helpchan = AddChannel(newchan, now, NULL, NULL);
2323             AddChannelUser(hs->helpserv, hs->helpchan)->modes |= MODE_CHANOP;
2324         } else if (!helpserv_in_channel(hs, old_helpchan)) {
2325             struct mod_chanmode change;
2326             mod_chanmode_init(&change);
2327             change.argc = 1;
2328             change.args[0].mode = MODE_CHANOP;
2329             change.args[0].u.member = AddChannelUser(hs->helpserv, hs->helpchan);
2330             mod_chanmode_announce(hs->helpserv, hs->helpchan, &change);
2331         }
2332
2333         if (!(botlist = dict_find(helpserv_bots_bychan_dict, hs->helpchan->name, NULL))) {
2334             botlist = helpserv_botlist_alloc();
2335             dict_insert(helpserv_bots_bychan_dict, hs->helpchan->name, botlist);
2336         }
2337         helpserv_botlist_append(botlist, hs);
2338
2339         snprintf(reason, MAXLEN, "HelpServ %s (%s) moved to %s by %s.", hs->helpserv->nick, oldchan, newchan, user->nick);
2340         global_message(MESSAGE_RECIPIENT_OPERS, reason);
2341
2342         return 1;
2343     } else {
2344         helpserv_notice(user, "HSMSG_INVALID_MOVE", argv[1]);
2345         return 0;
2346     }
2347 }
2348
2349 static HELPSERV_FUNC(cmd_bots) {
2350     dict_iterator_t it;
2351     struct helpfile_table tbl;
2352     unsigned int i;
2353
2354     helpserv_notice(user, "HSMSG_BOTLIST_HEADER");
2355
2356     tbl.length = dict_size(helpserv_bots_dict)+1;
2357     tbl.width = 4;
2358     tbl.flags = TABLE_NO_FREE;
2359     tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
2360     tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
2361     tbl.contents[0][0] = "Bot";
2362     tbl.contents[0][1] = "Channel";
2363     tbl.contents[0][2] = "Owner";
2364     tbl.contents[0][3] = "Inactivity";
2365
2366     for (it=dict_first(helpserv_bots_dict), i=1; it; it=iter_next(it), i++) {
2367         dict_iterator_t it2;
2368         struct helpserv_bot *bot;
2369         struct helpserv_user *owner=NULL;
2370
2371         bot = iter_data(it);
2372
2373         for (it2=dict_first(bot->users); it2; it2=iter_next(it2)) {
2374             if (((struct helpserv_user *)iter_data(it2))->level == HlOwner) {
2375                 owner = iter_data(it2);
2376                 break;
2377             }
2378         }
2379
2380         tbl.contents[i] = alloca(tbl.width * sizeof(**tbl.contents));
2381         tbl.contents[i][0] = iter_key(it);
2382         tbl.contents[i][1] = bot->helpchan->name;
2383         tbl.contents[i][2] = owner ? owner->handle->handle : "None";
2384         tbl.contents[i][3] = alloca(INTERVALLEN);
2385         intervalString((char*)tbl.contents[i][3], now - bot->last_active, user->handle_info);
2386     }
2387
2388     table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
2389
2390     return 1;
2391 }
2392
2393 static void helpserv_page_helper_gone(struct helpserv_bot *hs, struct helpserv_request *req, const char *reason) {
2394     const int from_opserv = 0;
2395
2396     if (!req->helper)
2397         return;
2398
2399     /* Let the user know that their request is now unhandled */
2400     if (req->user) {
2401         struct modeNode *mn = GetUserMode(hs->helpchan, req->user);
2402         helpserv_msguser(req->user, "HSMSG_REQ_UNASSIGNED", req->id, reason);
2403         if (hs->auto_devoice && mn && (mn->modes & MODE_VOICE)) {
2404             struct mod_chanmode change;
2405             mod_chanmode_init(&change);
2406             change.argc = 1;
2407             change.args[0].mode = MODE_REMOVE | MODE_VOICE;
2408             change.args[0].u.member = mn;
2409             mod_chanmode_announce(hs->helpserv, hs->helpchan, &change);
2410         }
2411         if(req->handle)
2412             helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_HELPER_GONE_1", req->id, req->user->nick, req->handle->handle, req->helper->handle->handle, reason);
2413         else
2414             helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_HELPER_GONE_2", req->id, req->user->nick, req->helper->handle->handle, reason);
2415     } else {
2416         if(req->handle)
2417             helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_HELPER_GONE_3", req->id, req->handle->handle, req->helper->handle->handle, reason);
2418         else
2419             helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_HELPER_GONE_2", req->id, req->helper->handle->handle, reason);
2420     }
2421
2422     /* Now put it back in the queue */
2423     if (hs->unhandled == NULL) {
2424         /* Nothing there, put it at the front */
2425         hs->unhandled = req;
2426         req->next_unhandled = NULL;
2427     } else {
2428         /* Should it be at the front? */
2429         if (hs->unhandled->opened >= req->opened) {
2430             req->next_unhandled = hs->unhandled;
2431             hs->unhandled = req;
2432         } else {
2433             struct helpserv_request *unhandled;
2434             /* Find the request that this should be inserted AFTER */
2435             for (unhandled=hs->unhandled; unhandled->next_unhandled && (unhandled->next_unhandled->opened < req->opened); unhandled = unhandled->next_unhandled);
2436             req->next_unhandled = unhandled->next_unhandled;
2437             unhandled->next_unhandled = req;
2438         }
2439     }
2440
2441     req->helper = NULL;
2442 }
2443
2444 /* This takes care of WHINE_DELAY and IDLE_DELAY */
2445 static void run_whine_interval(void *data) {
2446     struct helpserv_bot *hs=data;
2447     struct helpfile_table tbl;
2448     unsigned int i;
2449
2450     /* First, run the WHINE_DELAY */
2451     if (hs->intervals[INTERVAL_WHINE_DELAY]
2452         && (hs->page_types[PGSRC_ALERT] != PAGE_NONE)
2453         && (hs->page_targets[PGSRC_ALERT] != NULL)
2454         && (!hs->intervals[INTERVAL_EMPTY_INTERVAL] || !hs->helpchan_empty)) {
2455         struct helpserv_request *unh;
2456         struct helpserv_reqlist reqlist;
2457         unsigned int queuesize=0;
2458
2459         helpserv_reqlist_init(&reqlist);
2460
2461         for (unh = hs->unhandled; unh; unh = unh->next_unhandled) {
2462             queuesize++;
2463             if ((now - unh->opened) >= hs->intervals[INTERVAL_WHINE_DELAY]) {
2464                 helpserv_reqlist_append(&reqlist, unh);
2465             }
2466         }
2467
2468         if (reqlist.used) {
2469             char strwhinedelay[INTERVALLEN];
2470
2471             intervalString(strwhinedelay, hs->intervals[INTERVAL_WHINE_DELAY], NULL);
2472 #if ANNOYING_ALERT_PAGES
2473             tbl.length = reqlist.used + 1;
2474             tbl.width = 4;
2475             tbl.flags = TABLE_NO_FREE;
2476             tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
2477             tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
2478             tbl.contents[0][0] = "ID#";
2479             tbl.contents[0][1] = "Nick";
2480             tbl.contents[0][2] = "Account";
2481             tbl.contents[0][3] = "Waiting time";
2482
2483             for (i=1; i <= reqlist.used; i++) {
2484                 char reqid[12], unh_time[INTERVALLEN];
2485                 unh = reqlist.list[i-1];
2486
2487                 tbl.contents[i] = alloca(tbl.width * sizeof(**tbl.contents));
2488                 sprintf(reqid, "%lu", unh->id);
2489                 tbl.contents[i][0] = strdup(reqid);
2490                 tbl.contents[i][1] = unh->user ? unh->user->nick : "Not online";
2491                 tbl.contents[i][2] = unh->handle ? unh->handle->handle : "Not authed";
2492                 intervalString(unh_time, now - unh->opened, NULL);
2493                 tbl.contents[i][3] = strdup(unh_time);
2494             }
2495
2496             helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_WHINE_HEADER", reqlist.used, strwhinedelay, queuesize, dict_size(hs->requests));
2497             table_send(hs->helpserv, hs->page_targets[PGSRC_ALERT]->name, 0, page_type_funcs[hs->page_types[PGSRC_ALERT]], tbl);
2498
2499             for (i=1; i <= reqlist.used; i++) {
2500                 free((char *)tbl.contents[i][0]);
2501                 free((char *)tbl.contents[i][3]);
2502             }
2503 #else
2504             helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_WHINE_HEADER", reqlist.used, strwhinedelay, queuesize, dict_size(hs->requests));
2505 #endif
2506         }
2507
2508         helpserv_reqlist_clean(&reqlist);
2509     }
2510
2511     /* Next run IDLE_DELAY */
2512     if (hs->intervals[INTERVAL_IDLE_DELAY]
2513         && (hs->page_types[PGSRC_STATUS] != PAGE_NONE)
2514         && (hs->page_targets[PGSRC_STATUS] != NULL)) {
2515         struct modeList mode_list;
2516
2517         modeList_init(&mode_list);
2518
2519         for (i=0; i < hs->helpchan->members.used; i++) {
2520             struct modeNode *mn = hs->helpchan->members.list[i];
2521             /* Ignore ops. Perhaps this should be a set option? */
2522             if (mn->modes & MODE_CHANOP)
2523                 continue;
2524             /* Check if they've been idle long enough */
2525             if ((unsigned)(now - mn->idle_since) < hs->intervals[INTERVAL_IDLE_DELAY])
2526                 continue;
2527             /* Add them to the list of idle people.. */
2528             modeList_append(&mode_list, mn);
2529         }
2530
2531         if (mode_list.used) {
2532             char stridledelay[INTERVALLEN];
2533
2534             tbl.length = mode_list.used + 1;
2535             tbl.width = 4;
2536             tbl.flags = TABLE_NO_FREE;
2537             tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
2538             tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
2539             tbl.contents[0][0] = "Nick";
2540             tbl.contents[0][1] = "Account";
2541             tbl.contents[0][2] = "ID#";
2542             tbl.contents[0][3] = "Idle time";
2543
2544             for (i=1; i <= mode_list.used; i++) {
2545                 char reqid[12], idle_time[INTERVALLEN];
2546                 struct helpserv_reqlist *reqlist;
2547                 struct modeNode *mn = mode_list.list[i-1];
2548
2549                 tbl.contents[i] = alloca(tbl.width * sizeof(**tbl.contents));
2550                 tbl.contents[i][0] = mn->user->nick;
2551                 tbl.contents[i][1] = mn->user->handle_info ? mn->user->handle_info->handle : "Not authed";
2552
2553                 if ((reqlist = dict_find(helpserv_reqs_bynick_dict, mn->user->nick, NULL))) {
2554                     int j;
2555
2556                     for (j = reqlist->used-1; j >= 0; j--) {
2557                         struct helpserv_request *req = reqlist->list[j];
2558
2559                         if (req->hs == hs) {
2560                             sprintf(reqid, "%lu", req->id);
2561                             break;
2562                         }
2563                     }
2564
2565                     if (j < 0)
2566                         strcpy(reqid, "None");
2567                 } else {
2568                     strcpy(reqid, "None");
2569                 }
2570                 tbl.contents[i][2] = strdup(reqid);
2571
2572                 intervalString(idle_time, now - mn->idle_since, NULL);
2573                 tbl.contents[i][3] = strdup(idle_time);
2574             }
2575
2576             intervalString(stridledelay, hs->intervals[INTERVAL_IDLE_DELAY], NULL);
2577             helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_IDLE_HEADER", mode_list.used, hs->helpchan->name, stridledelay);
2578             table_send(hs->helpserv, hs->page_targets[PGSRC_STATUS]->name, 0, page_types[hs->page_types[PGSRC_STATUS]].func, tbl);
2579
2580             for (i=1; i <= mode_list.used; i++) {
2581                 free((char *)tbl.contents[i][2]);
2582                 free((char *)tbl.contents[i][3]);
2583             }
2584         }
2585
2586         modeList_clean(&mode_list);
2587     }
2588
2589     if (hs->intervals[INTERVAL_WHINE_INTERVAL]) {
2590         timeq_add(now + hs->intervals[INTERVAL_WHINE_INTERVAL], run_whine_interval, hs);
2591     }
2592 }
2593
2594 /* Returns -1 if there's any helpers,
2595  * 0 if there are no helpers
2596  * >1 if there are trials (number of trials)
2597  */
2598 static int find_helpchan_helpers(struct helpserv_bot *hs) {
2599     int num_trials=0;
2600     dict_iterator_t it;
2601
2602     for (it=dict_first(hs->users); it; it=iter_next(it)) {
2603         struct helpserv_user *hs_user=iter_data(it);
2604
2605         if (find_handle_in_channel(hs->helpchan, hs_user->handle, NULL)) {
2606             if (hs_user->level >= HlHelper) {
2607                 hs->helpchan_empty = 0;
2608                 return -1;
2609             }
2610             num_trials++;
2611         }
2612     }
2613
2614     hs->helpchan_empty = 1;
2615     return num_trials;
2616 }
2617
2618
2619 static void run_empty_interval(void *data) {
2620     struct helpserv_bot *hs=data;
2621     int num_trials=find_helpchan_helpers(hs);
2622     unsigned int num_unh;
2623     struct helpserv_request *unh;
2624
2625     if (num_trials == -1)
2626         return;
2627     if (hs->req_on_join && !hs->unhandled)
2628         return;
2629
2630     for (num_unh=0, unh=hs->unhandled; unh; num_unh++)
2631         unh = unh->next_unhandled;
2632
2633     if (num_trials)
2634         helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_ONLYTRIALALERT", hs->helpchan->name, num_trials, num_unh);
2635     else
2636         helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_EMPTYALERT", hs->helpchan->name, num_unh);
2637
2638     if (hs->intervals[INTERVAL_EMPTY_INTERVAL])
2639         timeq_add(now + hs->intervals[INTERVAL_EMPTY_INTERVAL], run_empty_interval, hs);
2640 }
2641
2642 static void free_user(void *data) {
2643     struct helpserv_user *hs_user = data;
2644     struct helpserv_bot *hs = hs_user->hs;
2645     struct helpserv_userlist *userlist;
2646     dict_iterator_t it;
2647
2648     if (hs->requests) {
2649         for (it=dict_first(hs->requests); it; it=iter_next(it)) {
2650             struct helpserv_request *req = iter_data(it);
2651
2652             if (req->helper == hs_user)
2653                 helpserv_page_helper_gone(hs, req, "been deleted");
2654         }
2655     }
2656
2657     userlist = dict_find(helpserv_users_byhand_dict, hs_user->handle->handle, NULL);
2658     if (userlist->used == 1) {
2659         dict_remove(helpserv_users_byhand_dict, hs_user->handle->handle);
2660     } else {
2661         helpserv_userlist_remove(userlist, hs_user);
2662     }
2663
2664     free(data);
2665 }
2666
2667 static struct helpserv_bot *register_helpserv(const char *nick, const char *help_channel, const char *registrar) {
2668     struct helpserv_bot *hs;
2669     struct helpserv_botlist *botlist;
2670
2671     /* Laziness dictates calloc, since there's a lot to set to NULL or 0, and
2672      * it's a harmless default */
2673     hs = calloc(1, sizeof(struct helpserv_bot));
2674
2675     if (!(hs->helpserv = AddLocalUser(nick, nick, NULL, helpserv_conf.description, NULL))) {
2676         free(hs);
2677         return NULL;
2678     }
2679
2680     reg_privmsg_func(hs->helpserv, helpserv_botmsg);
2681
2682     if (!(hs->helpchan = GetChannel(help_channel))) {
2683         hs->helpchan = AddChannel(help_channel, now, NULL, NULL);
2684         AddChannelUser(hs->helpserv, hs->helpchan)->modes |= MODE_CHANOP;
2685     } else {
2686         struct mod_chanmode change;
2687         mod_chanmode_init(&change);
2688         change.argc = 1;
2689         change.args[0].mode = MODE_CHANOP;
2690         change.args[0].u.member = AddChannelUser(hs->helpserv, hs->helpchan);
2691         mod_chanmode_announce(hs->helpserv, hs->helpchan, &change);
2692     }
2693
2694     if (registrar)
2695         hs->registrar = strdup(registrar);
2696
2697     hs->users = dict_new();
2698     /* Don't free keys - they use the handle_info's handle field */
2699     dict_set_free_data(hs->users, free_user);
2700     hs->requests = dict_new();
2701     dict_set_free_keys(hs->requests, free);
2702     dict_set_free_data(hs->requests, free_request);
2703
2704     dict_insert(helpserv_bots_dict, hs->helpserv->nick, hs);
2705
2706     if (!(botlist = dict_find(helpserv_bots_bychan_dict, hs->helpchan->name, NULL))) {
2707         botlist = helpserv_botlist_alloc();
2708         dict_insert(helpserv_bots_bychan_dict, hs->helpchan->name, botlist);
2709     }
2710     helpserv_botlist_append(botlist, hs);
2711     return hs;
2712 }
2713
2714 static HELPSERV_FUNC(cmd_register) {
2715     char *nick, *helpchan, reason[MAXLEN];
2716     struct handle_info *handle;
2717
2718     REQUIRE_PARMS(3);
2719     nick = argv[1];
2720     if (!is_valid_nick(nick)) {
2721         helpserv_notice(user, "HSMSG_ILLEGAL_NICK", nick);
2722         return 0;
2723     }
2724     if (GetUserH(nick)) {
2725         helpserv_notice(user, "HSMSG_NICK_EXISTS", nick);
2726         return 0;
2727     }
2728     helpchan = argv[2];
2729     if (!IsChannelName(helpchan)) {
2730         helpserv_notice(user, "HSMSG_ILLEGAL_CHANNEL", helpchan);
2731         HELPSERV_SYNTAX();
2732         return 0;
2733     }
2734     if (opserv_bad_channel(helpchan)) {
2735         helpserv_notice(user, "HSMSG_ILLEGAL_CHANNEL", helpchan);
2736         return 0;
2737     }
2738     if (!(handle = helpserv_get_handle_info(user, argv[3])))
2739         return 0;
2740
2741     if (!(hs = register_helpserv(nick, helpchan, user->handle_info->handle))) {
2742         helpserv_notice(user, "HSMSG_ERROR_ADDING_SERVICE", nick);
2743         return 0;
2744     }
2745
2746     hs->registered = now;
2747     helpserv_add_user(hs, handle, HlOwner);
2748
2749     helpserv_notice(user, "HSMSG_REG_SUCCESS", handle->handle, nick);
2750
2751     snprintf(reason, MAXLEN, "HelpServ %s (%s) registered to %s by %s.", nick, hs->helpchan->name, handle->handle, user->nick);
2752     /* Not sent to helpers, since they can't register HelpServ */
2753     global_message(MESSAGE_RECIPIENT_OPERS, reason);
2754     return 1;
2755 }
2756
2757 static void unregister_helpserv(struct helpserv_bot *hs) {
2758     enum message_type msgtype;
2759
2760     timeq_del(0, NULL, hs, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_FUNC);
2761
2762     /* Requests before users so that it doesn't spam mentioning now-unhandled
2763      * requests because the users were deleted */
2764     dict_delete(hs->requests);
2765     hs->requests = NULL; /* so we don't try to look up requests in free_user() */
2766     dict_delete(hs->users);
2767     free(hs->registrar);
2768
2769     for (msgtype=0; msgtype<MSGTYPE_COUNT; msgtype++)
2770         free(hs->messages[msgtype]);
2771 }
2772
2773 static void helpserv_free_bot(void *data) {
2774     unregister_helpserv(data);
2775     free(data);
2776 }
2777
2778 static void helpserv_unregister(struct helpserv_bot *bot, const char *quit_fmt, const char *global_fmt, const char *actor) {
2779     char reason[MAXLEN], channame[CHANNELLEN], *botname;
2780     struct helpserv_botlist *botlist;
2781     size_t len;
2782
2783     botlist = dict_find(helpserv_bots_bychan_dict, bot->helpchan->name, NULL);
2784     helpserv_botlist_remove(botlist, bot);
2785     if (!botlist->used)
2786         dict_remove(helpserv_bots_bychan_dict, bot->helpchan->name);
2787     botname=bot->helpserv->nick;
2788     len = strlen(bot->helpchan->name) + 1;
2789     safestrncpy(channame, bot->helpchan->name, len);
2790     snprintf(reason, sizeof(reason), quit_fmt, actor);
2791     DelUser(bot->helpserv, NULL, 1, reason);
2792     dict_remove(helpserv_bots_dict, botname);
2793     snprintf(reason, sizeof(reason), global_fmt, botname, channame, actor);
2794     global_message(MESSAGE_RECIPIENT_OPERS, reason);
2795 }
2796
2797 static HELPSERV_FUNC(cmd_unregister) {
2798     if (!from_opserv) {
2799         if (argc < 2 || strcmp(argv[1], "CONFIRM")) {
2800             helpserv_notice(user, "HSMSG_NEED_UNREG_CONFIRM");
2801             return 0;
2802         }
2803         log_audit(HS_LOG, LOG_COMMAND, user, hs->helpserv, hs->helpchan->name, 0, "unregister CONFIRM");
2804     }
2805
2806     helpserv_unregister(hs, "Unregistered by %s", "HelpServ %s (%s) unregistered by %s.", user->nick);
2807     return from_opserv;
2808 }
2809
2810 static HELPSERV_FUNC(cmd_expire) {
2811     struct helpserv_botlist victims;
2812     struct helpserv_bot *bot;
2813     dict_iterator_t it, next;
2814     unsigned int count = 0;
2815
2816     memset(&victims, 0, sizeof(victims));
2817     for (it = dict_first(helpserv_bots_dict); it; it = next) {
2818         bot = iter_data(it);
2819         next = iter_next(it);
2820         if ((unsigned int)(now - bot->last_active) < helpserv_conf.expire_age)
2821             continue;
2822         helpserv_unregister(bot, "Registration expired due to inactivity", "HelpServ %s (%s) expired at request of %s.", user->nick);
2823         count++;
2824     }
2825     helpserv_notice(user, "HSMSG_EXPIRATION_DONE", count);
2826     return 1;
2827 }
2828
2829 static HELPSERV_FUNC(cmd_giveownership) {
2830     struct handle_info *hi;
2831     struct helpserv_user *new_owner, *old_owner, *hs_user;
2832     dict_iterator_t it;
2833     char reason[MAXLEN];
2834
2835     if (!from_opserv && ((argc < 3) || strcmp(argv[2], "CONFIRM"))) {
2836         helpserv_notice(user, "HSMSG_NEED_GIVEOWNERSHIP_CONFIRM");
2837         return 0;
2838     }
2839     hi = helpserv_get_handle_info(user, argv[1]);
2840     if (!hi)
2841         return 0;
2842     new_owner = GetHSUser(hs, hi);
2843     if (!new_owner) {
2844         helpserv_notice(user, "HSMSG_NOT_IN_USERLIST", hi->handle, hs->helpserv->nick);
2845         return 0;
2846     }
2847     if (!from_opserv)
2848         old_owner = GetHSUser(hs, user->handle_info);
2849     else for (it = dict_first(hs->users), old_owner = NULL; it; it = iter_next(it)) {
2850         hs_user = iter_data(it);
2851         if (hs_user->level != HlOwner)
2852             continue;
2853         if (old_owner) {
2854             helpserv_notice(user, "HSMSG_MULTIPLE_OWNERS", hs->helpserv->nick);
2855             return 0;
2856         }
2857         old_owner = hs_user;
2858     }
2859     if (!from_opserv && (new_owner->handle == user->handle_info)) {
2860         helpserv_notice(user, "HSMSG_NO_TRANSFER_SELF");
2861         return 0;
2862     }
2863     if (old_owner)
2864         old_owner->level = HlManager;
2865     new_owner->level = HlOwner;
2866     helpserv_notice(user, "HSMSG_OWNERSHIP_GIVEN", hs->helpserv->nick, new_owner->handle->handle);
2867     sprintf(reason, "%s (%s) ownership transferred to %s by %s.", hs->helpserv->nick, hs->helpchan->name, new_owner->handle->handle, user->handle_info->handle);
2868     return 1;
2869 }
2870
2871 static HELPSERV_FUNC(cmd_weekstart) {
2872     struct handle_info *hi;
2873     struct helpserv_user *actor, *victim;
2874     int changed = 0;
2875
2876     REQUIRE_PARMS(2);
2877     actor = from_opserv ? NULL : GetHSUser(hs, user->handle_info);
2878     if (!(hi = helpserv_get_handle_info(user, argv[1])))
2879         return 0;
2880     if (!(victim = GetHSUser(hs, hi))) {
2881         helpserv_notice(user, "HSMSG_NOT_IN_USERLIST", hi->handle, hs->helpserv->nick);
2882         return 0;
2883     }
2884     if (actor && (actor->level <= victim->level) && (actor != victim)) {
2885         helpserv_notice(user, "MSG_USER_OUTRANKED", victim->handle->handle);
2886         return 0;
2887     }
2888     if (argc > 2 && (!actor || actor->level >= HlManager)) {
2889         int new_day = 7;
2890         switch (argv[2][0]) {
2891         case 's': case 'S':
2892             if ((argv[2][1] == 'u') || (argv[2][1] == 'U'))
2893                 new_day = 0;
2894             else if ((argv[2][1] == 'a') || (argv[2][1] == 'A'))
2895                 new_day = 6;
2896             break;
2897         case 'm': case 'M': new_day = 1; break;
2898         case 't': case 'T':
2899             if ((argv[2][1] == 'u') || (argv[2][1] == 'U'))
2900                 new_day = 2;
2901             else if ((argv[2][1] == 'h') || (argv[2][1] == 'H'))
2902                 new_day = 4;
2903             break;
2904         case 'w': case 'W': new_day = 3; break;
2905         case 'f': case 'F': new_day = 5; break;
2906         }
2907         if (new_day == 7) {
2908             helpserv_notice(user, "HSMSG_BAD_WEEKDAY", argv[2]);
2909             return 0;
2910         }
2911         victim->week_start = new_day;
2912         changed = 1;
2913     }
2914     helpserv_notice(user, "HSMSG_WEEK_STARTS", victim->handle->handle, weekday_names[victim->week_start]);
2915     return changed;
2916 }
2917
2918 static HELPSERV_FUNC(cmd_modstats) {
2919     struct handle_info *hi;
2920     struct helpserv_user *victim;
2921     const char *field_name;
2922     int week, mod;
2923     unsigned int *field = NULL;
2924     char *errptr;
2925
2926     REQUIRE_PARMS(5);
2927     if (!oper_has_access(user, (from_opserv ? opserv : hs->helpserv), helpserv_conf.modstats_level, 0))
2928         return 0;
2929     if (!(hi = helpserv_get_handle_info(user, argv[1])))
2930         return 0;
2931     if (!(victim = GetHSUser(hs, hi))) {
2932         helpserv_notice(user, "HSMSG_NOT_IN_USERLIST", hi->handle, hs->helpserv->nick);
2933         return 0;
2934     }
2935
2936     field_name = argv[2];
2937     if (!strcasecmp(argv[3], "total"))
2938         week = 4;
2939     else if(!strcasecmp(argv[3], "current"))
2940         week = 0;
2941     else {
2942         week = strtoul(argv[3], &errptr, 0);
2943         if (*errptr != '\0') {
2944             helpserv_notice(user, "HSMSG_MODSTATS_BAD_WEEK");
2945             return 0;
2946         }
2947     }
2948     mod = strtol(argv[4], NULL, 0);
2949
2950     if (week < 0 || week > 4) {
2951         helpserv_notice(user, "HSMSG_MODSTATS_BAD_WEEK");
2952         return 0;
2953     }
2954
2955     if (!strcasecmp(field_name, "time")) {
2956         if (victim->join_time && (week == 0 || week == 4)) {
2957             victim->time_per_week[0] += now - victim->join_time;
2958             victim->time_per_week[4] += now - victim->join_time;
2959             victim->join_time = now;
2960         }
2961         field = victim->time_per_week;
2962     }
2963     else if (!strcasecmp(field_name, "picked") || !strcasecmp(field_name, "picked_up") || !strcasecmp(field_name, "reqs"))
2964         field = victim->picked_up;
2965     else if (!strcasecmp(field_name, "closed"))
2966         field = victim->closed;
2967     else if (!strcasecmp(field_name, "ra_from") || !strcasecmp(field_name, "reassigned_from"))
2968         field = victim->reassigned_from;
2969     else if (!strcasecmp(field_name, "ra_to") || !strcasecmp(field_name, "reassigned_to"))
2970         field = victim->reassigned_to;
2971     else {
2972         helpserv_notice(user, "HSMSG_MODSTATS_BAD_FIELD");
2973         return 0;
2974     }
2975
2976     if (mod < 0 && mod < -(int)field[week]) {
2977         helpserv_notice(user, "HSMSG_MODSTATS_NEGATIVE");
2978         return 0;
2979     }
2980
2981     field[week] += mod;
2982     helpserv_notice(user, "HSMSG_MODSTATS_SUCCESS", victim->handle->handle);
2983
2984     return (mod != 0);
2985 }
2986
2987 static void set_page_target(struct helpserv_bot *hs, enum page_source idx, const char *target) {
2988     struct chanNode *new_target, *old_target;
2989
2990     if (target) {
2991         if (!IsChannelName(target)) {
2992             log_module(HS_LOG, LOG_ERROR, "%s has an invalid page target.", hs->helpserv->nick);
2993             return;
2994         }
2995         new_target = GetChannel(target);
2996         if (!new_target) {
2997             new_target = AddChannel(target, now, NULL, NULL);
2998             AddChannelUser(hs->helpserv, new_target);
2999         }
3000     } else {
3001         new_target = NULL;
3002     }
3003     if (new_target == hs->page_targets[idx])
3004         return;
3005     old_target = hs->page_targets[idx];
3006     hs->page_targets[idx] = NULL;
3007     if (old_target && !helpserv_in_channel(hs, old_target))
3008         DelChannelUser(hs->helpserv, old_target, "Changing page target.", 0);
3009     if (new_target && !helpserv_in_channel(hs, new_target)) {
3010         struct mod_chanmode change;
3011         mod_chanmode_init(&change);
3012         change.argc = 1;
3013         change.args[0].mode = MODE_CHANOP;
3014         change.args[0].u.member = AddChannelUser(hs->helpserv, new_target);
3015         mod_chanmode_announce(hs->helpserv, new_target, &change);
3016     }
3017     hs->page_targets[idx] = new_target;
3018 }
3019
3020 static int opt_page_target(struct userNode *user, struct helpserv_bot *hs, int from_opserv, int argc, char *argv[], enum page_source idx) {
3021     int changed = 0;
3022
3023     if (argc > 0) {
3024         if (!IsOper(user)) {
3025             helpserv_notice(user, "HSMSG_SET_NEED_OPER");
3026             return 0;
3027         }
3028         if (!strcmp(argv[0], "*")) {
3029             set_page_target(hs, idx, NULL);
3030             changed = 1;
3031         } else if (!IsChannelName(argv[0])) {
3032             helpserv_notice(user, "MSG_NOT_CHANNEL_NAME");
3033             return 0;
3034         } else {
3035             set_page_target(hs, idx, argv[0]);
3036             changed = 1;
3037         }
3038     }
3039     if (hs->page_targets[idx])
3040         helpserv_notice(user, page_sources[idx].print_target, hs->page_targets[idx]->name);
3041     else
3042         helpserv_notice(user, page_sources[idx].print_target, user_find_message(user, "MSG_NONE"));
3043     return changed;
3044 }
3045
3046 static HELPSERV_OPTION(opt_pagetarget_command) {
3047     return opt_page_target(user, hs, from_opserv, argc, argv, PGSRC_COMMAND);
3048 }
3049
3050 static HELPSERV_OPTION(opt_pagetarget_alert) {
3051     return opt_page_target(user, hs, from_opserv, argc, argv, PGSRC_ALERT);
3052 }
3053
3054 static HELPSERV_OPTION(opt_pagetarget_status) {
3055     return opt_page_target(user, hs, from_opserv, argc, argv, PGSRC_STATUS);
3056 }
3057
3058 static enum page_type page_type_from_name(const char *name) {
3059     enum page_type type;
3060     for (type=0; type<PAGE_COUNT; type++)
3061         if (!irccasecmp(page_types[type].db_name, name))
3062             return type;
3063     return PAGE_COUNT;
3064 }
3065
3066 static int opt_page_type(struct userNode *user, struct helpserv_bot *hs, int from_opserv, int argc, char *argv[], enum page_source idx) {
3067     enum page_type new_type;
3068     int changed=0;
3069
3070     if (argc > 0) {
3071         new_type = page_type_from_name(argv[0]);
3072         if (new_type == PAGE_COUNT) {
3073             helpserv_notice(user, "HSMSG_INVALID_OPTION", argv[0]);
3074             return 0;
3075         }
3076         hs->page_types[idx] = new_type;
3077         changed = 1;
3078     }
3079     helpserv_notice(user, page_sources[idx].print_type,
3080                     user_find_message(user, page_types[hs->page_types[idx]].print_name));
3081     return changed;
3082 }
3083
3084 static HELPSERV_OPTION(opt_pagetype) {
3085     return opt_page_type(user, hs, from_opserv, argc, argv, PGSRC_COMMAND);
3086 }
3087
3088 static HELPSERV_OPTION(opt_alert_page_type) {
3089     return opt_page_type(user, hs, from_opserv, argc, argv, PGSRC_ALERT);
3090 }
3091
3092 static HELPSERV_OPTION(opt_status_page_type) {
3093     return opt_page_type(user, hs, from_opserv, argc, argv, PGSRC_STATUS);
3094 }
3095
3096 static int opt_message(struct userNode *user, struct helpserv_bot *hs, int from_opserv, int argc, char *argv[], enum message_type idx) {
3097     int changed=0;
3098
3099     if (argc > 0) {
3100         char *msg = unsplit_string(argv, argc, NULL);
3101         free(hs->messages[idx]);
3102         hs->messages[idx] = strcmp(msg, "*") ? strdup(msg) : NULL;
3103         changed = 1;
3104     }
3105     if (hs->messages[idx])
3106         helpserv_notice(user, message_types[idx].print_name, hs->messages[idx]);
3107     else
3108         helpserv_notice(user, message_types[idx].print_name, user_find_message(user, "MSG_NONE"));
3109     return changed;
3110 }
3111
3112 static HELPSERV_OPTION(opt_greeting) {
3113     return opt_message(user, hs, from_opserv, argc, argv, MSGTYPE_GREETING);
3114 }
3115
3116 static HELPSERV_OPTION(opt_req_opened) {
3117     return opt_message(user, hs, from_opserv, argc, argv, MSGTYPE_REQ_OPENED);
3118 }
3119
3120 static HELPSERV_OPTION(opt_req_assigned) {
3121     return opt_message(user, hs, from_opserv, argc, argv, MSGTYPE_REQ_ASSIGNED);
3122 }
3123
3124 static HELPSERV_OPTION(opt_req_closed) {
3125     return opt_message(user, hs, from_opserv, argc, argv, MSGTYPE_REQ_CLOSED);
3126 }
3127
3128 static int opt_interval(struct userNode *user, struct helpserv_bot *hs, int from_opserv, int argc, char *argv[], enum interval_type idx, unsigned int min) {
3129     char buf[INTERVALLEN];
3130     int changed=0;
3131
3132     if (argc > 0) {
3133         unsigned long new_int = ParseInterval(argv[0]);
3134         if (!new_int && strcmp(argv[0], "0")) {
3135             helpserv_notice(user, "MSG_INVALID_DURATION", argv[0]);
3136             return 0;
3137         }
3138         if (new_int && new_int < min) {
3139             intervalString(buf, min, user->handle_info);
3140             helpserv_notice(user, "HSMSG_INVALID_INTERVAL", user_find_message(user, interval_types[idx].print_name), buf);
3141             return 0;
3142         }
3143         hs->intervals[idx] = new_int;
3144         changed = 1;
3145     }
3146     if (hs->intervals[idx]) {
3147         intervalString(buf, hs->intervals[idx], user->handle_info);
3148         helpserv_notice(user, interval_types[idx].print_name, buf);
3149     } else
3150         helpserv_notice(user, interval_types[idx].print_name, user_find_message(user, "HSMSG_0_DISABLED"));
3151     return changed;
3152 }
3153
3154 static HELPSERV_OPTION(opt_idle_delay) {
3155     return opt_interval(user, hs, from_opserv, argc, argv, INTERVAL_IDLE_DELAY, 60);
3156 }
3157
3158 static HELPSERV_OPTION(opt_whine_delay) {
3159     return opt_interval(user, hs, from_opserv, argc, argv, INTERVAL_WHINE_DELAY, 60);
3160 }
3161
3162 static HELPSERV_OPTION(opt_whine_interval) {
3163     unsigned int old_val = hs->intervals[INTERVAL_WHINE_INTERVAL];
3164     int retval;
3165
3166     retval = opt_interval(user, hs, from_opserv, argc, argv, INTERVAL_WHINE_INTERVAL, 60);
3167
3168     if (!old_val && hs->intervals[INTERVAL_WHINE_INTERVAL]) {
3169         timeq_add(now + hs->intervals[INTERVAL_WHINE_INTERVAL], run_whine_interval, hs);
3170     } else if (old_val && !hs->intervals[INTERVAL_WHINE_INTERVAL]) {
3171         timeq_del(0, run_whine_interval, hs, TIMEQ_IGNORE_WHEN);
3172     }
3173
3174     return retval;
3175 }
3176
3177 static HELPSERV_OPTION(opt_empty_interval) {
3178     unsigned int old_val = hs->intervals[INTERVAL_EMPTY_INTERVAL];
3179     int retval;
3180
3181     retval = opt_interval(user, hs, from_opserv, argc, argv, INTERVAL_EMPTY_INTERVAL, 60);
3182
3183     if (!old_val && hs->intervals[INTERVAL_EMPTY_INTERVAL]) {
3184         timeq_add(now + hs->intervals[INTERVAL_EMPTY_INTERVAL], run_empty_interval, hs);
3185     } else if (old_val && !hs->intervals[INTERVAL_EMPTY_INTERVAL]) {
3186         timeq_del(0, run_empty_interval, hs, TIMEQ_IGNORE_WHEN);
3187     }
3188
3189     return retval;
3190 }
3191
3192 static HELPSERV_OPTION(opt_stale_delay) {
3193     return opt_interval(user, hs, from_opserv, argc, argv, INTERVAL_STALE_DELAY, 60);
3194 }
3195
3196 static enum persistence_length persistence_from_name(const char *name) {
3197     enum persistence_length pers;
3198     for (pers=0; pers<PERSIST_COUNT; pers++)
3199         if (!irccasecmp(name, persistence_lengths[pers].db_name))
3200             return pers;
3201     return PERSIST_COUNT;
3202 }
3203
3204 static int opt_persist(struct userNode *user, struct helpserv_bot *hs, int from_opserv, int argc, char *argv[], enum persistence_type idx) {
3205     int changed=0;
3206
3207     if (argc > 0) {
3208         enum persistence_length new_pers = persistence_from_name(argv[0]);
3209         if (new_pers == PERSIST_COUNT) {
3210             helpserv_notice(user, "HSMSG_INVALID_OPTION", argv[0]);
3211             return 0;
3212         }
3213         hs->persist_lengths[idx] = new_pers;
3214         changed = 1;
3215     }
3216     helpserv_notice(user, persistence_lengths[idx].print_name,
3217                     user_find_message(user, persistence_lengths[hs->persist_lengths[idx]].print_name));
3218     return changed;
3219 }
3220
3221 static HELPSERV_OPTION(opt_request_persistence) {
3222     return opt_persist(user, hs, from_opserv, argc, argv, PERSIST_T_REQUEST);
3223 }
3224
3225 static HELPSERV_OPTION(opt_helper_persistence) {
3226     return opt_persist(user, hs, from_opserv, argc, argv, PERSIST_T_HELPER);
3227 }
3228
3229 static enum notification_type notification_from_name(const char *name) {
3230     enum notification_type notify;
3231     for (notify=0; notify<NOTIFY_COUNT; notify++)
3232         if (!irccasecmp(name, notification_types[notify].db_name))
3233             return notify;
3234     return NOTIFY_COUNT;
3235 }
3236
3237 static HELPSERV_OPTION(opt_notification) {
3238     int changed=0;
3239
3240     if (argc > 0) {
3241         enum notification_type new_notify = notification_from_name(argv[0]);
3242         if (new_notify == NOTIFY_COUNT) {
3243             helpserv_notice(user, "HSMSG_INVALID_OPTION", argv[0]);
3244             return 0;
3245         }
3246         if (!from_opserv && (new_notify == NOTIFY_HANDLE)) {
3247             helpserv_notice(user, "HSMSG_SET_NEED_OPER");
3248             return 0;
3249         }
3250         hs->notify = new_notify;
3251         changed = 1;
3252     }
3253     helpserv_notice(user, "HSMSG_SET_NOTIFICATION", user_find_message(user, notification_types[hs->notify].print_name));
3254     return changed;
3255 }
3256
3257 #define OPTION_UINT(var, name) do { \
3258     int changed=0; \
3259     if (argc > 0) { \
3260         (var) = strtoul(argv[0], NULL, 0); \
3261         changed = 1; \
3262     } \
3263     helpserv_notice(user, name, (var)); \
3264     return changed; \
3265 } while (0);
3266
3267 static HELPSERV_OPTION(opt_id_wrap) {
3268     OPTION_UINT(hs->id_wrap, "HSMSG_SET_IDWRAP");
3269 }
3270
3271 static HELPSERV_OPTION(opt_req_maxlen) {
3272     OPTION_UINT(hs->req_maxlen, "HSMSG_SET_REQMAXLEN");
3273 }
3274
3275 #define OPTION_BINARY(var, name) do { \
3276     int changed=0; \
3277     if (argc > 0) { \
3278         if (enabled_string(argv[0])) { \
3279             (var) = 1; \
3280             changed = 1; \
3281         } else if (disabled_string(argv[0])) { \
3282             (var) = 0; \
3283             changed = 1; \
3284         } else { \
3285             helpserv_notice(user, "MSG_INVALID_BINARY", argv[0]); \
3286             return 0; \
3287         } \
3288     } \
3289     helpserv_notice(user, name, user_find_message(user, (var) ? "MSG_ON" : "MSG_OFF")); \
3290     return changed; \
3291 } while (0);
3292
3293 static HELPSERV_OPTION(opt_privmsg_only) {
3294     OPTION_BINARY(hs->privmsg_only, "HSMSG_SET_PRIVMSGONLY");
3295 }
3296
3297 static HELPSERV_OPTION(opt_req_on_join) {
3298     OPTION_BINARY(hs->req_on_join, "HSMSG_SET_REQONJOIN");
3299 }
3300
3301 static HELPSERV_OPTION(opt_auto_voice) {
3302     OPTION_BINARY(hs->auto_voice, "HSMSG_SET_AUTOVOICE");
3303 }
3304
3305 static HELPSERV_OPTION(opt_auto_join) {
3306     OPTION_BINARY(hs->auto_join, "HSMSG_SET_AUTOJOIN");
3307 }
3308
3309 static HELPSERV_OPTION(opt_auto_devoice) {
3310     OPTION_BINARY(hs->auto_devoice, "HSMSG_SET_AUTODEVOICE");
3311 }
3312
3313 static HELPSERV_OPTION(opt_publicchan) {
3314  char *publicchan;
3315  int changed=0;
3316  if (argc > 0) {
3317   publicchan = argv[0];
3318                 if(strcmp(publicchan, "*")) {
3319           if (!IsChannelName(publicchan)) {
3320     helpserv_notice(user, "HSMSG_ILLEGAL_CHANNEL", publicchan);
3321     HELPSERV_SYNTAX();
3322     return 0;
3323    }
3324    if (opserv_bad_channel(publicchan)) {
3325     helpserv_notice(user, "HSMSG_ILLEGAL_CHANNEL", publicchan);
3326     return 0;
3327    }
3328                 }
3329                 if (!hs->publicchan || (hs->publicchan && irccasecmp(hs->publicchan->name, publicchan))) {
3330    if(hs->publicchan) {
3331                   //there is another public chan o.O
3332                          //part
3333                          DelChannelUser(hs->helpserv, hs->publicchan, "unregistered.", 0);
3334                                 hs->publicchan = NULL;
3335                  }
3336                         changed = 1;
3337                         if(strcmp(publicchan, "*")) {
3338                          if (!(hs->publicchan = GetChannel(publicchan))) {
3339      hs->publicchan = AddChannel(publicchan, now, NULL, NULL);
3340      AddChannelUser(hs->helpserv, hs->publicchan)->modes |= MODE_CHANOP;
3341     } else {
3342      struct mod_chanmode change;
3343      mod_chanmode_init(&change);
3344      change.argc = 1;
3345      change.args[0].mode = MODE_CHANOP;
3346      change.args[0].u.member = AddChannelUser(hs->helpserv, hs->publicchan);
3347      mod_chanmode_announce(hs->helpserv, hs->publicchan, &change);
3348                          }
3349                         }
3350   }
3351  } else {
3352          changed = 0;
3353         }
3354         helpserv_notice(user, "HSMSG_SET_PUBLICCHAN", (hs->publicchan) ? hs->publicchan->name : user_find_message(user,"MSG_NONE")); \
3355  return changed;
3356 }
3357
3358 static HELPSERV_FUNC(cmd_set) {
3359     helpserv_option_func_t *opt;
3360
3361     if (argc < 2) {
3362         unsigned int i;
3363         helpserv_option_func_t *display[] = {
3364             opt_publicchan, opt_pagetarget_command, opt_pagetarget_alert, opt_pagetarget_status,
3365             opt_pagetype, opt_alert_page_type, opt_status_page_type,
3366             opt_greeting, opt_req_opened, opt_req_assigned, opt_req_closed,
3367             opt_idle_delay, opt_whine_delay, opt_whine_interval,
3368             opt_empty_interval, opt_stale_delay, opt_request_persistence,
3369             opt_helper_persistence, opt_notification, opt_id_wrap,
3370             opt_req_maxlen, opt_privmsg_only, opt_req_on_join, opt_auto_voice, opt_auto_join,
3371             opt_auto_devoice
3372         };
3373
3374         helpserv_notice(user, "HSMSG_QUEUE_OPTIONS");
3375         for (i=0; i<ArrayLength(display); i++)
3376             display[i](user, hs, from_opserv, 0, argv);
3377         return 1;
3378     }
3379
3380     if (!(opt = dict_find(helpserv_option_dict, argv[1], NULL))) {
3381         helpserv_notice(user, "HSMSG_INVALID_OPTION", argv[1]);
3382         return 0;
3383     }
3384
3385     if ((argc > 2) && !from_opserv) {
3386         struct helpserv_user *hs_user;
3387
3388         if (!(hs_user = dict_find(hs->users, user->handle_info->handle, NULL))) {
3389             helpserv_notice(user, "HSMSG_WTF_WHO_ARE_YOU", hs->helpserv->nick);
3390             return 0;
3391         }
3392
3393         if (hs_user->level < HlManager) {
3394             helpserv_notice(user, "HSMSG_NEED_MANAGER");
3395             return 0;
3396         }
3397     }
3398     return opt(user, hs, from_opserv, argc-2, argv+2);
3399 }
3400
3401 static int user_write_helper(const char *key, void *data, void *extra) {
3402     struct helpserv_user *hs_user = data;
3403     struct saxdb_context *ctx = extra;
3404     struct string_list strlist;
3405     char str[5][16], *strs[5];
3406     unsigned int i;
3407
3408     saxdb_start_record(ctx, key, 0);
3409     /* Helper identification. */
3410     saxdb_write_string(ctx, KEY_HELPER_LEVEL, helpserv_level2str(hs_user->level));
3411     saxdb_write_string(ctx, KEY_HELPER_HELPMODE, (hs_user->help_mode ? "1" : "0"));
3412     saxdb_write_int(ctx, KEY_HELPER_WEEKSTART, hs_user->week_start);
3413     /* Helper stats */
3414     saxdb_start_record(ctx, KEY_HELPER_STATS, 0);
3415     for (i=0; i < ArrayLength(strs); ++i)
3416         strs[i] = str[i];
3417     strlist.list = strs;
3418     strlist.used = 5;
3419     /* Time in help channel */
3420     for (i=0; i < strlist.used; i++) {
3421         unsigned int week_time = hs_user->time_per_week[i];
3422         if ((i==0 || i==4) && hs_user->join_time)
3423             week_time += now - hs_user->join_time;
3424         sprintf(str[i], "%u", week_time);
3425     }
3426     saxdb_write_string_list(ctx, KEY_HELPER_STATS_TIME, &strlist);
3427     /* Requests picked up */
3428     for (i=0; i < strlist.used; i++)
3429         sprintf(str[i], "%u", hs_user->picked_up[i]);
3430     saxdb_write_string_list(ctx, KEY_HELPER_STATS_PICKUP, &strlist);
3431     /* Requests closed */
3432     for (i=0; i < strlist.used; i++)
3433         sprintf(str[i], "%u", hs_user->closed[i]);
3434     saxdb_write_string_list(ctx, KEY_HELPER_STATS_CLOSE, &strlist);
3435     /* Requests reassigned from user */
3436     for (i=0; i < strlist.used; i++)
3437         sprintf(str[i], "%u", hs_user->reassigned_from[i]);
3438     saxdb_write_string_list(ctx, KEY_HELPER_STATS_REASSIGNFROM, &strlist);
3439     /* Requests reassigned to user */
3440     for (i=0; i < strlist.used; i++)
3441         sprintf(str[i], "%u", hs_user->reassigned_to[i]);
3442     saxdb_write_string_list(ctx, KEY_HELPER_STATS_REASSIGNTO, &strlist);
3443     /* End of stats and whole record. */
3444     saxdb_end_record(ctx);
3445     saxdb_end_record(ctx);
3446     return 0;
3447 }
3448
3449 static int user_read_helper(const char *key, void *data, void *extra) {
3450     struct record_data *rd = data;
3451     struct helpserv_bot *hs = extra;
3452     struct helpserv_user *hs_user;
3453     struct handle_info *handle;
3454     dict_t stats;
3455     enum helpserv_level level;
3456     char *str;
3457     struct string_list *strlist;
3458     unsigned int i;
3459
3460     if (rd->type != RECDB_OBJECT || !dict_size(rd->d.object)) {
3461         log_module(HS_LOG, LOG_ERROR, "Invalid user %s for %s.", key, hs->helpserv->nick);
3462         return 0;
3463     }
3464
3465     if (!(handle = get_handle_info(key))) {
3466         log_module(HS_LOG, LOG_ERROR, "Nonexistant account %s for %s.", key, hs->helpserv->nick);
3467         return 0;
3468     }
3469     str = database_get_data(rd->d.object, KEY_HELPER_LEVEL, RECDB_QSTRING);
3470     if (str) {
3471         level = helpserv_str2level(str);
3472         if (level == HlNone) {
3473             log_module(HS_LOG, LOG_ERROR, "Account %s has invalid level %s.", key, str);
3474             return 0;
3475         }
3476     } else {
3477         log_module(HS_LOG, LOG_ERROR, "Account %s has no level field for %s.", key, hs->helpserv->nick);
3478         return 0;
3479     }
3480
3481     hs_user = helpserv_add_user(hs, handle, level);
3482
3483     str = database_get_data(rd->d.object, KEY_HELPER_HELPMODE, RECDB_QSTRING);
3484     hs_user->help_mode = (str && strtol(str, NULL, 0)) ? 1 : 0;
3485     str = database_get_data(rd->d.object, KEY_HELPER_WEEKSTART, RECDB_QSTRING);
3486     hs_user->week_start = str ? strtol(str, NULL, 0) : 0;
3487
3488     /* Stats */
3489     stats = database_get_data(GET_RECORD_OBJECT(rd), KEY_HELPER_STATS, RECDB_OBJECT);
3490
3491     if (stats) {
3492         /* The tests for strlist->used are for converting the old format to the new one */
3493         strlist = database_get_data(stats, KEY_HELPER_STATS_TIME, RECDB_STRING_LIST);
3494         if (strlist) {
3495             for (i=0; i < 5 && i < strlist->used; i++)
3496                 hs_user->time_per_week[i] = strtoul(strlist->list[i], NULL, 0);
3497             if (strlist->used == 4)
3498                 hs_user->time_per_week[4] = hs_user->time_per_week[0]+hs_user->time_per_week[1]+hs_user->time_per_week[2]+hs_user->time_per_week[3];
3499         }
3500         strlist = database_get_data(stats, KEY_HELPER_STATS_PICKUP, RECDB_STRING_LIST);
3501         if (strlist) {
3502             for (i=0; i < 5 && i < strlist->used; i++)
3503                 hs_user->picked_up[i] = strtoul(strlist->list[i], NULL, 0);
3504             if (strlist->used == 2)
3505                 hs_user->picked_up[4] = hs_user->picked_up[0]+hs_user->picked_up[1];
3506         }
3507         strlist = database_get_data(stats, KEY_HELPER_STATS_CLOSE, RECDB_STRING_LIST);
3508         if (strlist) {
3509             for (i=0; i < 5 && i < strlist->used; i++)
3510                 hs_user->closed[i] = strtoul(strlist->list[i], NULL, 0);
3511             if (strlist->used == 2)
3512                 hs_user->closed[4] = hs_user->closed[0]+hs_user->closed[1];
3513         }
3514         strlist = database_get_data(stats, KEY_HELPER_STATS_REASSIGNFROM, RECDB_STRING_LIST);
3515         if (strlist) {
3516             for (i=0; i < 5 && i < strlist->used; i++)
3517                 hs_user->reassigned_from[i] = strtoul(strlist->list[i], NULL, 0);
3518             if (strlist->used == 2)
3519                 hs_user->reassigned_from[4] = hs_user->reassigned_from[0]+hs_user->reassigned_from[1];
3520         }
3521         strlist = database_get_data(stats, KEY_HELPER_STATS_REASSIGNTO, RECDB_STRING_LIST);
3522         if (strlist) {
3523             for (i=0; i < 5 && i < strlist->used; i++)
3524                 hs_user->reassigned_to[i] = strtoul(strlist->list[i], NULL, 0);
3525             if (strlist->used == 2)
3526                 hs_user->reassigned_to[4] = hs_user->reassigned_to[0]+hs_user->reassigned_to[1];
3527         }
3528     }
3529
3530     return 0;
3531 }
3532
3533 static int request_write_helper(const char *key, void *data, void *extra) {
3534     struct helpserv_request *request = data;
3535     struct saxdb_context *ctx = extra;
3536
3537     if (!request->handle)
3538         return 0;
3539
3540     saxdb_start_record(ctx, key, 0);
3541     if (request->helper) {
3542         saxdb_write_string(ctx, KEY_REQUEST_HELPER, request->helper->handle->handle);
3543         saxdb_write_int(ctx, KEY_REQUEST_ASSIGNED, request->assigned);
3544     }
3545     saxdb_write_string(ctx, KEY_REQUEST_HANDLE, request->handle->handle);
3546     saxdb_write_int(ctx, KEY_REQUEST_OPENED, request->opened);
3547     saxdb_write_string_list(ctx, KEY_REQUEST_TEXT, request->text);
3548     saxdb_end_record(ctx);
3549     return 0;
3550 }
3551
3552 static int request_read_helper(const char *key, void *data, void *extra) {
3553     struct record_data *rd = data;
3554     struct helpserv_bot *hs = extra;
3555     struct helpserv_request *request;
3556     struct string_list *strlist;
3557     char *str;
3558
3559     if (rd->type != RECDB_OBJECT || !dict_size(rd->d.object)) {
3560         log_module(HS_LOG, LOG_ERROR, "Invalid request %s:%s.", hs->helpserv->nick, key);
3561         return 0;
3562     }
3563
3564     request = calloc(1, sizeof(struct helpserv_request));
3565
3566     request->id = strtoul(key, NULL, 0);
3567     request->hs = hs;
3568     request->user = NULL;
3569     request->parent_nick_list = request->parent_hand_list = NULL;
3570
3571     str = database_get_data(rd->d.object, KEY_REQUEST_HANDLE, RECDB_QSTRING);
3572     if (!str || !(request->handle = get_handle_info(str))) {
3573         log_module(HS_LOG, LOG_ERROR, "Request %s:%s has an invalid or nonexistant account.", hs->helpserv->nick, key);
3574         free(request);
3575         return 0;
3576     }
3577     if (!(request->parent_hand_list = dict_find(helpserv_reqs_byhand_dict, request->handle->handle, NULL))) {
3578         request->parent_hand_list = helpserv_reqlist_alloc();
3579         dict_insert(helpserv_reqs_byhand_dict, request->handle->handle, request->parent_hand_list);
3580     }
3581     helpserv_reqlist_append(request->parent_hand_list, request);
3582
3583     str = database_get_data(rd->d.object, KEY_REQUEST_OPENED, RECDB_QSTRING);
3584     if (!str) {
3585         log_module(HS_LOG, LOG_ERROR, "Request %s:%s has a nonexistant opening time. Using time(NULL).", hs->helpserv->nick, key);
3586         request->opened = time(NULL);
3587     } else {
3588         request->opened = strtoul(str, NULL, 0);
3589     }
3590
3591     str = database_get_data(rd->d.object, KEY_REQUEST_ASSIGNED, RECDB_QSTRING);
3592     if (str)
3593         request->assigned = strtoul(str, NULL, 0);
3594
3595     str = database_get_data(rd->d.object, KEY_REQUEST_HELPER, RECDB_QSTRING);
3596     if (str) {
3597         if (!(request->helper = dict_find(hs->users, str, NULL))) {
3598             log_module(HS_LOG, LOG_ERROR, "Request %s:%s has an invalid or nonexistant helper.", hs->helpserv->nick, key);
3599             free(request);
3600             return 0;
3601         }
3602     } else {
3603         if (!hs->unhandled) {
3604             request->next_unhandled = NULL;
3605             hs->unhandled = request;
3606         } else if (hs->unhandled->opened > request->opened) {
3607             request->next_unhandled = hs->unhandled;
3608             hs->unhandled = request;
3609         } else {
3610             struct helpserv_request *unh;
3611             for (unh = hs->unhandled; unh->next_unhandled && (unh->next_unhandled->opened < request->opened); unh = unh->next_unhandled);
3612             request->next_unhandled = unh->next_unhandled;
3613             unh->next_unhandled = request;
3614         }
3615     }
3616
3617     strlist = database_get_data(rd->d.object, KEY_REQUEST_TEXT, RECDB_STRING_LIST);
3618     if (!strlist) {
3619         log_module(HS_LOG, LOG_ERROR, "Request %s:%s has no text.", hs->helpserv->nick, key);
3620         free(request);
3621         return 0;
3622     }
3623     request->text = string_list_copy(strlist);
3624
3625     dict_insert(hs->requests, strdup(key), request);
3626
3627     return 0;
3628 }
3629
3630 static int
3631 helpserv_bot_write(const char *key, void *data, void *extra) {
3632     const struct helpserv_bot *hs = data;
3633     struct saxdb_context *ctx = extra;
3634     enum page_source pagesrc;
3635     enum message_type msgtype;
3636     enum interval_type inttype;
3637     enum persistence_type persisttype;
3638     struct string_list *slist;
3639
3640     /* Entire bot */
3641     saxdb_start_record(ctx, key, 1);
3642
3643     /* Helper list */
3644     saxdb_start_record(ctx, KEY_HELPERS, 1);
3645     dict_foreach(hs->users, user_write_helper, ctx);
3646     saxdb_end_record(ctx);
3647
3648     /* Open requests */
3649     if (hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_CLOSE) {
3650         saxdb_start_record(ctx, KEY_REQUESTS, 0);
3651         dict_foreach(hs->requests, request_write_helper, ctx);
3652         saxdb_end_record(ctx);
3653     }
3654
3655     /* Other settings and state */
3656     saxdb_write_string(ctx, KEY_HELP_CHANNEL, hs->helpchan->name);
3657     if(hs->publicchan) saxdb_write_string(ctx, KEY_PUBLIC_CHANNEL, hs->publicchan->name);
3658     slist = alloc_string_list(PGSRC_COUNT);
3659     for (pagesrc=0; pagesrc<PGSRC_COUNT; pagesrc++) {
3660         struct chanNode *target = hs->page_targets[pagesrc];
3661         string_list_append(slist, strdup(target ? target->name : "*"));
3662     }
3663     saxdb_write_string_list(ctx, KEY_PAGE_DEST, slist);
3664     free_string_list(slist);
3665     for (pagesrc=0; pagesrc<PGSRC_COUNT; pagesrc++) {
3666         const char *src = page_types[hs->page_types[pagesrc]].db_name;
3667         saxdb_write_string(ctx, page_sources[pagesrc].db_name, src);
3668     }
3669     for (msgtype=0; msgtype<MSGTYPE_COUNT; msgtype++) {
3670         const char *msg = hs->messages[msgtype];
3671         if (msg)
3672             saxdb_write_string(ctx, message_types[msgtype].db_name, msg);
3673     }
3674     for (inttype=0; inttype<INTERVAL_COUNT; inttype++) {
3675         if (!hs->intervals[inttype])
3676             continue;
3677         saxdb_write_int(ctx, interval_types[inttype].db_name, hs->intervals[inttype]);
3678     }
3679     for (persisttype=0; persisttype<PERSIST_T_COUNT; persisttype++) {
3680         const char *persist = persistence_lengths[hs->persist_lengths[persisttype]].db_name;
3681         saxdb_write_string(ctx, persistence_types[persisttype].db_name, persist);
3682     }
3683     saxdb_write_string(ctx, KEY_NOTIFICATION, notification_types[hs->notify].db_name);
3684     saxdb_write_int(ctx, KEY_REGISTERED, hs->registered);
3685     saxdb_write_int(ctx, KEY_IDWRAP, hs->id_wrap);
3686     saxdb_write_int(ctx, KEY_REQ_MAXLEN, hs->req_maxlen);
3687     saxdb_write_int(ctx, KEY_LAST_REQUESTID, hs->last_requestid);
3688     if (hs->registrar)
3689         saxdb_write_string(ctx, KEY_REGISTRAR, hs->registrar);
3690     saxdb_write_int(ctx, KEY_PRIVMSG_ONLY, hs->privmsg_only);
3691     saxdb_write_int(ctx, KEY_REQ_ON_JOIN, hs->req_on_join);
3692     saxdb_write_int(ctx, KEY_AUTO_VOICE, hs->auto_voice);
3693     saxdb_write_int(ctx, KEY_AUTO_JOIN, hs->auto_join);
3694     saxdb_write_int(ctx, KEY_AUTO_DEVOICE, hs->auto_devoice);
3695     saxdb_write_int(ctx, KEY_LAST_ACTIVE, hs->last_active);
3696
3697     /* End bot record */
3698     saxdb_end_record(ctx);
3699     return 0;
3700 }
3701
3702 static int
3703 helpserv_saxdb_write(struct saxdb_context *ctx) {
3704     saxdb_start_record(ctx, KEY_BOTS, 1);
3705     dict_foreach(helpserv_bots_dict, helpserv_bot_write, ctx);
3706     saxdb_end_record(ctx);
3707     saxdb_write_int(ctx, KEY_LAST_STATS_UPDATE, last_stats_update);
3708     return 0;
3709 }
3710
3711 static int helpserv_bot_read(const char *key, void *data, UNUSED_ARG(void *extra)) {
3712     struct record_data *br = data, *raw_record;
3713     struct helpserv_bot *hs;
3714     char *registrar, *helpchannel_name, *publicchannel_name, *str;
3715     dict_t users, requests;
3716     enum page_source pagesrc;
3717     enum message_type msgtype;
3718     enum interval_type inttype;
3719     enum persistence_type persisttype;
3720
3721     users = database_get_data(GET_RECORD_OBJECT(br), KEY_HELPERS, RECDB_OBJECT);
3722     if (!users) {
3723         log_module(HS_LOG, LOG_ERROR, "%s has no users.", key);
3724         return 0;
3725     }
3726     helpchannel_name = database_get_data(GET_RECORD_OBJECT(br), KEY_HELP_CHANNEL, RECDB_QSTRING);
3727     if (!helpchannel_name || !IsChannelName(helpchannel_name)) {
3728         log_module(HS_LOG, LOG_ERROR, "%s has an invalid channel name.", key);
3729         return 0;
3730     }
3731     registrar = database_get_data(GET_RECORD_OBJECT(br), KEY_REGISTRAR, RECDB_QSTRING);
3732
3733     hs = register_helpserv(key, helpchannel_name, registrar);
3734
3735                                 publicchannel_name = database_get_data(GET_RECORD_OBJECT(br), KEY_PUBLIC_CHANNEL, RECDB_QSTRING);
3736     if (publicchannel_name) {
3737                                  if(!IsChannelName(publicchannel_name)) {
3738         log_module(HS_LOG, LOG_ERROR, "%s has an invalid channel name.", key);
3739         return 0;
3740                                         } else {
3741                                          if (!(hs->publicchan = GetChannel(publicchannel_name))) {
3742        hs->publicchan = AddChannel(publicchannel_name, now, NULL, NULL);
3743        AddChannelUser(hs->helpserv, hs->publicchan)->modes |= MODE_CHANOP;
3744       } else {
3745        struct mod_chanmode change;
3746        mod_chanmode_init(&change);
3747        change.argc = 1;
3748        change.args[0].mode = MODE_CHANOP;
3749        change.args[0].u.member = AddChannelUser(hs->helpserv, hs->publicchan);
3750        mod_chanmode_announce(hs->helpserv, hs->publicchan, &change);
3751                                                 }
3752                                         }
3753     }
3754     raw_record = dict_find(GET_RECORD_OBJECT(br), KEY_PAGE_DEST, NULL);
3755     switch (raw_record ? raw_record->type : RECDB_INVALID) {
3756     case RECDB_QSTRING:
3757         set_page_target(hs, PGSRC_COMMAND, GET_RECORD_QSTRING(raw_record));
3758         pagesrc = PGSRC_COMMAND + 1;
3759         break;
3760     case RECDB_STRING_LIST: {
3761         struct string_list *slist = GET_RECORD_STRING_LIST(raw_record);
3762         for (pagesrc=0; (pagesrc<slist->used) && (pagesrc<PGSRC_COUNT); pagesrc++) {
3763             const char *dest = slist->list[pagesrc];
3764             set_page_target(hs, pagesrc, strcmp(dest, "*") ? dest : NULL);
3765         }
3766         break;
3767     }
3768     default:
3769         set_page_target(hs, PGSRC_COMMAND, NULL);
3770         pagesrc = PGSRC_COMMAND + 1;
3771         break;
3772     }
3773     while (pagesrc < PGSRC_COUNT) {
3774         set_page_target(hs, pagesrc++, hs->page_targets[PGSRC_COMMAND] ? hs->page_targets[PGSRC_COMMAND]->name : NULL);
3775     }
3776
3777     for (pagesrc=0; pagesrc<PGSRC_COUNT; pagesrc++) {
3778         str = database_get_data(GET_RECORD_OBJECT(br), page_sources[pagesrc].db_name, RECDB_QSTRING);
3779         hs->page_types[pagesrc] = str ? page_type_from_name(str) : PAGE_NONE;
3780     }
3781
3782     for (msgtype=0; msgtype<MSGTYPE_COUNT; msgtype++) {
3783         str = database_get_data(GET_RECORD_OBJECT(br), message_types[msgtype].db_name, RECDB_QSTRING);
3784         hs->messages[msgtype] = str ? strdup(str) : NULL;
3785     }
3786
3787     for (inttype=0; inttype<INTERVAL_COUNT; inttype++) {
3788         str = database_get_data(GET_RECORD_OBJECT(br), interval_types[inttype].db_name, RECDB_QSTRING);
3789         hs->intervals[inttype] = str ? ParseInterval(str) : 0;
3790     }
3791     if (hs->intervals[INTERVAL_WHINE_INTERVAL])
3792         timeq_add(now + hs->intervals[INTERVAL_WHINE_INTERVAL], run_whine_interval, hs);
3793     if (hs->intervals[INTERVAL_EMPTY_INTERVAL])
3794         timeq_add(now + hs->intervals[INTERVAL_EMPTY_INTERVAL], run_empty_interval, hs);
3795
3796     for (persisttype=0; persisttype<PERSIST_T_COUNT; persisttype++) {
3797         str = database_get_data(GET_RECORD_OBJECT(br), persistence_types[persisttype].db_name, RECDB_QSTRING);
3798         hs->persist_lengths[persisttype] = str ? persistence_from_name(str) : PERSIST_QUIT;
3799     }
3800     str = database_get_data(GET_RECORD_OBJECT(br), KEY_NOTIFICATION, RECDB_QSTRING);
3801     hs->notify = str ? notification_from_name(str) : NOTIFY_NONE;
3802     str = database_get_data(GET_RECORD_OBJECT(br), KEY_REGISTERED, RECDB_QSTRING);
3803     if (str)
3804         hs->registered = strtol(str, NULL, 0);
3805     str = database_get_data(GET_RECORD_OBJECT(br), KEY_IDWRAP, RECDB_QSTRING);
3806     if (str)
3807         hs->id_wrap = strtoul(str, NULL, 0);
3808     str = database_get_data(GET_RECORD_OBJECT(br), KEY_REQ_MAXLEN, RECDB_QSTRING);
3809     if (str)
3810         hs->req_maxlen = strtoul(str, NULL, 0);
3811     str = database_get_data(GET_RECORD_OBJECT(br), KEY_LAST_REQUESTID, RECDB_QSTRING);
3812     if (str)
3813         hs->last_requestid = strtoul(str, NULL, 0);
3814     str = database_get_data(GET_RECORD_OBJECT(br), KEY_PRIVMSG_ONLY, RECDB_QSTRING);
3815     hs->privmsg_only = str ? enabled_string(str) : 0;
3816     str = database_get_data(GET_RECORD_OBJECT(br), KEY_REQ_ON_JOIN, RECDB_QSTRING);
3817     hs->req_on_join = str ? enabled_string(str) : 0;
3818     str = database_get_data(GET_RECORD_OBJECT(br), KEY_AUTO_VOICE, RECDB_QSTRING);
3819     hs->auto_voice = str ? enabled_string(str) : 0;
3820     str = database_get_data(GET_RECORD_OBJECT(br), KEY_AUTO_JOIN, RECDB_QSTRING);
3821     hs->auto_join = str ? enabled_string(str) : 0;
3822     str = database_get_data(GET_RECORD_OBJECT(br), KEY_AUTO_DEVOICE, RECDB_QSTRING);
3823     hs->auto_devoice = str ? enabled_string(str) : 0;
3824     str = database_get_data(GET_RECORD_OBJECT(br), KEY_LAST_ACTIVE, RECDB_QSTRING);
3825     hs->last_active = str ? strtoul(str, NULL, 0) : now;
3826
3827     dict_foreach(users, user_read_helper, hs);
3828
3829     requests = database_get_data(GET_RECORD_OBJECT(br), KEY_REQUESTS, RECDB_OBJECT);
3830     if (requests)
3831         dict_foreach(requests, request_read_helper, hs);
3832
3833     return 0;
3834 }
3835
3836 static int
3837 helpserv_saxdb_read(struct dict *conf_db) {
3838     dict_t object;
3839     char *str;
3840
3841     if ((object = database_get_data(conf_db, KEY_BOTS, RECDB_OBJECT))) {
3842         dict_foreach(object, helpserv_bot_read, NULL);
3843     }
3844
3845     str = database_get_data(conf_db, KEY_LAST_STATS_UPDATE, RECDB_QSTRING);
3846     last_stats_update = str ? strtoul(str, NULL, 0) : now;
3847     return 0;
3848 }
3849
3850 static void helpserv_conf_read(void) {
3851     dict_t conf_node;
3852     const char *str;
3853
3854     if (!(conf_node = conf_get_data(HELPSERV_CONF_NAME, RECDB_OBJECT))) {
3855         log_module(HS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type", HELPSERV_CONF_NAME);
3856         return;
3857     }
3858
3859     str = database_get_data(conf_node, "db_backup_freq", RECDB_QSTRING);
3860     helpserv_conf.db_backup_frequency = str ? ParseInterval(str) : 7200;
3861
3862     str = database_get_data(conf_node, "description", RECDB_QSTRING);
3863     helpserv_conf.description = str ? str : "Help Queue Manager";
3864
3865     str = database_get_data(conf_node, "reqlogfile", RECDB_QSTRING);
3866     if (str && strlen(str))
3867         helpserv_conf.reqlogfile = str;
3868     else
3869         helpserv_conf.reqlogfile = NULL;
3870
3871     str = database_get_data(conf_node, "expiration", RECDB_QSTRING);
3872     helpserv_conf.expire_age = ParseInterval(str ? str : "60d");
3873     str = database_get_data(conf_node, "modstats_level", RECDB_QSTRING);
3874     helpserv_conf.modstats_level = str ? strtoul(str, NULL, 0) : 850;
3875     str = database_get_data(conf_node, "user_escape", RECDB_QSTRING);
3876     helpserv_conf.user_escape = str ? str[0] : '@';
3877
3878     if (reqlog_f) {
3879         fclose(reqlog_f);
3880         reqlog_f = NULL;
3881     }
3882     if (helpserv_conf.reqlogfile
3883         && !(reqlog_f = fopen(helpserv_conf.reqlogfile, "a"))) {
3884         log_module(HS_LOG, LOG_ERROR, "Unable to open request logfile (%s): %s", helpserv_conf.reqlogfile, strerror(errno));
3885     }
3886 }
3887
3888 static struct helpserv_cmd *
3889 helpserv_define_func(const char *name, helpserv_func_t *func, enum helpserv_level level, long flags) {
3890     struct helpserv_cmd *cmd = calloc(1, sizeof(struct helpserv_cmd));
3891
3892     cmd->access = level;
3893     cmd->weight = 1.0;
3894     cmd->func = func;
3895     cmd->flags = flags;
3896     dict_insert(helpserv_func_dict, name, cmd);
3897
3898     return cmd;
3899 }
3900
3901 /* Drop requests that persist until part when a user leaves the chan */
3902 static void handle_part(struct modeNode *mn, UNUSED_ARG(const char *reason)) {
3903     struct helpserv_botlist *botlist;
3904     struct helpserv_userlist *userlist;
3905     const int from_opserv = 0; /* for helpserv_notice */
3906     unsigned int i;
3907
3908     if ((botlist = dict_find(helpserv_bots_bychan_dict, mn->channel->name, NULL))) {
3909         for (i=0; i < botlist->used; i++) {
3910             struct helpserv_bot *hs;
3911             dict_iterator_t it;
3912
3913             hs = botlist->list[i];
3914             if (!hs->helpserv)
3915                 continue;
3916             if (hs->persist_lengths[PERSIST_T_REQUEST] != PERSIST_PART)
3917                 continue;
3918
3919             for (it=dict_first(hs->requests); it; it=iter_next(it)) {
3920                 struct helpserv_request *req = iter_data(it);
3921
3922                 if (mn->user != req->user)
3923                     continue;
3924                                                                                                                                 if (GetUserMode(hs->helpchan, mn->user)) //publicchan
3925                     continue;
3926                 if (req->text->used) {
3927                     helpserv_message(hs, mn->user, MSGTYPE_REQ_DROPPED);
3928                     helpserv_msguser(mn->user, "HSMSG_REQ_DROPPED_PART", mn->channel->name, req->id);
3929                     if (req->helper && (hs->notify >= NOTIFY_DROP))
3930                         helpserv_notify(req->helper, "HSMSG_NOTIFY_REQ_DROP_PART", req->id, mn->user->nick);
3931                 }
3932                 helpserv_log_request(req, "Dropped");
3933                 dict_remove(hs->requests, iter_key(it));
3934                 break;
3935             }
3936         }
3937     }
3938
3939     if (mn->user->handle_info && (userlist = dict_find(helpserv_users_byhand_dict, mn->user->handle_info->handle, NULL))) {
3940         for (i=0; i < userlist->used; i++) {
3941             struct helpserv_user *hs_user = userlist->list[i];
3942             struct helpserv_bot *hs = hs_user->hs;
3943             dict_iterator_t it;
3944
3945             if ((hs->helpserv == NULL) || (hs->helpchan != mn->channel) || find_handle_in_channel(hs->helpchan, mn->user->handle_info, mn->user))
3946                 continue;
3947
3948             /* In case of the clock being set back for whatever reason,
3949              * minimize penalty. Don't duplicate this in handle_quit because
3950              * when users quit, handle_part is called for every channel first.
3951              */
3952             if (hs_user->join_time && (hs_user->join_time < now)) {
3953                 hs_user->time_per_week[0] += (unsigned int)(now - hs_user->join_time);
3954                 hs_user->time_per_week[4] += (unsigned int)(now - hs_user->join_time);
3955             }
3956             hs_user->join_time = 0;
3957
3958             for (it=dict_first(hs->requests); it; it=iter_next(it)) {
3959                 struct helpserv_request *req=iter_data(it);
3960
3961                 if ((hs->persist_lengths[PERSIST_T_HELPER] == PERSIST_PART)
3962                     && (req->helper == hs_user)) {
3963                     char our_reason[CHANNELLEN + 8];
3964                     sprintf(our_reason, "parted %s", mn->channel->name);
3965                     helpserv_page_helper_gone(hs, req, our_reason);
3966                 }
3967             }
3968
3969             if (hs->intervals[INTERVAL_EMPTY_INTERVAL] && hs_user->level >= HlHelper) {
3970                 int num_trials;
3971
3972                 if ((num_trials = find_helpchan_helpers(hs)) >= 0) {
3973                     unsigned int num_unh;
3974                     struct helpserv_request *unh;
3975
3976                     for (num_unh=0, unh=hs->unhandled; unh; num_unh++)
3977                         unh = unh->next_unhandled;
3978
3979                     if (num_trials) {
3980                         helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_FIRSTONLYTRIALALERT", hs->helpchan->name, mn->user->nick, num_trials, num_unh);
3981                     } else {
3982                         helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_FIRSTEMPTYALERT", hs->helpchan->name, mn->user->nick, num_unh);
3983                     }
3984                     if (num_unh || !hs->req_on_join) {
3985                         timeq_del(0, run_empty_interval, hs, TIMEQ_IGNORE_WHEN);
3986                         timeq_add(now + hs->intervals[INTERVAL_EMPTY_INTERVAL], run_empty_interval, hs);
3987                     }
3988                 }
3989             }
3990         }
3991     }
3992 }
3993
3994 /* Drop requests that persist until part or quit when a user quits. Otherwise
3995  * set req->user to null (it's no longer valid) if they have a handle,
3996  * and drop it if they don't (nowhere to store the request).
3997  *
3998  * Unassign requests where req->helper persists until the helper parts or
3999  * quits. */
4000 static void handle_quit(struct userNode *user, UNUSED_ARG(struct userNode *killer), UNUSED_ARG(const char *why)) {
4001     struct helpserv_reqlist *reqlist;
4002     struct helpserv_userlist *userlist;
4003     unsigned int i, n;
4004
4005     if (IsLocal(user)) {
4006         struct helpserv_bot *hs;
4007         if ((hs = dict_find(helpserv_bots_dict, user->nick, NULL))) {
4008             hs->helpserv = NULL;
4009         }
4010         return;
4011     }
4012
4013     if ((reqlist = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL))) {
4014         n = reqlist->used;
4015         for (i=0; i < n; i++) {
4016             struct helpserv_request *req = reqlist->list[0];
4017
4018             if ((req->hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_QUIT) || !req->handle) {
4019                 char buf[12];
4020                 sprintf(buf, "%lu", req->id);
4021
4022                 if (req->helper && (req->hs->notify >= NOTIFY_DROP))
4023                     helpserv_notify(req->helper, "HSMSG_NOTIFY_REQ_DROP_QUIT", req->id, req->user->nick);
4024
4025                 helpserv_log_request(req, "Dropped");
4026                 dict_remove(req->hs->requests, buf);
4027             } else {
4028                 req->user = NULL;
4029                 req->parent_nick_list = NULL;
4030                 helpserv_reqlist_remove(reqlist, req);
4031
4032                 if (req->helper && (req->hs->notify >= NOTIFY_USER))
4033                     helpserv_notify(req->helper, "HSMSG_NOTIFY_USER_QUIT", req->id, user->nick);
4034             }
4035         }
4036
4037         dict_remove(helpserv_reqs_bynick_dict, user->nick);
4038     }
4039
4040     if (user->handle_info && (userlist = dict_find(helpserv_users_byhand_dict, user->handle_info->handle, NULL))) {
4041         for (i=0; i < userlist->used; i++) {
4042             struct helpserv_user *hs_user = userlist->list[i];
4043             struct helpserv_bot *hs = hs_user->hs;
4044             dict_iterator_t it;
4045
4046             if ((hs->helpserv == NULL) || user->next_authed || (user->handle_info->users != user))
4047                 continue;
4048
4049             for (it=dict_first(hs->requests); it; it=iter_next(it)) {
4050                 struct helpserv_request *req=iter_data(it);
4051
4052                 if ((hs->persist_lengths[PERSIST_T_HELPER] == PERSIST_QUIT) && (req->helper == hs_user)) {
4053                     helpserv_page_helper_gone(hs, req, "disconnected");
4054                 }
4055             }
4056         }
4057     }
4058 }
4059
4060 static void associate_requests_bybot(struct helpserv_bot *hs, struct userNode *user, int force_greet) {
4061     struct helpserv_reqlist *reqlist, *hand_reqlist=NULL;
4062     struct helpserv_request *newest=NULL, *nicknewest=NULL;
4063     unsigned int i;
4064
4065     if (!(user->handle_info && (hand_reqlist = dict_find(helpserv_reqs_byhand_dict, user->handle_info->handle, NULL))) && !force_greet) {
4066         return;
4067     }
4068
4069     reqlist = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL);
4070
4071     if (hand_reqlist) {
4072         for (i=0; i < hand_reqlist->used; i++) {
4073             struct helpserv_request *req=hand_reqlist->list[i];
4074
4075             if (req->user || (req->hs != hs))
4076                 continue;
4077
4078             req->user = user;
4079             if (!reqlist) {
4080                 reqlist = helpserv_reqlist_alloc();
4081                 dict_insert(helpserv_reqs_bynick_dict, user->nick, reqlist);
4082             }
4083             req->parent_nick_list = reqlist;
4084             helpserv_reqlist_append(reqlist, req);
4085
4086             if (req->helper && (hs->notify >= NOTIFY_USER))
4087                 helpserv_notify(req->helper, "HSMSG_NOTIFY_USER_FOUND", req->id, user->nick);
4088
4089             if (!newest || (newest->opened < req->opened))
4090                 newest = req;
4091         }
4092     }
4093
4094     /* If it's supposed to force a greeting, only bail out if there are no
4095      * requests at all. If it's not supposed to force a greeting, bail out if
4096      * nothing was changed. */
4097     if (!(newest || (force_greet && reqlist)))
4098         return;
4099
4100     /* Possible conditions here:
4101      * 1. newest == NULL, force_greeting == 1, reqlist != NULL
4102      * 2. newest != NULL, force_greeting doesn't matter, reqlist != NULL */
4103
4104     /* Figure out which request will get their next message */
4105     for (i=0; i < reqlist->used; i++) {
4106         struct helpserv_request *req=reqlist->list[i];
4107
4108         if (req->hs != hs)
4109             continue;
4110
4111         if (!nicknewest || (nicknewest->opened < req->opened))
4112             nicknewest = req;
4113
4114         
4115         if (hs->auto_voice && req->helper)
4116         {
4117             struct mod_chanmode change;
4118             mod_chanmode_init(&change);
4119             change.argc = 1;
4120             change.args[0].mode = MODE_VOICE;
4121             if ((change.args[0].u.member = GetUserMode(hs->helpchan, user)))
4122                 mod_chanmode_announce(hs->helpserv, hs->helpchan, &change);
4123         }
4124     }
4125
4126     if ((force_greet && nicknewest) || (newest && (nicknewest == newest))) {
4127         /* Let the user know. Either the user is forced to be greeted, or the
4128          * above has changed which request will get their next message. */
4129         //helpserv_msguser(user, "HSMSG_GREET_EXISTING_REQ", hs->helpchan->name, nicknewest->id);
4130     }
4131 }
4132
4133 static void associate_requests_bychan(struct chanNode *chan, struct userNode *user, int force_greet) {
4134     struct helpserv_botlist *botlist;
4135     unsigned int i;
4136
4137     if (!(botlist = dict_find(helpserv_bots_bychan_dict, chan->name, NULL)))
4138         return;
4139
4140     for (i=0; i < botlist->used; i++)
4141         associate_requests_bybot(botlist->list[i], user, force_greet);
4142 }
4143
4144
4145 /* Greet users upon joining a helpserv channel (if greeting is set) and set
4146  * req->user to the user joining for all requests owned by the user's handle
4147  * (if any) with a req->user == NULL */
4148 static int handle_join(struct modeNode *mNode) {
4149     struct userNode *user = mNode->user;
4150     struct chanNode *chan = mNode->channel;
4151     struct helpserv_botlist *botlist;
4152     unsigned int i;
4153     const int from_opserv = 0; /* for helpserv_notice */
4154
4155     if (IsLocal(user))
4156         return 0;
4157
4158     if (!(botlist = dict_find(helpserv_bots_bychan_dict, chan->name, NULL)))
4159         return 0;
4160
4161     for (i=0; i < botlist->used; i++) {
4162         struct helpserv_bot *hs=botlist->list[i];
4163
4164         if (user->handle_info) {
4165             struct helpserv_user *hs_user;
4166
4167             if ((hs_user = dict_find(hs->users, user->handle_info->handle, NULL))) {
4168                 if (!hs_user->join_time)
4169                     hs_user->join_time = now;
4170
4171                 if (hs_user->level >= HlHelper && hs->intervals[INTERVAL_EMPTY_INTERVAL] && hs->helpchan_empty) {
4172                     hs->helpchan_empty = 0;
4173                     timeq_del(0, run_empty_interval, hs, TIMEQ_IGNORE_WHEN);
4174                     helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_EMPTYNOMORE", user->nick, hs->helpchan->name);
4175                 }
4176                 continue; /* Don't want helpers to have request-on-join */
4177             }
4178         }
4179
4180         if (self->burst && !hs->req_on_join)
4181             continue;
4182
4183         associate_requests_bybot(hs, user, 1);
4184
4185         helpserv_message(hs, user, MSGTYPE_GREETING);
4186
4187         /* Make sure this is at the end (because of the continues) */
4188         if (hs->req_on_join) {
4189             struct helpserv_reqlist *reqlist;
4190             unsigned int j;
4191
4192             if ((reqlist = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL))) {
4193                 for (j=0; j < reqlist->used; j++)
4194                     if (reqlist->list[j]->hs == hs)
4195                         break;
4196                 if (j < reqlist->used)
4197                     continue;
4198             }
4199
4200             create_request(user, hs, 1);
4201         }
4202     }
4203     return 0;
4204 }
4205
4206 /* Update helpserv_reqs_bynick_dict upon nick change */
4207 static void handle_nickchange(struct userNode *user, const char *old_nick) {
4208     struct helpserv_reqlist *reqlist;
4209     unsigned int i;
4210
4211     if (!(reqlist = dict_find(helpserv_reqs_bynick_dict, old_nick, NULL)))
4212         return;
4213
4214     /* Don't free the list when we switch it over to the new nick. */
4215     dict_remove2(helpserv_reqs_bynick_dict, old_nick, 1);
4216     dict_insert(helpserv_reqs_bynick_dict, user->nick, reqlist);
4217
4218     for (i=0; i < reqlist->used; i++) {
4219         struct helpserv_request *req=reqlist->list[i];
4220
4221         if (req->helper && (req->hs->notify >= NOTIFY_USER))
4222             helpserv_notify(req->helper, "HSMSG_NOTIFY_USER_NICK", req->id, old_nick, user->nick);
4223     }
4224 }
4225
4226 /* Also update helpserv_reqs_byhand_dict upon handle rename */
4227 static void handle_nickserv_rename(struct handle_info *handle, const char *old_handle) {
4228     struct helpserv_reqlist *reqlist;
4229     struct helpserv_userlist *userlist;
4230     unsigned int i;
4231
4232     /* First, rename the handle in the requests dict */
4233     if ((reqlist = dict_find(helpserv_reqs_byhand_dict, old_handle, NULL))) {
4234         /* Don't free the list */
4235         dict_remove2(helpserv_reqs_byhand_dict, old_handle, 1);
4236         dict_insert(helpserv_reqs_byhand_dict, handle->handle, reqlist);
4237     }
4238
4239     /* Second, rename the handle in the users dict */
4240     if ((userlist = dict_find(helpserv_users_byhand_dict, old_handle, NULL))) {
4241         dict_remove2(helpserv_users_byhand_dict, old_handle, 1);
4242
4243         for (i=0; i < userlist->used; i++)
4244             dict_remove2(userlist->list[i]->hs->users, old_handle, 1);
4245
4246         dict_insert(helpserv_users_byhand_dict, handle->handle, userlist);
4247         for (i=0; i < userlist->used; i++)
4248             dict_insert(userlist->list[i]->hs->users, handle->handle, userlist->list[i]);
4249     }
4250
4251     if (reqlist) {
4252         for (i=0; i < reqlist->used; i++) {
4253             struct helpserv_request *req=reqlist->list[i];
4254
4255             if (req->helper && (req->hs->notify >= NOTIFY_HANDLE))
4256                 helpserv_notify(req->helper, "HSMSG_NOTIFY_HAND_RENAME", req->id, old_handle, handle->handle);
4257         }
4258     }
4259 }
4260
4261 /* Deals with two cases:
4262  * 1. No handle -> handle
4263  *    - Bots with a request assigned to both the user (w/o handle) and the
4264  *      handle can exist in this case. When a message is sent,
4265  *      helpserv_usermsg will append it to the most recently opened request.
4266  *    - Requests assigned to the handle are NOT assigned to the user, since
4267  *      multiple people can auth to the same handle at once. Wait for them to
4268  *      join / privmsg before setting req->user.
4269  * 2. Handle -> handle
4270  *    - Generally avoided, but sometimes the code may allow this.
4271  *    - Requests that persist only until part/quit are brought along to the
4272  *      new handle.
4273  *    - Requests that persist until closed (stay saved with the handle) are
4274  *      left with the old handle. This is to prevent the confusing situation
4275  *      where some requests are carried over to the new handle, and some are
4276  *      left (because req->handle is the same for all of them, but only some
4277  *      have req->user set).
4278  * - In either of the above cases, if a user is on a bot's userlist and has
4279  *   requests assigned to them, it will give them a list. */
4280 static void handle_nickserv_auth(struct userNode *user, struct handle_info *old_handle) {
4281     struct helpserv_reqlist *reqlist, *dellist=NULL, *hand_reqlist, *oldhand_reqlist;
4282     struct helpserv_userlist *userlist;
4283     unsigned int i, j;
4284     dict_iterator_t it;
4285     const int from_opserv = 0; /* for helpserv_notice */
4286
4287     if (!user->handle_info)
4288         return; /* Authed user is quitting */
4289
4290     if ((userlist = dict_find(helpserv_users_byhand_dict, user->handle_info->handle, NULL))) {
4291         for (i=0; i < userlist->used; i++) {
4292             struct helpserv_user *hs_user = userlist->list[i];
4293             struct helpserv_bot *hs = hs_user->hs;
4294             struct helpserv_reqlist helper_reqs;
4295             struct helpfile_table tbl;
4296
4297             if (!hs_user->join_time && find_handle_in_channel(hs->helpchan, hs_user->handle, NULL))
4298                 hs_user->join_time = now;
4299
4300             helpserv_reqlist_init(&helper_reqs);
4301
4302             for (it=dict_first(hs->requests); it; it=iter_next(it)) {
4303                 struct helpserv_request *req=iter_data(it);
4304
4305                 if (req->helper == hs_user)
4306                     helpserv_reqlist_append(&helper_reqs, req);
4307             }
4308
4309             if (helper_reqs.used) {
4310                 tbl.length = helper_reqs.used+1;
4311                 tbl.width = 5;
4312                 tbl.flags = TABLE_NO_FREE;
4313                 tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
4314                 tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
4315                 tbl.contents[0][0] = "Bot";
4316                 tbl.contents[0][1] = "ID#";
4317                 tbl.contents[0][2] = "Nick";
4318                 tbl.contents[0][3] = "Account";
4319                 tbl.contents[0][4] = "Opened";
4320
4321                 for (j=1; j <= helper_reqs.used; j++) {
4322                     struct helpserv_request *req=helper_reqs.list[j-1];
4323                     char reqid[12], timestr[MAX_LINE_SIZE];
4324                     time_t feh;
4325
4326                     tbl.contents[j] = alloca(tbl.width * sizeof(**tbl.contents));
4327                     tbl.contents[j][0] = req->hs->helpserv->nick;
4328                     sprintf(reqid, "%lu", req->id);
4329                     tbl.contents[j][1] = strdup(reqid);
4330                     tbl.contents[j][2] = req->user ? req->user->nick : "Not online";
4331                     tbl.contents[j][3] = req->handle ? req->handle->handle : "Not authed";
4332                     feh = req->opened;
4333                     strftime(timestr, MAX_LINE_SIZE, HSFMT_TIME, localtime(&feh));
4334                     tbl.contents[j][4] = strdup(timestr);
4335                 }
4336
4337                 helpserv_notice(user, "HSMSG_REQLIST_AUTH");
4338                 table_send(hs->helpserv, user->nick, 0, NULL, tbl);
4339
4340                 for (j=1; j <= helper_reqs.used; j++) {
4341                     free((char *)tbl.contents[j][1]);
4342                     free((char *)tbl.contents[j][4]);
4343                 }
4344             }
4345
4346             helpserv_reqlist_clean(&helper_reqs);
4347         }
4348     }
4349
4350
4351     if (!(reqlist = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL))) {
4352         for (i=0; i < user->channels.used; i++)
4353             associate_requests_bychan(user->channels.list[i]->channel, user, 0);
4354         return;
4355     }
4356
4357     if (!(hand_reqlist = dict_find(helpserv_reqs_byhand_dict, user->handle_info->handle, NULL))) {
4358         hand_reqlist = helpserv_reqlist_alloc();
4359         dict_insert(helpserv_reqs_byhand_dict, user->handle_info->handle, hand_reqlist);
4360     }
4361
4362     if (old_handle) {
4363         dellist = helpserv_reqlist_alloc();
4364         oldhand_reqlist = dict_find(helpserv_reqs_byhand_dict, old_handle->handle, NULL);
4365     } else {
4366         oldhand_reqlist = NULL;
4367     }
4368
4369     for (i=0; i < reqlist->used; i++) {
4370         struct helpserv_request *req = reqlist->list[i];
4371         struct helpserv_bot *hs=req->hs;
4372
4373         if (!old_handle || hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_PART || hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_QUIT) {
4374             /* The request needs to be assigned to the new handle; either it
4375              * only persists until part/quit (so it makes sense to keep it as
4376              * close to the user as possible, and if it's made persistent later
4377              * then it's attached to the new handle) or there is no old handle.
4378              */
4379
4380             req->handle = user->handle_info;
4381
4382             req->parent_hand_list = hand_reqlist;
4383             helpserv_reqlist_append(hand_reqlist, req);
4384
4385             if (oldhand_reqlist) {
4386                 if (oldhand_reqlist->used == 1) {
4387                     dict_remove(helpserv_reqs_byhand_dict, old_handle->handle);
4388                     oldhand_reqlist = NULL;
4389                 } else {
4390                     helpserv_reqlist_remove(oldhand_reqlist, req);
4391                 }
4392             }
4393
4394             if (old_handle) {
4395                 char buf[CHANNELLEN + 14];
4396
4397                 if (hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_PART) {
4398                     sprintf(buf, "part channel %s", hs->helpchan->name);
4399                 } else {
4400                     strcpy(buf, "quit irc");
4401                 }
4402
4403                 helpserv_msguser(user, "HSMSG_REQ_AUTH_MOVED", user->handle_info->handle, hs->helpchan->name, req->id, old_handle->handle, buf);
4404                 if (req->helper && (hs->notify >= NOTIFY_HANDLE))
4405                     helpserv_notify(req->helper, "HSMSG_NOTIFY_HAND_MOVE", req->id, user->handle_info->handle, old_handle->handle);
4406             } else {
4407                 if (req->helper && (hs->notify >= NOTIFY_HANDLE))
4408                     helpserv_notify(req->helper, "HSMSG_NOTIFY_HAND_AUTH", req->id, user->nick, user->handle_info->handle);
4409             }
4410         } else {
4411             req->user = NULL;
4412             req->parent_nick_list = NULL;
4413             /* Would rather not mess with the list while iterating through
4414              * it */
4415             helpserv_reqlist_append(dellist, req);
4416
4417             helpserv_msguser(user, "HSMSG_REQ_AUTH_STUCK", user->handle_info->handle, hs->helpchan->name, req->id, old_handle->handle);
4418             if (req->helper && (hs->notify >= NOTIFY_HANDLE))
4419                 helpserv_notify(req->helper, "HSMSG_NOTIFY_HAND_STUCK", req->id, user->nick, user->handle_info->handle, old_handle->handle);
4420         }
4421     }
4422
4423     if (old_handle) {
4424         if (dellist->used) {
4425             if (dellist->used == reqlist->used) {
4426                 dict_remove(helpserv_reqs_bynick_dict, user->nick);
4427             } else {
4428                 for (i=0; i < dellist->used; i++)
4429                     helpserv_reqlist_remove(reqlist, dellist->list[i]);
4430             }
4431         }
4432         helpserv_reqlist_free(dellist);
4433     }
4434
4435     for (i=0; i < user->channels.used; i++)
4436         associate_requests_bychan(user->channels.list[i]->channel, user, 0);
4437 }
4438
4439
4440 /* Disassociate all requests from the handle. If any have req->user == NULL
4441  * then give them to the user doing the unregistration (if not an oper/helper)
4442  * otherwise the first nick it finds authed (it lets them know about this). If
4443  * there are no users authed to the handle online, the requests are lost. This
4444  * may result in the user having >1 request/bot, and messages go to the most
4445  * recently opened request.
4446  *
4447  * Also, remove the user from all bots that it has access in.
4448  * helpserv_del_user() will take care of unassigning the requests. */
4449 static void handle_nickserv_unreg(struct userNode *user, struct handle_info *handle) {
4450     struct helpserv_reqlist *hand_reqlist;
4451     struct helpserv_userlist *userlist;
4452     unsigned int i, n;
4453     const int from_opserv = 0; /* for helpserv_notice */
4454     struct helpserv_bot *hs; /* for helpserv_notice */
4455
4456     if ((userlist = dict_find(helpserv_users_byhand_dict, handle->handle, NULL))) {
4457         n=userlist->used;
4458
4459         /* Each time helpserv_del_user is called, that entry is going to be
4460          * taken out of userlist... so this should cope with that */
4461         for (i=0; i < n; i++) {
4462             struct helpserv_user *hs_user=userlist->list[0];
4463             helpserv_del_user(hs_user->hs, hs_user);
4464         }
4465     }
4466
4467     if (!(hand_reqlist = dict_find(helpserv_reqs_byhand_dict, handle->handle, NULL))) {
4468         return;
4469     }
4470
4471     n = hand_reqlist->used;
4472     for (i=0; i < n; i++) {
4473         struct helpserv_request *req=hand_reqlist->list[0];
4474         hs = req->hs;
4475
4476         req->handle = NULL;
4477         req->parent_hand_list = NULL;
4478         helpserv_reqlist_remove(hand_reqlist, req);
4479         if (user && req->helper && (hs->notify >= NOTIFY_HANDLE))
4480             helpserv_notify(req->helper, "HSMSG_NOTIFY_HAND_UNREG", req->id, handle->handle, user->nick);
4481
4482         if (!req->user) {
4483             if (!user) {
4484                 /* This is probably an expire. Silently remove everything. */
4485
4486                 char buf[12];
4487                 if (req->helper && (hs->notify >= NOTIFY_DROP))
4488                     helpserv_notify(req->helper, "HSMSG_NOTIFY_REQ_DROP_UNREGGED", req->id, req->handle->handle);
4489                 sprintf(buf, "%lu", req->id);
4490                 helpserv_log_request(req, "Account unregistered");
4491                 dict_remove(req->hs->requests, buf);
4492             } else if (user->handle_info == handle) {
4493                 req->user = user;
4494                 if (!(req->parent_nick_list = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL))) {
4495                     req->parent_nick_list = helpserv_reqlist_alloc();
4496                     dict_insert(helpserv_reqs_bynick_dict, user->nick, req->parent_nick_list);
4497                 }
4498                 helpserv_reqlist_append(req->parent_nick_list, req);
4499
4500                 if (hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_CLOSE)
4501                     helpserv_msguser(req->user, "HSMSG_REQ_WARN_UNREG", handle->handle, hs->helpchan->name, req->id);
4502             } else {
4503                 if (handle->users) {
4504                     req->user = handle->users;
4505
4506                     if (!(req->parent_nick_list = dict_find(helpserv_reqs_bynick_dict, req->user->nick, NULL))) {
4507                         req->parent_nick_list = helpserv_reqlist_alloc();
4508                         dict_insert(helpserv_reqs_bynick_dict, req->user->nick, req->parent_nick_list);
4509                     }
4510                     helpserv_reqlist_append(req->parent_nick_list, req);
4511
4512                     helpserv_msguser(req->user, "HSMSG_REQ_ASSIGNED_UNREG", handle->handle, hs->helpchan->name, req->id);
4513                     if (req->helper && (hs->notify >= NOTIFY_USER))
4514                         helpserv_notify(req->helper, "HSMSG_NOTIFY_USER_MOVE", req->id, handle->handle, req->user->nick);
4515                 } else {
4516                     char buf[12];
4517
4518                     helpserv_notice(user, "HSMSG_REQ_DROPPED_UNREG", handle->handle, hs->helpchan->name, req->id);
4519                     if (req->helper && (hs->notify >= NOTIFY_DROP))
4520                         helpserv_notify(req->helper, "HSMSG_NOTIFY_REQ_DROP_UNREGGED", req->id, req->handle->handle);
4521                     sprintf(buf, "%lu", req->id);
4522                     helpserv_log_request(req, "Account unregistered");
4523                     dict_remove(req->hs->requests, buf);
4524                 }
4525             }
4526         }
4527     }
4528
4529     dict_remove(helpserv_reqs_byhand_dict, handle->handle);
4530 }
4531
4532 static void handle_nickserv_merge(struct userNode *user, struct handle_info *handle_to, struct handle_info *handle_from) {
4533     struct helpserv_reqlist *reqlist_from, *reqlist_to;
4534     unsigned int i;
4535
4536     reqlist_to = dict_find(helpserv_reqs_byhand_dict, handle_to->handle, NULL);
4537
4538     if ((reqlist_from = dict_find(helpserv_reqs_byhand_dict, handle_from->handle, NULL))) {
4539         for (i=0; i < reqlist_from->used; i++) {
4540             struct helpserv_request *req=reqlist_from->list[i];
4541
4542             if (!reqlist_to) {
4543                 reqlist_to = helpserv_reqlist_alloc();
4544                 dict_insert(helpserv_reqs_byhand_dict, handle_to->handle, reqlist_to);
4545             }
4546             req->parent_hand_list = reqlist_to;
4547             req->handle = handle_to;
4548             helpserv_reqlist_append(reqlist_to, req);
4549         }
4550         dict_remove(helpserv_reqs_byhand_dict, handle_from->handle);
4551     }
4552
4553     if (reqlist_to) {
4554         for (i=0; i < reqlist_to->used; i++) {
4555             struct helpserv_request *req=reqlist_to->list[i];
4556
4557             if (req->helper && (req->hs->notify >= NOTIFY_HANDLE)) {
4558                 helpserv_notify(req->helper, "HSMSG_NOTIFY_HAND_MERGE", req->id, handle_to->handle, handle_from->handle, user->nick);
4559             }
4560         }
4561     }
4562 }
4563
4564 static void handle_nickserv_allowauth(struct userNode *user, struct userNode *target, struct handle_info *handle) {
4565     struct helpserv_reqlist *reqlist;
4566     unsigned int i;
4567
4568     if ((reqlist = dict_find(helpserv_reqs_bynick_dict, target->nick, NULL))) {
4569         for (i=0; i < reqlist->used; i++) {
4570             struct helpserv_request *req=reqlist->list[i];
4571
4572             if (req->helper && (req->hs->notify >= NOTIFY_HANDLE)) {
4573                 if (handle) {
4574                     helpserv_notify(req->helper, "HSMSG_NOTIFY_ALLOWAUTH", req->id, target->nick, user->nick, handle->handle);
4575                 } else {
4576                     helpserv_notify(req->helper, "HSMSG_NOTIFY_UNALLOWAUTH", req->id, target->nick, user->nick);
4577                 }
4578             }
4579         }
4580     }
4581 }
4582
4583 static void handle_nickserv_failpw(struct userNode *user, struct handle_info *handle) {
4584     struct helpserv_reqlist *reqlist;
4585     unsigned int i;
4586
4587     if ((reqlist = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL))) {
4588         for (i=0; i < reqlist->used; i++) {
4589             struct helpserv_request *req=reqlist->list[i];
4590             if (req->helper && (req->hs->notify >= NOTIFY_HANDLE))
4591                 helpserv_notify(req->helper, "HSMSG_NOTIFY_FAILPW", req->id, user->nick, handle->handle);
4592         }
4593     }
4594 }
4595
4596 static unsigned long helpserv_next_stats(time_t after_when) {
4597     struct tm *timeinfo = localtime(&after_when);
4598
4599     /* This works because mktime(3) says it will accept out-of-range values
4600      * and fix them for us. tm_wday and tm_yday are ignored. */
4601     timeinfo->tm_mday++;
4602
4603     /* We want to run stats at midnight (local time). */
4604     timeinfo->tm_sec = timeinfo->tm_min = timeinfo->tm_hour = 0;
4605
4606     return mktime(timeinfo);
4607 }
4608
4609 /* If data != NULL, then don't add to the timeq */
4610 static void helpserv_run_stats(unsigned long when) {
4611     struct helpserv_bot *hs;
4612     struct helpserv_user *hs_user;
4613     time_t feh;
4614     unsigned int day;
4615     int i;
4616     dict_iterator_t it, it2;
4617
4618     last_stats_update = when;
4619     feh = when;
4620     day = localtime(&feh)->tm_wday;
4621     for (it=dict_first(helpserv_bots_dict); it; it=iter_next(it)) {
4622         hs = iter_data(it);
4623
4624         for (it2=dict_first(hs->users); it2; it2=iter_next(it2)) {
4625             hs_user = iter_data(it2);
4626
4627             /* Skip the helper if it's not their week-start day. */
4628             if (hs_user->week_start != day)
4629                 continue;
4630
4631             /* Adjust their credit if they are in-channel at rollover. */
4632             if (hs_user->join_time) {
4633                 hs_user->time_per_week[0] += when - hs_user->join_time;
4634                 hs_user->time_per_week[4] += when - hs_user->join_time;
4635                 hs_user->join_time = when;
4636             }
4637
4638             /* Shift everything */
4639             for (i=3; i > 0; i--) {
4640                 hs_user->time_per_week[i] = hs_user->time_per_week[i-1];
4641                 hs_user->picked_up[i] = hs_user->picked_up[i-1];
4642                 hs_user->closed[i] = hs_user->closed[i-1];
4643                 hs_user->reassigned_from[i] = hs_user->reassigned_from[i-1];
4644                 hs_user->reassigned_to[i] = hs_user->reassigned_to[i-1];
4645             }
4646
4647             /* Reset it for this week */
4648             hs_user->time_per_week[0] = hs_user->picked_up[0] = hs_user->closed[0] = hs_user->reassigned_from[0] = hs_user->reassigned_to[0] = 0;
4649         }
4650     }
4651 }
4652
4653 static void helpserv_timed_run_stats(UNUSED_ARG(void *data)) {
4654     helpserv_run_stats(now);
4655     timeq_add(helpserv_next_stats(now), helpserv_timed_run_stats, data);
4656 }
4657
4658 static void
4659 helpserv_define_option(const char *name, helpserv_option_func_t *func) {
4660     dict_insert(helpserv_option_dict, name, func);
4661 }
4662
4663 static void helpserv_db_cleanup(void) {
4664     shutting_down=1;
4665     unreg_part_func(handle_part);
4666     unreg_del_user_func(handle_quit);
4667     close_helpfile(helpserv_helpfile);
4668     dict_delete(helpserv_func_dict);
4669     dict_delete(helpserv_option_dict);
4670     dict_delete(helpserv_usercmd_dict);
4671     dict_delete(helpserv_bots_dict);
4672     dict_delete(helpserv_bots_bychan_dict);
4673     dict_delete(helpserv_reqs_bynick_dict);
4674     dict_delete(helpserv_reqs_byhand_dict);
4675     dict_delete(helpserv_users_byhand_dict);
4676
4677     if (reqlog_f)
4678         fclose(reqlog_f);
4679 }
4680
4681 int helpserv_init() {
4682     HS_LOG = log_register_type("HelpServ", "file:helpserv.log");
4683     conf_register_reload(helpserv_conf_read);
4684
4685     helpserv_func_dict = dict_new();
4686     dict_set_free_data(helpserv_func_dict, free);
4687     helpserv_define_func("HELP", cmd_help, HlNone, CMD_NOT_OVERRIDE|CMD_IGNORE_EVENT);
4688     helpserv_define_func("LIST", cmd_list, HlTrial, CMD_NEED_BOT|CMD_IGNORE_EVENT);
4689     helpserv_define_func("NEXT", cmd_next, HlTrial, CMD_NEED_BOT|CMD_NEVER_FROM_OPSERV);
4690     helpserv_define_func("PICKUP", cmd_pickup, HlTrial, CMD_NEED_BOT|CMD_NEVER_FROM_OPSERV);
4691     helpserv_define_func("REASSIGN", cmd_reassign, HlManager, CMD_NEED_BOT|CMD_NEVER_FROM_OPSERV);
4692     helpserv_define_func("CLOSE", cmd_close, HlTrial, CMD_NEED_BOT|CMD_NEVER_FROM_OPSERV);
4693     helpserv_define_func("SHOW", cmd_show, HlTrial, CMD_NEED_BOT|CMD_IGNORE_EVENT);
4694     helpserv_define_func("ADDNOTE", cmd_addnote, HlTrial, CMD_NEED_BOT);
4695     helpserv_define_func("ADDOWNER", cmd_addowner, HlOper, CMD_NEED_BOT|CMD_FROM_OPSERV_ONLY);
4696     helpserv_define_func("DELOWNER", cmd_deluser, HlOper, CMD_NEED_BOT|CMD_FROM_OPSERV_ONLY);
4697     helpserv_define_func("ADDTRIAL", cmd_addtrial, HlManager, CMD_NEED_BOT);
4698     helpserv_define_func("ADDHELPER", cmd_addhelper, HlManager, CMD_NEED_BOT);
4699     helpserv_define_func("ADDMANAGER", cmd_addmanager, HlOwner, CMD_NEED_BOT);
4700     helpserv_define_func("GIVEOWNERSHIP", cmd_giveownership, HlOwner, CMD_NEED_BOT);
4701     helpserv_define_func("DELUSER", cmd_deluser, HlManager, CMD_NEED_BOT);
4702     helpserv_define_func("HELPERS", cmd_helpers, HlNone, CMD_NEED_BOT);
4703     helpserv_define_func("WLIST", cmd_wlist, HlNone, CMD_NEED_BOT);
4704     helpserv_define_func("MLIST", cmd_mlist, HlNone, CMD_NEED_BOT);
4705     helpserv_define_func("HLIST", cmd_hlist, HlNone, CMD_NEED_BOT);
4706     helpserv_define_func("TLIST", cmd_tlist, HlNone, CMD_NEED_BOT);
4707     helpserv_define_func("CLVL", cmd_clvl, HlManager, CMD_NEED_BOT);
4708     helpserv_define_func("PAGE", cmd_page, HlTrial, CMD_NEED_BOT);
4709     helpserv_define_func("SET", cmd_set, HlHelper, CMD_NEED_BOT);
4710     helpserv_define_func("STATS", cmd_stats, HlTrial, CMD_NEED_BOT);
4711     helpserv_define_func("STATSREPORT", cmd_statsreport, HlManager, CMD_NEED_BOT);
4712     helpserv_define_func("UNREGISTER", cmd_unregister, HlOwner, CMD_NEED_BOT);
4713     helpserv_define_func("READHELP", cmd_readhelp, HlOper, CMD_FROM_OPSERV_ONLY);
4714     helpserv_define_func("REGISTER", cmd_register, HlOper, CMD_FROM_OPSERV_ONLY);
4715     helpserv_define_func("MOVE", cmd_move, HlOper, CMD_FROM_OPSERV_ONLY|CMD_NEED_BOT);
4716     helpserv_define_func("BOTS", cmd_bots, HlOper, CMD_FROM_OPSERV_ONLY|CMD_IGNORE_EVENT);
4717     helpserv_define_func("EXPIRE", cmd_expire, HlOper, CMD_FROM_OPSERV_ONLY);
4718     helpserv_define_func("WEEKSTART", cmd_weekstart, HlTrial, CMD_NEED_BOT);
4719     helpserv_define_func("MODSTATS", cmd_modstats, HlOwner, CMD_NEED_BOT);
4720
4721     helpserv_option_dict = dict_new();
4722     helpserv_define_option("PAGETARGET", opt_pagetarget_command);
4723     helpserv_define_option("ALERTPAGETARGET", opt_pagetarget_alert);
4724     helpserv_define_option("STATUSPAGETARGET", opt_pagetarget_status);
4725     helpserv_define_option("PAGE", opt_pagetype);
4726     helpserv_define_option("PAGETYPE", opt_pagetype);
4727     helpserv_define_option("ALERTPAGETYPE", opt_alert_page_type);
4728     helpserv_define_option("STATUSPAGETYPE", opt_status_page_type);
4729     helpserv_define_option("GREETING", opt_greeting);
4730     helpserv_define_option("REQOPENED", opt_req_opened);
4731     helpserv_define_option("REQASSIGNED", opt_req_assigned);
4732     helpserv_define_option("REQCLOSED", opt_req_closed);
4733     helpserv_define_option("IDLEDELAY", opt_idle_delay);
4734     helpserv_define_option("WHINEDELAY", opt_whine_delay);
4735     helpserv_define_option("WHINEINTERVAL", opt_whine_interval);
4736     helpserv_define_option("EMPTYINTERVAL", opt_empty_interval);
4737     helpserv_define_option("STALEDELAY", opt_stale_delay);
4738     helpserv_define_option("REQPERSIST", opt_request_persistence);
4739     helpserv_define_option("HELPERPERSIST", opt_helper_persistence);
4740     helpserv_define_option("NOTIFICATION", opt_notification);
4741     helpserv_define_option("REQMAXLEN", opt_req_maxlen);
4742     helpserv_define_option("IDWRAP", opt_id_wrap);
4743     helpserv_define_option("PRIVMSGONLY", opt_privmsg_only);
4744     helpserv_define_option("REQONJOIN", opt_req_on_join);
4745     helpserv_define_option("AUTOVOICE", opt_auto_voice);
4746     helpserv_define_option("AUTOJOIN", opt_auto_join);
4747     helpserv_define_option("AUTODEVOICE", opt_auto_devoice);
4748                                 helpserv_define_option("PUBLICCHAN", opt_publicchan);
4749
4750     helpserv_usercmd_dict = dict_new();
4751     dict_insert(helpserv_usercmd_dict, "WAIT", usercmd_wait);
4752
4753     helpserv_bots_dict = dict_new();
4754     dict_set_free_data(helpserv_bots_dict, helpserv_free_bot);
4755
4756     helpserv_bots_bychan_dict = dict_new();
4757     dict_set_free_data(helpserv_bots_bychan_dict, helpserv_botlist_free);
4758
4759     helpserv_reqs_bynick_dict = dict_new();
4760     dict_set_free_data(helpserv_reqs_bynick_dict, helpserv_reqlist_free);
4761     helpserv_reqs_byhand_dict = dict_new();
4762     dict_set_free_data(helpserv_reqs_byhand_dict, helpserv_reqlist_free);
4763
4764     helpserv_users_byhand_dict = dict_new();
4765     dict_set_free_data(helpserv_users_byhand_dict, helpserv_userlist_free);
4766
4767     saxdb_register("HelpServ", helpserv_saxdb_read, helpserv_saxdb_write);
4768     helpserv_helpfile_read();
4769
4770     /* Make up for downtime... though this will only really affect the
4771      * time_per_week */
4772     if (last_stats_update && (helpserv_next_stats(last_stats_update) < now)) {
4773         unsigned long statsrun = last_stats_update;
4774         while ((statsrun = helpserv_next_stats(statsrun)) < now)
4775             helpserv_run_stats(statsrun);
4776     }
4777     timeq_add(helpserv_next_stats(now), helpserv_timed_run_stats, NULL);
4778
4779     reg_join_func(handle_join);
4780     reg_part_func(handle_part); /* also deals with kick */
4781     reg_nick_change_func(handle_nickchange);
4782     reg_del_user_func(handle_quit);
4783
4784     reg_auth_func(handle_nickserv_auth);
4785     reg_handle_rename_func(handle_nickserv_rename);
4786     reg_unreg_func(handle_nickserv_unreg);
4787     reg_allowauth_func(handle_nickserv_allowauth);
4788     reg_failpw_func(handle_nickserv_failpw);
4789     reg_handle_merge_func(handle_nickserv_merge);
4790
4791     reg_exit_func(helpserv_db_cleanup);
4792
4793     helpserv_module = module_register("helpserv", HS_LOG, HELPSERV_HELPFILE_NAME, helpserv_expand_variable);
4794     modcmd_register(helpserv_module, "helpserv", cmd_helpserv, 1, MODCMD_REQUIRE_AUTHED|MODCMD_NO_LOG|MODCMD_NO_DEFAULT_BIND, "level", "800", NULL);
4795     message_register_table(msgtab);
4796     return 1;
4797 }
4798
4799 int
4800 helpserv_finalize(void) {
4801     return 1;
4802 }