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