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