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