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