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