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