1 /* mod-helpserv.c - Support Helper assistant service
2 * Copyright 2002-2003, 2006 srvx Development Team
4 * This file is part of srvx.
6 * srvx is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
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.
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.
21 /* Wishlist for helpserv.c
22 * - Make HelpServ send unassigned request count to helpers as they join the
25 * - Get cmd_statsreport to sort by time and show requests closed
26 * - .. then make statsreport show weighted combination of time and requests closed :)
27 * - Something for users to use a subset of commands... like closing their
28 * own request(s), viewing what they've sent so far, and finding their
30 * - It would be nice to have some variable expansions for the custom
31 * request open/close/etc messages. ${request/id}, etc
32 * - Allow manipulation of the persist types on a per-request basis... so
33 * if it's normally set to close requests after a user quits, but there
34 * is a long-term issue, then that single request can remain
46 #define HELPSERV_CONF_NAME "modules/helpserv"
47 #define HELPSERV_HELPFILE_NAME "mod-helpserv.help"
48 const char *helpserv_module_deps[] = { NULL };
51 #define KEY_BOTS "bots"
52 #define KEY_LAST_STATS_UPDATE "last_stats_update"
53 #define KEY_NICK "nick"
54 #define KEY_DB_BADCHANS "badchans"
55 #define KEY_HELP_CHANNEL "help_channel"
56 #define KEY_PAGE_DEST "page_dest"
57 #define KEY_CMDWORD "cmdword"
58 #define KEY_PERSIST_LENGTH "persist_length"
59 #define KEY_REGISTERED "registered"
60 #define KEY_REGISTRAR "registrar"
61 #define KEY_IDWRAP "id_wrap"
62 #define KEY_REQ_MAXLEN "req_maxlen"
63 #define KEY_LAST_REQUESTID "last_requestid"
64 #define KEY_HELPERS "users"
65 #define KEY_HELPER_LEVEL "level"
66 #define KEY_HELPER_HELPMODE "helpmode"
67 #define KEY_HELPER_WEEKSTART "weekstart"
68 #define KEY_HELPER_STATS "stats"
69 #define KEY_HELPER_STATS_TIME "time"
70 #define KEY_HELPER_STATS_PICKUP "picked_up"
71 #define KEY_HELPER_STATS_CLOSE "closed"
72 #define KEY_HELPER_STATS_REASSIGNFROM "reassign_from"
73 #define KEY_HELPER_STATS_REASSIGNTO "reassign_to"
74 #define KEY_REQUESTS "requests"
75 #define KEY_REQUEST_HELPER "helper"
76 #define KEY_REQUEST_ASSIGNED "assigned"
77 #define KEY_REQUEST_HANDLE "handle"
78 #define KEY_REQUEST_TEXT "text"
79 #define KEY_REQUEST_OPENED "opened"
80 #define KEY_REQUEST_NICK "nick"
81 #define KEY_REQUEST_USERHOST "userhost"
82 #define KEY_REQUEST_CLOSED "closed"
83 #define KEY_REQUEST_CLOSEREASON "closereason"
84 #define KEY_NOTIFICATION "notification"
85 #define KEY_PRIVMSG_ONLY "privmsg_only"
86 #define KEY_REQ_ON_JOIN "req_on_join"
87 #define KEY_AUTO_VOICE "auto_voice"
88 #define KEY_AUTO_DEVOICE "auto_devoice"
89 #define KEY_LAST_ACTIVE "last_active"
92 #define HSFMT_TIME "%a, %d %b %Y %H:%M:%S %Z"
93 static const struct message_entry msgtab[] = {
94 { "HSMSG_READHELP_SUCCESS", "Read HelpServ help database in %lu.%03lu seconds." },
95 { "HSMSG_INVALID_BOT", "This command requires a valid HelpServ bot name." },
96 { "HSMSG_ILLEGAL_CHANNEL", "$b%s$b is an illegal channel; cannot use it." },
97 { "HSMSG_INTERNAL_COMMAND", "$b%s$b appears to be an internal HelpServ command, sorry." },
98 { "HSMSG_NOT_IN_USERLIST", "%s lacks access to $b%s$b." },
99 { "HSMSG_LEVEL_TOO_LOW", "You lack access to this command." },
100 { "HSMSG_OPER_CMD", "This command can only be executed via $O." },
101 { "HSMSG_WTF_WHO_ARE_YOU", "$bUnable to find you on the %s userlist.$b This is a bug. Please report it." },
102 { "HSMSG_NO_USE_OPSERV", "This command cannot be used via $O. If you really need to use it, add yourself to the userlist." },
103 { "HSMSG_OPSERV_NEED_USER", "To use this command via $O, you must supply a user to target." },
104 { "HSMSG_PAGE_REQUEST", "Page from $b%s$b: $b%s$b" },
105 { "HSMSG_BAD_REQ_TYPE", "I don't know how to list $b%s$b requests." },
108 { "HSMSG_GREET_EXISTING_REQ", "Welcome back to %s. You have already opened request ID#%lu. Any messages you send to $S will be appended to that request." },
109 { "HSMSG_GREET_PRIVMSG_EXISTREQ", "Hello again. Your account has a previously opened request ID#%lu. This message and any further messages you send to $S will be appended to that request." },
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 { "HSMSG_MODSTATS_BAD_FIELD", "The specified field does not exist." },
127 { "HSMSG_MODSTATS_BAD_WEEK", "The specified week is invalid." },
128 { "HSMSG_MODSTATS_NEGATIVE", "This modification would result in a negative value." },
129 { "HSMSG_MODSTATS_SUCCESS", "$b%s$b's stats have been modified successfully." },
132 { "HSMSG_ILLEGAL_NICK", "$b%s$b is an illegal nick; cannot use it." },
133 { "HSMSG_NICK_EXISTS", "The nick %s is in use by someone else." },
134 { "HSMSG_REG_SUCCESS", "%s now has ownership of bot %s." },
135 { "HSMSG_NEED_UNREG_CONFIRM", "To unregister this bot, you must /msg $S unregister CONFIRM" },
136 { "HSMSG_ERROR_ADDING_SERVICE", "Error creating new user $b%s$b." },
139 { "HSMSG_RENAMED", "%s has been renamed to $b%s$b." },
140 { "HSMSG_MOVE_SAME_CHANNEL", "You cannot move %s to the same channel it is on." },
141 { "HSMSG_INVALID_MOVE", "$b%s$b is not a valid nick or channel name." },
142 { "HSMSG_NEED_GIVEOWNERSHIP_CONFIRM", "To transfer ownership of this bot, you must /msg $S giveownership newowner CONFIRM" },
143 { "HSMSG_MULTIPLE_OWNERS", "There is more than one owner of %s; please use other commands to change ownership." },
144 { "HSMSG_NO_TRANSFER_SELF", "You cannot give ownership to your own account." },
145 { "HSMSG_OWNERSHIP_GIVEN", "Ownership of $b%s$b has been transferred to account $b%s$b." },
148 { "HSMSG_INVALID_OPTION", "$b%s$b is not a valid option." },
149 { "HSMSG_QUEUE_OPTIONS", "HelpServ Queue Options:" },
150 { "HSMSG_SET_COMMAND_TYPE", "$bPageType $b %s" },
151 { "HSMSG_SET_ALERT_TYPE", "$bAlertPageType $b %s" },
152 { "HSMSG_SET_STATUS_TYPE", "$bStatusPageType $b %s" },
153 { "HSMSG_SET_COMMAND_TARGET", "$bPageTarget $b %s" },
154 { "HSMSG_SET_ALERT_TARGET", "$bAlertPageTarget $b %s" },
155 { "HSMSG_SET_STATUS_TARGET", "$bStatusPageTarget$b %s" },
156 { "HSMSG_SET_GREETING", "$bGreeting $b %s" },
157 { "HSMSG_SET_REQOPENED", "$bReqOpened $b %s" },
158 { "HSMSG_SET_REQASSIGNED", "$bReqAssigned $b %s" },
159 { "HSMSG_SET_REQCLOSED", "$bReqClosed $b %s" },
160 { "HSMSG_SET_IDLEDELAY", "$bIdleDelay $b %s" },
161 { "HSMSG_SET_WHINEDELAY", "$bWhineDelay $b %s" },
162 { "HSMSG_SET_WHINEINTERVAL", "$bWhineInterval $b %s" },
163 { "HSMSG_SET_EMPTYINTERVAL", "$bEmptyInterval $b %s" },
164 { "HSMSG_SET_STALEDELAY", "$bStaleDelay $b %s" },
165 { "HSMSG_SET_REQPERSIST", "$bReqPersist $b %s" },
166 { "HSMSG_SET_HELPERPERSIST", "$bHelperPersist $b %s" },
167 { "HSMSG_SET_NOTIFICATION", "$bNotification $b %s" },
168 { "HSMSG_SET_IDWRAP", "$bIDWrap $b %d" },
169 { "HSMSG_SET_REQMAXLEN", "$bReqMaxLen $b %d" },
170 { "HSMSG_SET_PRIVMSGONLY", "$bPrivmsgOnly $b %s" },
171 { "HSMSG_SET_REQONJOIN", "$bReqOnJoin $b %s" },
172 { "HSMSG_SET_AUTOVOICE", "$bAutoVoice $b %s" },
173 { "HSMSG_SET_AUTODEVOICE", "$bAutoDevoice $b %s" },
174 { "HSMSG_PAGE_NOTICE", "notice" },
175 { "HSMSG_PAGE_PRIVMSG", "privmsg" },
176 { "HSMSG_PAGE_ONOTICE", "onotice" },
177 { "HSMSG_LENGTH_PART", "part" },
178 { "HSMSG_LENGTH_QUIT", "quit" },
179 { "HSMSG_LENGTH_CLOSE", "close" },
180 { "HSMSG_NOTIFY_DROP", "ReqDrop" },
181 { "HSMSG_NOTIFY_USERCHANGES", "UserChanges" },
182 { "HSMSG_NOTIFY_ACCOUNTCHANGES", "AccountChanges" },
183 { "HSMSG_INVALID_INTERVAL", "Sorry, %s must be at least %s." },
184 { "HSMSG_0_DISABLED", "0 (Disabled)" },
185 { "HSMSG_NEED_MANAGER", "Only managers or higher can do this." },
186 { "HSMSG_SET_NEED_OPER", "This option can only be set by an oper." },
189 { "HSMSG_REQ_INVALID", "$b%s$b is not a valid request ID, or there are no requests for that nick or account." },
190 { "HSMSG_REQ_NOT_YOURS_ASSIGNED_TO", "Request $b%lu$b is not assigned to you; it is currently assigned to %s." },
191 { "HSMSG_REQ_NOT_YOURS_UNASSIGNED", "Request $b%lu$b is not assigned to you; it is currently unassigned." },
192 { "HSMSG_REQ_FOUNDMANY", "The user you entered had multiple requests. The oldest one is being used." },
193 { "HSMSG_REQ_CLOSED", "Request $b%lu$b has been closed." },
194 { "HSMSG_REQ_NO_UNASSIGNED", "There are no unassigned requests." },
195 { "HSMSG_USERCMD_NO_REQUEST", "You must have an open request to use a user command." },
196 { "HSMSG_USERCMD_UNKNOWN", "I do not know the user command $b%s$b." },
197 { "HSMSG_REQ_YOU_NOT_IN_HELPCHAN_OPEN", "You cannot open this request as you are not in %s." },
198 { "HSMSG_REQ_YOU_NOT_IN_HELPCHAN", "You cannot be assigned this request as you are not in %s." },
199 { "HSMSG_REQ_HIM_NOT_IN_HELPCHAN", "%s cannot be assigned this request as they are not in %s." },
200 { "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." },
201 { "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." },
202 { "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." },
203 { "HSMSG_REQ_ASSIGNED_YOU", "You have been assigned request ID#%lu:" },
204 { "HSMSG_REQ_REASSIGNED", "You have been assigned request ID#%lu (reassigned from %s):" },
205 { "HSMSG_REQ_INFO_1", "Request ID#%lu:" },
206 { "HSMSG_REQ_INFO_2a", " - Nick %s / Account %s" },
207 { "HSMSG_REQ_INFO_2b", " - Nick %s / Not authed" },
208 { "HSMSG_REQ_INFO_2c", " - Online somewhere / Account %s" },
209 { "HSMSG_REQ_INFO_2d", " - Not online / Account %s" },
210 { "HSMSG_REQ_INFO_2e", " - Not online / No account" },
211 { "HSMSG_REQ_INFO_3", " - Opened at %s (%s ago)" },
212 { "HSMSG_REQ_INFO_4", " - Message:" },
213 { "HSMSG_REQ_INFO_MESSAGE", " %s" },
214 { "HSMSG_REQ_ASSIGNED", "Your helper for request ID#%lu is %s (Current nick: %s)" },
215 { "HSMSG_REQ_ASSIGNED_AGAIN", "Your helper for request ID#%lu has been changed to %s (Current nick: %s)" },
216 { "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." },
217 { "HSMSG_REQ_NEW", "Your message has been recorded and assigned request ID#%lu. A helper should contact you shortly." },
218 { "HSMSG_REQ_NEWONJOIN", "Welcome to %s. You have been assigned request ID#%lu. A helper should contact you shortly." },
219 { "HSMSG_REQ_UNHANDLED_TIME", "The oldest unhandled request has been waiting for %s." },
220 { "HSMSG_REQ_NO_UNHANDLED", "There are no other unhandled requests." },
221 { "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." },
222 { "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." },
223 { "HSMSG_REQ_PERSIST_HANDLE", "Everything you tell me until you are helped will be recorded." },
224 { "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." },
225 { "HSMSQ_REQ_TEXT_ADDED", "Message from $b%s:$b %s" },
226 { "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." },
228 /* Messages that are inserted into request text */
229 { "HSMSG_REQMSG_NOTE_ADDED", "Your note for request ID#%lu has been recorded." },
231 /* Automatically generated page messages */
232 { "HSMSG_PAGE_NEW_REQUEST_AUTHED", "New request (ID#%lu) from $b%s$b (Account %s)" },
233 { "HSMSG_PAGE_NEW_REQUEST_UNAUTHED", "New request (ID#%lu) from $b%s$b (Not logged in)" },
234 { "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." },
235 { "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." },
236 { "HSMSG_PAGE_CLOSE_REQUEST_1", "Request ID#%lu from $b%s$b (Account %s) has been closed by %s." },
237 { "HSMSG_PAGE_CLOSE_REQUEST_2", "Request ID#%lu from $b%s$b (Not authed) has been closed by %s." },
238 { "HSMSG_PAGE_CLOSE_REQUEST_3", "Request ID#%lu from an offline user (Account %s) has been closed by %s." },
239 { "HSMSG_PAGE_CLOSE_REQUEST_4", "Request ID#%lu from an offline user (no account) has been closed by %s." },
240 { "HSMSG_PAGE_ASSIGN_REQUEST_1", "Request ID#%lu from $b%s$b (Account %s) has been assigned to %s." },
241 { "HSMSG_PAGE_ASSIGN_REQUEST_2", "Request ID#%lu from $b%s$b (Not authed) has been assigned to %s." },
242 { "HSMSG_PAGE_ASSIGN_REQUEST_3", "Request ID#%lu from an offline user (Account %s) has been assigned to %s." },
243 { "HSMSG_PAGE_ASSIGN_REQUEST_4", "Request ID#%lu from an offline user (no account) has been assigned to %s." },
244 /* The last %s is still an I18N lose. Blame whoever decided to overload it so much. */
245 { "HSMSG_PAGE_HELPER_GONE_1", "Request ID#%lu from $b%s$b (Account %s) $bhas been unassigned$b, as its helper, %s has %s." },
246 { "HSMSG_PAGE_HELPER_GONE_2", "Request ID#%lu from $b%s$b (Not authed) $bhas been unassigned$b, as its helper, %s has %s." },
247 { "HSMSG_PAGE_HELPER_GONE_3", "Request ID#%lu from an offline user (Account %s) $bhas been unassigned$b, as its helper, %s has %s." },
248 { "HSMSG_PAGE_HELPER_GONE_4", "Request ID#%lu from an offline user (No account) $bhas been unassigned$b, as its helper, %s has %s." },
249 { "HSMSG_PAGE_WHINE_HEADER", "$b%u unhandled request(s)$b waiting at least $b%s$b (%u unhandled, %u total)" },
250 { "HSMSG_PAGE_IDLE_HEADER", "$b%u users$b in %s $bidle at least %s$b:" },
251 { "HSMSG_PAGE_EMPTYALERT", "$b%s has no helpers present (%u unhandled request(s))$b" },
252 { "HSMSG_PAGE_ONLYTRIALALERT", "$b%s has no full helpers present (%d trial(s) present; %u unhandled request(s))$b" },
253 { "HSMSG_PAGE_FIRSTEMPTYALERT", "$b%s has no helpers present because %s has left (%u unhandled request(s))$b" },
254 { "HSMSG_PAGE_FIRSTONLYTRIALALERT", "$b%s has no full helpers present because %s has left (%d trial(s) present; %u unhandled request(s))$b" },
255 { "HSMSG_PAGE_EMPTYNOMORE", "%s has joined %s; cancelling the \"no helpers present\" alert" },
257 /* Notification messages */
258 { "HSMSG_NOTIFY_USER_QUIT", "The user for request ID#%lu, $b%s$b, has disconnected." },
259 { "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." },
260 { "HSMSG_NOTIFY_USER_NICK", "The user for request ID#%lu has changed their nick from $b%s$b to $b%s$b." },
261 { "HSMSG_NOTIFY_USER_FOUND", "The user for request ID#%lu is now online using nick $b%s$b." },
262 { "HSMSG_NOTIFY_HAND_RENAME", "The account for request ID#%lu has been renamed from $b%s$b to $b%s$b." },
263 { "HSMSG_NOTIFY_HAND_MOVE", "The account for request ID#%lu has been changed to $b%s$b from $b%s$b." },
264 { "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." },
265 { "HSMSG_NOTIFY_HAND_AUTH", "The user for request ID#%lu, $b%s$b, has authenticated to account $b%s$b." },
266 { "HSMSG_NOTIFY_HAND_UNREG", "The account for request ID#%lu, $b%s$b, has been unregistered by $b%s$b." },
267 { "HSMSG_NOTIFY_HAND_MERGE", "The account for request ID#%lu, $b%s$b, has been merged with $b%s$b by %s." },
268 { "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." },
269 { "HSMSG_NOTIFY_UNALLOWAUTH", "The user for request ID#%lu, $b%s$b, has had their ability to authenticate without hostmask checking revoked by %s." },
270 { "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." },
271 { "HSMSG_NOTIFY_REQ_DROP_PART", "Request ID#%lu has been $bdropped$b because %s left the help channel." },
272 { "HSMSG_NOTIFY_REQ_DROP_QUIT", "Request ID#%lu has been $bdropped$b because %s quit IRC." },
273 { "HSMSG_NOTIFY_REQ_DROP_UNREGGED", "Request ID#%lu (account %s) has been $bdropped$b because the account was unregistered." },
275 /* Presence and request-related messages */
276 { "HSMSG_REQ_DROPPED_PART", "You have left $b%s$b. Your help request (ID#%lu) has been deleted." },
277 { "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." },
278 { "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." },
279 { "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." },
280 { "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." },
281 { "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." },
284 { "HSMSG_BOTLIST_HEADER", "$bCurrent HelpServ bots:$b" },
285 { "HSMSG_USERLIST_HEADER", "$b%s users:$b" },
286 { "HSMSG_USERLIST_ZOOT_LVL", "%s $b%ss$b:" },
287 { "HSMSG_REQLIST_AUTH", "You are currently assigned these requests:" },
288 { "HSMSG_REQ_LIST_TOP_UNASSIGNED", "Listing $ball unassigned$b requests (%d in list)." },
289 { "HSMSG_REQ_LIST_TOP_ASSIGNED", "Listing $ball assigned$b requests (%d in list)." },
290 { "HSMSG_REQ_LIST_TOP_YOUR", "Listing $byour$b requests (%d in list)." },
291 { "HSMSG_REQ_LIST_TOP_ALL", "Listing $ball$b requests (%d in list)." },
292 { "HSMSG_REQ_LIST_NONE", "There are no matching requests." },
293 { "HSMSG_STATS_TOP", "Stats for %s user $b%s$b (week starts %s):" },
294 { "HSMSG_STATS_TIME", "$uTime spent helping in %s:$u" },
295 { "HSMSG_STATS_REQS", "$uRequest activity statistics:$u" },
297 /* Status report headers */
298 { "HSMSG_STATS_REPORT_0", "Stats report for current week" },
299 { "HSMSG_STATS_REPORT_1", "Stats report for one week ago" },
300 { "HSMSG_STATS_REPORT_2", "Stats report for two weeks ago" },
301 { "HSMSG_STATS_REPORT_3", "Stats report for three weeks ago" },
303 /* Responses to user commands */
304 { "HSMSG_YOU_BEING_HELPED", "You are already being helped." },
305 { "HSMSG_YOU_BEING_HELPED_BY", "You are already being helped by $b%s$b." },
306 { "HSMSG_WAIT_STATUS", "You are %d of %d in line; the first person has waited %s." },
310 enum helpserv_level {
320 static const char *helpserv_level_names[] = {
338 static const struct {
343 { "none", "MSG_NONE", NULL },
344 { "notice", "HSMSG_PAGE_NOTICE", irc_notice },
345 { "privmsg", "HSMSG_PAGE_PRIVMSG", irc_privmsg },
346 { "onotice", "HSMSG_PAGE_ONOTICE", irc_wallchops },
357 static const struct {
362 { "command", "HSMSG_SET_COMMAND_TARGET", "HSMSG_SET_COMMAND_TYPE" },
363 { "alert", "HSMSG_SET_ALERT_TARGET", "HSMSG_SET_ALERT_TYPE" },
364 { "status", "HSMSG_SET_STATUS_TARGET", "HSMSG_SET_STATUS_TYPE" },
371 MSGTYPE_REQ_ASSIGNED,
377 static const struct {
380 } message_types[] = {
381 { "greeting", "HSMSG_SET_GREETING" },
382 { "reqopened", "HSMSG_SET_REQOPENED" },
383 { "reqassigned", "HSMSG_SET_REQASSIGNED" },
384 { "reqclosed", "HSMSG_SET_REQCLOSED" },
385 { "reqdropped", "HSMSG_SET_REQDROPPED" },
391 INTERVAL_WHINE_DELAY,
392 INTERVAL_WHINE_INTERVAL,
393 INTERVAL_EMPTY_INTERVAL,
394 INTERVAL_STALE_DELAY,
398 static const struct {
401 } interval_types[] = {
402 { "idledelay", "HSMSG_SET_IDLEDELAY" },
403 { "whinedelay", "HSMSG_SET_WHINEDELAY" },
404 { "whineinterval", "HSMSG_SET_WHINEINTERVAL" },
405 { "emptyinterval", "HSMSG_SET_EMPTYINTERVAL" },
406 { "staledelay", "HSMSG_SET_STALEDELAY" },
410 enum persistence_type {
416 static const struct {
419 } persistence_types[] = {
420 { "reqpersist", "HSMSG_SET_REQPERSIST" },
421 { "helperpersist", "HSMSG_SET_HELPERPERSIST" },
425 enum persistence_length {
432 static const struct {
435 } persistence_lengths[] = {
436 { "part", "HSMSG_LENGTH_PART" },
437 { "quit", "HSMSG_LENGTH_QUIT" },
438 { "close", "HSMSG_LENGTH_CLOSE" },
442 enum notification_type {
450 static const struct {
453 } notification_types[] = {
454 { "none", "MSG_NONE" },
455 { "reqdrop", "HSMSG_NOTIFY_DROP" },
456 { "userchanges", "HSMSG_NOTIFY_USERCHANGES" },
457 { "accountchanges", "HSMSG_NOTIFY_ACCOUNTCHANGES" },
461 static const char *weekday_names[] = {
472 static const char *statsreport_week[] = {
473 "HSMSG_STATS_REPORT_0",
474 "HSMSG_STATS_REPORT_1",
475 "HSMSG_STATS_REPORT_2",
476 "HSMSG_STATS_REPORT_3"
480 const char *description;
481 const char *reqlogfile;
482 unsigned long db_backup_frequency;
483 unsigned int expire_age;
484 unsigned long modstats_level;
488 static unsigned long last_stats_update;
489 static int shutting_down;
490 static FILE *reqlog_f;
491 static struct log_type *HS_LOG;
493 #define CMD_NEED_BOT 0x001
494 #define CMD_NOT_OVERRIDE 0x002
495 #define CMD_FROM_OPSERV_ONLY 0x004
496 #define CMD_IGNORE_EVENT 0x008
497 #define CMD_NEVER_FROM_OPSERV 0x010
499 struct helpserv_bot {
500 struct userNode *helpserv;
502 struct chanNode *helpchan;
504 struct chanNode *page_targets[PGSRC_COUNT];
505 enum page_type page_types[PGSRC_COUNT];
506 char *messages[MSGTYPE_COUNT];
507 unsigned long intervals[INTERVAL_COUNT];
508 enum notification_type notify;
510 /* This is a default; it can be changed on a per-request basis */
511 enum persistence_length persist_lengths[PERSIST_T_COUNT];
513 dict_t users; /* indexed by handle */
515 struct helpserv_request *unhandled; /* linked list of unhandled requests */
516 dict_t requests; /* indexed by request id */
517 unsigned long last_requestid;
518 unsigned long id_wrap;
519 unsigned long req_maxlen; /* Maxmimum request length in lines */
521 unsigned int privmsg_only : 1;
522 unsigned int req_on_join : 1;
523 unsigned int auto_voice : 1;
524 unsigned int auto_devoice : 1;
526 unsigned int helpchan_empty : 1;
528 unsigned long registered;
529 unsigned long last_active;
533 struct helpserv_user {
534 struct handle_info *handle;
535 struct helpserv_bot *hs;
536 unsigned int help_mode : 1;
537 unsigned int week_start : 3;
538 enum helpserv_level level;
540 unsigned long join_time; /* when they joined, or 0 if not in channel */
541 /* [0] through [3] are n weeks ago, [4] is the total of everything before that */
542 unsigned int time_per_week[5]; /* how long they've were in the channel the past 4 weeks */
543 unsigned int picked_up[5]; /* how many requests they have picked up */
544 unsigned int closed[5]; /* how many requests they have closed */
545 unsigned int reassigned_from[5]; /* how many requests reassigned from them to others */
546 unsigned int reassigned_to[5]; /* how many requests reassigned from others to them */
549 struct helpserv_request {
550 struct helpserv_user *helper;
551 struct helpserv_bot *hs;
552 struct string_list *text;
553 struct helpserv_request *next_unhandled;
555 struct helpserv_reqlist *parent_nick_list;
556 struct helpserv_reqlist *parent_hand_list;
558 /* One, but not both, of "user" and "handle" may be NULL,
559 * depending on what we know about the user.
561 * If persist == PERSIST_CLOSE when the user quits, then it
562 * switches to handle instead of user... and stays that way (it's
563 * possible to have >1 nick per handle, so you can't really decide
564 * to reassign a userNode to it unless they send another message
567 struct userNode *user;
568 struct handle_info *handle;
571 unsigned long opened;
572 unsigned long assigned;
573 unsigned long updated;
576 #define DEFINE_LIST_ALLOC(STRUCTNAME) \
577 struct STRUCTNAME * STRUCTNAME##_alloc() {\
578 struct STRUCTNAME *newlist; \
579 newlist = malloc(sizeof(struct STRUCTNAME)); \
580 STRUCTNAME##_init(newlist); \
583 void STRUCTNAME##_free(void *data) {\
584 struct STRUCTNAME *list = data; /* void * to let dict_set_free_data use it */ \
585 STRUCTNAME##_clean(list); \
589 DECLARE_LIST(helpserv_botlist, struct helpserv_bot *);
590 DEFINE_LIST(helpserv_botlist, struct helpserv_bot *)
591 DEFINE_LIST_ALLOC(helpserv_botlist)
593 DECLARE_LIST(helpserv_reqlist, struct helpserv_request *);
594 DEFINE_LIST(helpserv_reqlist, struct helpserv_request *)
595 DEFINE_LIST_ALLOC(helpserv_reqlist)
597 DECLARE_LIST(helpserv_userlist, struct helpserv_user *);
598 DEFINE_LIST(helpserv_userlist, struct helpserv_user *)
599 DEFINE_LIST_ALLOC(helpserv_userlist)
601 struct helpfile *helpserv_helpfile;
602 static struct module *helpserv_module;
603 static dict_t helpserv_func_dict;
604 static dict_t helpserv_usercmd_dict; /* contains helpserv_usercmd_t */
605 static dict_t helpserv_option_dict;
606 static dict_t helpserv_bots_dict; /* indexed by nick */
607 static dict_t helpserv_bots_bychan_dict; /* indexed by chan, holds a struct helpserv_botlist */
608 /* QUESTION: why are these outside of any helpserv_bot struct? */
609 static dict_t helpserv_reqs_bynick_dict; /* indexed by nick, holds a struct helpserv_reqlist */
610 static dict_t helpserv_reqs_byhand_dict; /* indexed by handle, holds a struct helpserv_reqlist */
611 static dict_t helpserv_users_byhand_dict; /* indexed by handle, holds a struct helpserv_userlist */
613 /* This is so that overrides can "speak" from opserv */
614 extern struct userNode *opserv;
616 #define HELPSERV_SYNTAX() helpserv_help(hs, from_opserv, user, argv[0])
617 #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[]))
618 typedef HELPSERV_FUNC(helpserv_func_t);
619 #define HELPSERV_USERCMD(NAME) void NAME(struct helpserv_request *req, UNUSED_ARG(struct userNode *likely_helper), UNUSED_ARG(char *args))
620 typedef HELPSERV_USERCMD(helpserv_usercmd_t);
621 #define HELPSERV_OPTION(NAME) HELPSERV_FUNC(NAME)
622 typedef HELPSERV_OPTION(helpserv_option_func_t);
624 static HELPSERV_FUNC(cmd_help);
626 #define REQUIRE_PARMS(N) if (argc < N) { \
627 helpserv_notice(user, "MSG_MISSING_PARAMS", argv[0]); \
631 /* For messages going to users being helped */
632 #if defined(GCC_VARMACROS)
633 # define helpserv_msguser(target, ARGS...) send_message_type((from_opserv ? 0 : hs->privmsg_only), (target), (from_opserv ? opserv : hs->helpserv), ARGS)
634 # define helpserv_user_reply(ARGS...) send_message_type(req->hs->privmsg_only, req->user, req->hs->helpserv, ARGS)
635 /* For messages going to helpers */
636 # define helpserv_notice(target, ARGS...) send_message((target), (from_opserv ? opserv : hs->helpserv), ARGS)
637 # define helpserv_notify(helper, ARGS...) do { struct userNode *_target; for (_target = (helper)->handle->users; _target; _target = _target->next_authed) { \
638 if(!_target->next_authed || GetUserMode(helper->hs->helpchan, _target)) {\
639 send_message(_target, (helper)->hs->helpserv, ARGS); \
643 # define helpserv_page(TYPE, ARGS...) do { \
644 int msg_type=0; struct chanNode *target=helpserv_get_page_type(hs, (TYPE), &msg_type); \
645 if (target) send_target_message(msg_type, target->name, hs->helpserv, ARGS); \
647 #elif defined(C99_VARMACROS)
648 # define helpserv_msguser(target, ...) send_message_type((from_opserv ? 0 : hs->privmsg_only), (target), (from_opserv ? opserv : hs->helpserv), __VA_ARGS__)
649 # define helpserv_user_reply(...) send_message_type(req->hs->privmsg_only, req->user, req->hs->helpserv, __VA_ARGS__)
650 /* For messages going to helpers */
651 # define helpserv_notice(target, ...) send_message((target), (from_opserv ? opserv : hs->helpserv), __VA_ARGS__)
652 # define helpserv_notify(helper, ...) do { struct userNode *_target; for (_target = (helper)->handle->users; _target; _target = _target->next_authed) { \
653 if(!_target->next_authed || GetUserMode(helper->hs->helpchan, _target)) {\
654 send_message(_target, (helper)->hs->helpserv, __VA_ARGS__); \
658 # define helpserv_page(TYPE, ...) do { \
659 int msg_type=0; struct chanNode *target=helpserv_get_page_type(hs, (TYPE), &msg_type); \
660 if (target) send_target_message(msg_type, target->name, hs->helpserv, __VA_ARGS__); \
663 #define helpserv_message(hs, target, id) do { if ((hs)->messages[id]) { \
665 send_message_type(4, (target), opserv, "%s", (hs)->messages[id]); \
667 send_message_type(4 | hs->privmsg_only, (target), hs->helpserv, "%s", (hs)->messages[id]); \
669 #define helpserv_get_handle_info(user, text) smart_get_handle_info((from_opserv ? opserv : hs->helpserv) , (user), (text))
671 struct helpserv_cmd {
672 enum helpserv_level access;
673 helpserv_func_t *func;
678 static void run_empty_interval(void *data);
680 static void helpserv_interval(char *output, unsigned long interval) {
681 int num_hours = interval / 3600;
682 int num_minutes = (interval % 3600) / 60;
683 sprintf(output, "%u hour%s, %u minute%s", num_hours, num_hours == 1 ? "" : "s", num_minutes, num_minutes == 1 ? "" : "s");
686 static const char * helpserv_level2str(enum helpserv_level level) {
687 if (level <= HlOper) {
688 return helpserv_level_names[level];
690 log_module(HS_LOG, LOG_ERROR, "helpserv_level2str receieved invalid level %d.", level);
695 static enum helpserv_level helpserv_str2level(const char *msg) {
696 enum helpserv_level nn;
697 for (nn=HlNone; nn<=HlOper; nn++) {
698 if (!irccasecmp(msg, helpserv_level_names[nn]))
701 log_module(HS_LOG, LOG_ERROR, "helpserv_str2level received invalid level %s.", msg);
702 return HlNone; /* Error */
705 static struct helpserv_user *GetHSUser(struct helpserv_bot *hs, struct handle_info *hi) {
706 return dict_find(hs->users, hi->handle, NULL);
709 static void helpserv_log_request(struct helpserv_request *req, const char *reason) {
710 char key[27+NICKLEN];
711 char userhost[USERLEN+HOSTLEN+2];
712 struct saxdb_context *ctx;
716 assert(reason != NULL);
717 if (!reqlog_f || !(ctx = saxdb_open_context(reqlog_f)))
719 sprintf(key, "%s-%lu-%lu", req->hs->helpserv->nick, (unsigned long)req->opened, req->id);
720 if ((res = setjmp(*saxdb_jmp_buf(ctx))) != 0) {
721 log_module(HS_LOG, LOG_ERROR, "Unable to log helpserv request: %s.", strerror(res));
723 saxdb_start_record(ctx, key, 1);
725 saxdb_write_string(ctx, KEY_REQUEST_HELPER, req->helper->handle->handle);
726 saxdb_write_int(ctx, KEY_REQUEST_ASSIGNED, req->assigned);
729 saxdb_write_string(ctx, KEY_REQUEST_HANDLE, req->handle->handle);
732 saxdb_write_string(ctx, KEY_REQUEST_NICK, req->user->nick);
733 sprintf(userhost, "%s@%s", req->user->ident, req->user->hostname);
734 saxdb_write_string(ctx, KEY_REQUEST_USERHOST, userhost);
736 saxdb_write_int(ctx, KEY_REQUEST_OPENED, req->opened);
737 saxdb_write_int(ctx, KEY_REQUEST_CLOSED, now);
738 saxdb_write_string(ctx, KEY_REQUEST_CLOSEREASON, reason);
739 saxdb_write_string_list(ctx, KEY_REQUEST_TEXT, req->text);
740 saxdb_end_record(ctx);
741 saxdb_close_context(ctx, 0);
745 static struct chanNode *helpserv_get_page_type(struct helpserv_bot *hs, enum page_source type, int *msg_type)
747 switch (hs->page_types[type]) {
758 log_module(HS_LOG, LOG_ERROR, "helpserv_page() called but %s has an invalid page type %d.", hs->helpserv->nick, type);
759 /* and fall through */
763 return hs->page_targets[type];
766 /* Searches for a request by number, nick, or account (num|nick|*account).
767 * As there can potentially be >1 match, it takes a reqlist. The return
768 * value is the "best" request found (explained in the comment block below).
770 * If num_results is not NULL, it is set to the number of potentially matching
772 * If hs_user is not NULL, requests assigned to this helper will be given
773 * preference (oldest assigned, falling back to oldest if there are none).
775 static struct helpserv_request * smart_get_request(struct helpserv_bot *hs, struct helpserv_user *hs_user, const char *needle, int *num_results) {
776 struct helpserv_reqlist *reqlist, resultlist;
777 struct helpserv_request *req, *oldest=NULL, *oldest_assigned=NULL;
778 struct userNode *user;
784 if (*needle == '*') {
785 /* This test (handle) requires the least processing, so it's first */
786 if (!(reqlist = dict_find(helpserv_reqs_byhand_dict, needle+1, NULL)))
788 helpserv_reqlist_init(&resultlist);
789 for (i=0; i < reqlist->used; i++) {
790 req = reqlist->list[i];
792 helpserv_reqlist_append(&resultlist, req);
797 } else if (!needle[strspn(needle, "0123456789")]) {
798 /* The string is 100% numeric - a request id */
799 if (!(req = dict_find(hs->requests, needle, NULL)))
804 } else if ((user = GetUserH(needle))) {
805 /* And finally, search by nick */
806 if (!(reqlist = dict_find(helpserv_reqs_bynick_dict, needle, NULL)))
808 helpserv_reqlist_init(&resultlist);
810 for (i=0; i < reqlist->used; i++) {
811 req = reqlist->list[i];
813 helpserv_reqlist_append(&resultlist, req);
818 /* If the nick didn't have anything, try their handle */
819 if (!resultlist.used && user->handle_info) {
820 char star_handle[NICKSERV_HANDLE_LEN+2];
822 helpserv_reqlist_clean(&resultlist);
823 sprintf(star_handle, "*%s", user->handle_info->handle);
825 return smart_get_request(hs, hs_user, star_handle, num_results);
831 if (resultlist.used == 0) {
832 helpserv_reqlist_clean(&resultlist);
834 } else if (resultlist.used == 1) {
835 req = resultlist.list[0];
836 helpserv_reqlist_clean(&resultlist);
840 /* In case there is >1 request returned, use the oldest one assigned to
841 * the helper executing the command. Otherwise, use the oldest request.
842 * This may not be the intended result for cmd_pickup (first unhandled
843 * request may be better), or cmd_reassign (first handled request), but
844 * it's close enough, and there really aren't supposed to be multiple
845 * requests per person anyway; they're just side effects of authing. */
847 for (i=0; i < resultlist.used; i++) {
848 req = resultlist.list[i];
849 if (!oldest || req->opened < oldest->opened)
851 if (hs_user && (!oldest_assigned || (req->helper == hs_user && req->opened < oldest_assigned->opened)))
852 oldest_assigned = req;
855 helpserv_reqlist_clean(&resultlist);
857 return oldest_assigned ? oldest_assigned : oldest;
860 static struct helpserv_request * create_request(struct userNode *user, struct helpserv_bot *hs, int from_join) {
861 struct helpserv_request *req = calloc(1, sizeof(struct helpserv_request));
862 char lbuf[3][MAX_LINE_SIZE], req_id[INTERVALLEN];
863 struct helpserv_reqlist *reqlist, *hand_reqlist;
864 const unsigned int from_opserv = 0;
869 req->id = ++hs->last_requestid;
870 sprintf(req_id, "%lu", req->id);
871 dict_insert(hs->requests, strdup(req_id), req);
876 if (hs->last_requestid < hs->id_wrap) {
877 for (i=hs->last_requestid; i < hs->id_wrap; i++) {
878 sprintf(buf, "%lu", i);
879 if (!dict_find(hs->requests, buf, NULL)) {
880 hs->last_requestid = i-1;
885 if (hs->last_requestid >= hs->id_wrap) {
886 for (i=1; i < hs->id_wrap; i++) {
887 sprintf(buf, "%lu", i);
888 if (!dict_find(hs->requests, buf, NULL)) {
889 hs->last_requestid = i-1;
893 if (i >= hs->id_wrap) {
894 log_module(HS_LOG, LOG_INFO, "%s has more requests than its id_wrap.", hs->helpserv->nick);
901 req->text = alloc_string_list(4);
903 req->handle = user->handle_info;
904 if (from_join && self->burst) {
905 extern unsigned long burst_begin;
906 /* We need to keep all the requests during a burst join together,
907 * even if the burst takes more than 1 second. ircu seems to burst
908 * in reverse-join order. */
909 req->opened = burst_begin;
915 if (!hs->unhandled) {
917 req->next_unhandled = NULL;
918 } else if (self->burst && hs->unhandled->opened >= req->opened) {
919 req->next_unhandled = hs->unhandled;
921 } else if (self->burst) {
922 struct helpserv_request *unh;
923 /* Add the request to the beginning of the set of requests with
924 * req->opened having the same value. This makes reqonjoin create
925 * requests in the correct order while bursting. Note that this
926 * does not correct request ids, so they will be in reverse order
927 * though "/msg botname next" will work properly. */
928 for (unh = hs->unhandled; unh->next_unhandled && unh->next_unhandled->opened < req->opened; unh = unh->next_unhandled) ;
929 req->next_unhandled = unh->next_unhandled;
930 unh->next_unhandled = req;
932 struct helpserv_request *unh;
934 for (unh = hs->unhandled; unh->next_unhandled; unh = unh->next_unhandled) ;
935 req->next_unhandled = NULL;
936 unh->next_unhandled = req;
939 if (!(reqlist = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL))) {
940 reqlist = helpserv_reqlist_alloc();
941 dict_insert(helpserv_reqs_bynick_dict, user->nick, reqlist);
943 req->parent_nick_list = reqlist;
944 helpserv_reqlist_append(reqlist, req);
946 if (user->handle_info) {
947 if (!(hand_reqlist = dict_find(helpserv_reqs_byhand_dict, user->handle_info->handle, NULL))) {
948 hand_reqlist = helpserv_reqlist_alloc();
949 dict_insert(helpserv_reqs_byhand_dict, user->handle_info->handle, hand_reqlist);
951 req->parent_hand_list = hand_reqlist;
952 helpserv_reqlist_append(hand_reqlist, req);
954 req->parent_hand_list = NULL;
958 fmt = user_find_message(user, "HSMSG_REQ_NEWONJOIN");
959 sprintf(lbuf[0], fmt, hs->helpchan->name, req->id);
961 fmt = user_find_message(user, "HSMSG_REQ_NEW");
962 sprintf(lbuf[0], fmt, req->id);
964 if (req != hs->unhandled) {
965 intervalString(req_id, now - hs->unhandled->opened, user->handle_info);
966 fmt = user_find_message(user, "HSMSG_REQ_UNHANDLED_TIME");
967 sprintf(lbuf[1], fmt, req_id);
969 fmt = user_find_message(user, "HSMSG_REQ_NO_UNHANDLED");
970 sprintf(lbuf[1], "%s", fmt);
972 switch (hs->persist_lengths[PERSIST_T_REQUEST]) {
974 fmt = user_find_message(user, "HSMSG_REQ_PERSIST_PART");
975 sprintf(lbuf[2], fmt, hs->helpchan->name, hs->helpchan->name);
978 fmt = user_find_message(user, "HSMSG_REQ_PERSIST_QUIT");
979 sprintf(lbuf[2], "%s", fmt);
982 log_module(HS_LOG, LOG_ERROR, "%s has an invalid req_persist.", hs->helpserv->nick);
984 if (user->handle_info) {
985 fmt = user_find_message(user, "HSMSG_REQ_PERSIST_HANDLE");
987 fmt = user_find_message(user, "HSMSG_REQ_PERSIST_QUIT");
989 sprintf(lbuf[2], "%s", fmt);
992 helpserv_message(hs, user, MSGTYPE_REQ_OPENED);
994 send_message_type(4, user, opserv, "%s %s %s", lbuf[0], lbuf[1], lbuf[2]);
996 send_message_type(4, user, hs->helpserv, "%s %s %s", lbuf[0], lbuf[1], lbuf[2]);
998 if (hs->req_on_join && req == hs->unhandled && hs->helpchan_empty && !user->uplink->burst) {
999 timeq_del(0, run_empty_interval, hs, TIMEQ_IGNORE_WHEN);
1000 run_empty_interval(hs);
1006 /* Handle a message from a user to a HelpServ bot. */
1007 static void helpserv_usermsg(struct userNode *user, struct helpserv_bot *hs, const char *text) {
1008 const int from_opserv = 0; /* for helpserv_notice */
1009 struct helpserv_request *req=NULL, *newest=NULL;
1010 struct helpserv_reqlist *reqlist, *hand_reqlist;
1013 if ((reqlist = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL))) {
1014 for (i=0; i < reqlist->used; i++) {
1015 req = reqlist->list[i];
1018 if (!newest || (newest->opened < req->opened))
1022 /* If nothing was found, this will set req to NULL */
1026 if (user->handle_info) {
1027 hand_reqlist = dict_find(helpserv_reqs_byhand_dict, user->handle_info->handle, NULL);
1028 if (!req && hand_reqlist) {
1029 /* Most recent first again */
1030 for (i=0; i < hand_reqlist->used; i++) {
1031 req = hand_reqlist->list[i];
1032 if ((req->hs != hs) || req->user)
1034 if (!newest || (newest->opened < req->opened))
1042 reqlist = helpserv_reqlist_alloc();
1043 dict_insert(helpserv_reqs_bynick_dict, user->nick, reqlist);
1045 req->parent_nick_list = reqlist;
1046 helpserv_reqlist_append(reqlist, req);
1048 if (req->helper && (hs->notify >= NOTIFY_USER))
1049 helpserv_notify(req->helper, "HSMSG_NOTIFY_USER_FOUND", req->id, user->nick);
1051 helpserv_msguser(user, "HSMSG_GREET_PRIVMSG_EXISTREQ", req->id);
1055 hand_reqlist = NULL;
1059 if (text[0] == helpserv_conf.user_escape) {
1060 helpserv_msguser(user, "HSMSG_USERCMD_NO_REQUEST");
1063 if ((hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_PART) && !GetUserMode(hs->helpchan, user)) {
1064 helpserv_msguser(user, "HSMSG_REQ_YOU_NOT_IN_HELPCHAN_OPEN", hs->helpchan->name);
1068 req = create_request(user, hs, 0);
1069 if (user->handle_info)
1070 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_NEW_REQUEST_AUTHED", req->id, user->nick, user->handle_info->handle);
1072 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_NEW_REQUEST_UNAUTHED", req->id, user->nick);
1073 } else if (text[0] == helpserv_conf.user_escape) {
1074 char cmdname[MAXLEN], *space;
1075 helpserv_usercmd_t *usercmd;
1076 struct userNode *likely_helper;
1078 /* Find somebody likely to be the helper */
1080 likely_helper = NULL;
1081 else if ((likely_helper = req->helper->handle->users) && !likely_helper->next_authed) {
1082 /* only one user it could be :> */
1083 } else for (likely_helper = req->helper->handle->users; likely_helper; likely_helper = likely_helper->next_authed)
1084 if (GetUserMode(hs->helpchan, likely_helper))
1087 /* Parse out command name */
1088 space = strchr(text+1, ' ');
1090 strncpy(cmdname, text+1, space-text-1);
1092 strcpy(cmdname, text+1);
1094 /* Call the user command function */
1095 usercmd = dict_find(helpserv_usercmd_dict, cmdname, NULL);
1097 usercmd(req, likely_helper, space+1);
1099 helpserv_msguser(user, "HSMSG_USERCMD_UNKNOWN", cmdname);
1101 } else if (hs->intervals[INTERVAL_STALE_DELAY]
1102 && (req->updated < now - hs->intervals[INTERVAL_STALE_DELAY])
1103 && (!hs->req_maxlen || req->text->used < hs->req_maxlen)) {
1104 char buf[MAX_LINE_SIZE], updatestr[INTERVALLEN], timestr[MAX_LINE_SIZE];
1108 strftime(timestr, MAX_LINE_SIZE, HSFMT_TIME, localtime(&feh));
1109 intervalString(updatestr, now - req->updated, user->handle_info);
1110 if (req->helper && (hs->notify >= NOTIFY_USER))
1111 if (user->handle_info)
1112 helpserv_notify(req->helper, "HSMSG_PAGE_UPD_REQUEST_AUTHED", req->id, user->nick, user->handle_info->handle, timestr, updatestr);
1114 helpserv_notify(req->helper, "HSMSG_PAGE_UPD_REQUEST_NOT_AUTHED", req->id, user->nick, timestr, updatestr);
1116 if (user->handle_info)
1117 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_UPD_REQUEST_AUTHED", req->id, user->nick, user->handle_info->handle, timestr, updatestr);
1119 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_UPD_REQUEST_NOT_AUTHED", req->id, user->nick, timestr, updatestr);
1121 strftime(timestr, MAX_LINE_SIZE, HSFMT_TIME, localtime(&feh));
1122 snprintf(buf, MAX_LINE_SIZE, "[Stale request updated at %s]", timestr);
1123 string_list_append(req->text, strdup(buf));
1127 if (!hs->req_maxlen || req->text->used < hs->req_maxlen) {
1128 struct userNode *likely_helper;
1130 string_list_append(req->text, strdup(text));
1131 /* Find somebody likely to be the helper */
1133 likely_helper = NULL;
1134 else if ((likely_helper = req->helper->handle->users) && !likely_helper->next_authed) {
1135 /* only one user it could be :> */
1136 } else for (likely_helper = req->helper->handle->users; likely_helper; likely_helper = likely_helper->next_authed)
1137 if (GetUserMode(hs->helpchan, likely_helper))
1141 send_target_message(1, likely_helper->nick, hs->helpserv, "HSMSQ_REQ_TEXT_ADDED", user->nick, text);
1144 helpserv_msguser(user, "HSMSG_REQ_MAXLEN");
1147 /* Handle messages direct to a HelpServ bot. */
1148 static void helpserv_botmsg(struct userNode *user, struct userNode *target, const char *text, UNUSED_ARG(int server_qualified)) {
1149 struct helpserv_bot *hs;
1150 struct helpserv_cmd *cmd;
1151 struct helpserv_user *hs_user;
1152 char *argv[MAXNUMPARAMS];
1153 char tmpline[MAXLEN];
1154 int argc, argv_shift;
1155 const int from_opserv = 0; /* for helpserv_notice */
1157 /* Ignore things consisting of empty lines or from ourselves */
1158 if (!*text || IsLocal(user))
1161 hs = dict_find(helpserv_bots_dict, target->nick, NULL);
1163 /* See if we should listen to their message as a command (helper)
1164 * or a help request (user) */
1165 if (!user->handle_info || !(hs_user = dict_find(hs->users, user->handle_info->handle, NULL))) {
1166 helpserv_usermsg(user, hs, text);
1171 safestrncpy(tmpline, text, sizeof(tmpline));
1172 argc = split_line(tmpline, false, ArrayLength(argv)-argv_shift, argv+argv_shift);
1176 cmd = dict_find(helpserv_func_dict, argv[argv_shift], NULL);
1178 helpserv_notice(user, "MSG_COMMAND_UNKNOWN", argv[argv_shift]);
1181 if (cmd->flags & CMD_FROM_OPSERV_ONLY) {
1182 helpserv_notice(user, "HSMSG_OPER_CMD");
1185 if (cmd->access > hs_user->level) {
1186 helpserv_notice(user, "HSMSG_LEVEL_TOO_LOW");
1190 helpserv_notice(user, "HSMSG_INTERNAL_COMMAND", argv[argv_shift]);
1191 } else if (cmd->func(user, hs, 0, argc, argv+argv_shift)) {
1192 unsplit_string(argv+argv_shift, argc, tmpline);
1193 log_audit(HS_LOG, LOG_COMMAND, user, hs->helpserv, hs->helpchan->name, 0, tmpline);
1197 /* Handle a control command from an IRC operator */
1198 static MODCMD_FUNC(cmd_helpserv) {
1199 struct helpserv_bot *hs = NULL;
1200 struct helpserv_cmd *subcmd;
1201 const int from_opserv = 1; /* for helpserv_notice */
1202 char botnick[NICKLEN+1]; /* in case command is unregister */
1206 send_help(user, opserv, helpserv_helpfile, NULL);
1210 if (!(subcmd = dict_find(helpserv_func_dict, argv[1], NULL))) {
1211 helpserv_notice(user, "MSG_COMMAND_UNKNOWN", argv[1]);
1215 if (!subcmd->func) {
1216 helpserv_notice(user, "HSMSG_INTERNAL_COMMAND", argv[1]);
1220 if ((subcmd->flags & CMD_NEED_BOT) && ((argc < 3) || !(hs = dict_find(helpserv_bots_dict, argv[2], NULL)))) {
1221 helpserv_notice(user, "HSMSG_INVALID_BOT");
1225 if (subcmd->flags & CMD_NEVER_FROM_OPSERV) {
1226 helpserv_notice(user, "HSMSG_NO_USE_OPSERV");
1232 strcpy(botnick, hs->helpserv->nick);
1233 retval = subcmd->func(user, hs, 1, argc-2, argv+2);
1235 strcpy(botnick, "No bot");
1236 retval = subcmd->func(user, hs, 1, argc-1, argv+1);
1242 static void helpserv_help(struct helpserv_bot *hs, int from_opserv, struct userNode *user, const char *topic) {
1243 send_help(user, (from_opserv ? opserv : hs->helpserv), helpserv_helpfile, topic);
1246 static int append_entry(const char *key, UNUSED_ARG(void *data), void *extra) {
1247 struct helpfile_expansion *exp = extra;
1250 row = exp->value.table.length++;
1251 exp->value.table.contents[row] = calloc(1, sizeof(char*));
1252 exp->value.table.contents[row][0] = key;
1256 static struct helpfile_expansion helpserv_expand_variable(const char *variable) {
1257 struct helpfile_expansion exp;
1259 if (!irccasecmp(variable, "index")) {
1260 exp.type = HF_TABLE;
1261 exp.value.table.length = 1;
1262 exp.value.table.width = 1;
1263 exp.value.table.flags = TABLE_REPEAT_ROWS;
1264 exp.value.table.contents = calloc(dict_size(helpserv_func_dict)+1, sizeof(char**));
1265 exp.value.table.contents[0] = calloc(1, sizeof(char*));
1266 exp.value.table.contents[0][0] = "Commands:";
1267 dict_foreach(helpserv_func_dict, append_entry, &exp);
1271 exp.type = HF_STRING;
1272 exp.value.str = NULL;
1276 static void helpserv_helpfile_read(void) {
1277 helpserv_helpfile = open_helpfile(HELPSERV_HELPFILE_NAME, helpserv_expand_variable);
1280 static HELPSERV_USERCMD(usercmd_wait) {
1281 struct helpserv_request *other;
1283 char buf[INTERVALLEN];
1287 helpserv_user_reply("HSMSG_YOU_BEING_HELPED_BY", likely_helper->nick);
1289 helpserv_user_reply("HSMSG_YOU_BEING_HELPED");
1293 for (other = req->hs->unhandled, pos = -1, count = 0;
1295 other = other->next_unhandled, ++count) {
1300 intervalString(buf, now - req->hs->unhandled->opened, req->user->handle_info);
1301 helpserv_user_reply("HSMSG_WAIT_STATUS", pos+1, count, buf);
1304 static HELPSERV_FUNC(cmd_help) {
1310 topic = unsplit_string(argv+1, argc-1, NULL);
1311 helpserv_help(hs, from_opserv, user, topic);
1316 static HELPSERV_FUNC(cmd_readhelp) {
1317 struct timeval start, stop;
1318 struct helpfile *old_helpfile = helpserv_helpfile;
1320 gettimeofday(&start, NULL);
1321 helpserv_helpfile_read();
1322 if (helpserv_helpfile) {
1323 close_helpfile(old_helpfile);
1325 helpserv_helpfile = old_helpfile;
1327 gettimeofday(&stop, NULL);
1328 stop.tv_sec -= start.tv_sec;
1329 stop.tv_usec -= start.tv_usec;
1330 if (stop.tv_usec < 0) {
1332 stop.tv_usec += 1000000;
1334 helpserv_notice(user, "HSMSG_READHELP_SUCCESS", (unsigned long)stop.tv_sec, (unsigned long)stop.tv_usec/1000);
1339 static struct helpserv_user * helpserv_add_user(struct helpserv_bot *hs, struct handle_info *handle, enum helpserv_level level) {
1340 struct helpserv_user *hs_user;
1341 struct helpserv_userlist *userlist;
1343 hs_user = calloc(1, sizeof(struct helpserv_user));
1344 hs_user->handle = handle;
1346 hs_user->help_mode = 0;
1347 hs_user->level = level;
1348 hs_user->join_time = find_handle_in_channel(hs->helpchan, handle, NULL) ? now : 0;
1349 dict_insert(hs->users, handle->handle, hs_user);
1351 if (!(userlist = dict_find(helpserv_users_byhand_dict, handle->handle, NULL))) {
1352 userlist = helpserv_userlist_alloc();
1353 dict_insert(helpserv_users_byhand_dict, handle->handle, userlist);
1355 helpserv_userlist_append(userlist, hs_user);
1360 static void helpserv_del_user(struct helpserv_bot *hs, struct helpserv_user *hs_user) {
1361 dict_remove(hs->users, hs_user->handle->handle);
1364 static int cmd_add_user(struct helpserv_bot *hs, int from_opserv, struct userNode *user, enum helpserv_level level, int argc, char *argv[]) {
1365 struct helpserv_user *actor, *new_user;
1366 struct handle_info *handle;
1371 actor = GetHSUser(hs, user->handle_info);
1372 if (actor->level < HlManager) {
1373 helpserv_notice(user, "HSMSG_CANNOT_ADD");
1380 if (!(handle = helpserv_get_handle_info(user, argv[1])))
1383 if (GetHSUser(hs, handle)) {
1384 helpserv_notice(user, "HSMSG_USER_EXISTS", handle->handle);
1388 if (!(from_opserv) && actor && (actor->level <= level)) {
1389 helpserv_notice(user, "HSMSG_NO_BUMP_ACCESS");
1393 new_user = helpserv_add_user(hs, handle, level);
1395 helpserv_notice(user, "HSMSG_ADDED_USER", helpserv_level2str(level), handle->handle);
1399 static HELPSERV_FUNC(cmd_deluser) {
1400 struct helpserv_user *actor=NULL, *victim;
1401 struct handle_info *handle;
1402 enum helpserv_level level;
1407 actor = GetHSUser(hs, user->handle_info);
1408 if (actor->level < HlManager) {
1409 helpserv_notice(user, "HSMSG_CANNOT_DEL");
1414 if (!(handle = helpserv_get_handle_info(user, argv[1])))
1417 if (!(victim = GetHSUser(hs, handle))) {
1418 helpserv_notice(user, "HSMSG_NOT_IN_USERLIST", handle->handle, hs->helpserv->nick);
1422 if (!from_opserv && actor && (actor->level <= victim->level)) {
1423 helpserv_notice(user, "MSG_USER_OUTRANKED", victim->handle->handle);
1427 level = victim->level;
1428 helpserv_del_user(hs, victim);
1429 helpserv_notice(user, "HSMSG_DELETED_USER", helpserv_level2str(level), handle->handle);
1434 helpserv_user_comp(const void *arg_a, const void *arg_b)
1436 const struct helpserv_user *a = *(struct helpserv_user**)arg_a;
1437 const struct helpserv_user *b = *(struct helpserv_user**)arg_b;
1439 if (a->level != b->level)
1440 res = b->level - a->level;
1442 res = irccasecmp(a->handle->handle, b->handle->handle);
1446 static int show_helper_range(struct userNode *user, struct helpserv_bot *hs, int from_opserv, enum helpserv_level min_lvl, enum helpserv_level max_lvl) {
1447 struct helpserv_userlist users;
1448 struct helpfile_table tbl;
1449 struct helpserv_user *hs_user;
1451 enum helpserv_level last_level;
1455 users.size = dict_size(hs->users);
1456 users.list = alloca(users.size*sizeof(hs->users[0]));
1457 helpserv_notice(user, "HSMSG_USERLIST_HEADER", hs->helpserv->nick);
1458 for (it = dict_first(hs->users); it; it = iter_next(it)) {
1459 hs_user = iter_data(it);
1460 if (hs_user->level < min_lvl)
1462 if (hs_user->level > max_lvl)
1464 users.list[users.used++] = hs_user;
1467 helpserv_notice(user, "MSG_NONE");
1470 qsort(users.list, users.used, sizeof(users.list[0]), helpserv_user_comp);
1471 switch (user->handle_info->userlist_style) {
1473 tbl.length = users.used + 1;
1475 tbl.flags = TABLE_NO_FREE;
1476 tbl.contents = alloca(tbl.length * sizeof(tbl.contents[0]));
1477 tbl.contents[0] = alloca(tbl.width * sizeof(tbl.contents[0][0]));
1478 tbl.contents[0][0] = "Level";
1479 tbl.contents[0][1] = "Handle";
1480 tbl.contents[0][2] = "WeekStart";
1481 for (ii = 0; ii < users.used; ) {
1482 hs_user = users.list[ii++];
1483 tbl.contents[ii] = alloca(tbl.width * sizeof(tbl.contents[0][0]));
1484 tbl.contents[ii][0] = helpserv_level_names[hs_user->level];
1485 tbl.contents[ii][1] = hs_user->handle->handle;
1486 tbl.contents[ii][2] = weekday_names[hs_user->week_start];
1488 table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
1490 case HI_STYLE_ZOOT: default:
1491 last_level = HlNone;
1494 tbl.flags = TABLE_NO_FREE | TABLE_REPEAT_ROWS | TABLE_NO_HEADERS;
1495 tbl.contents = alloca(users.used * sizeof(tbl.contents[0]));
1496 for (ii = 0; ii < users.used; ) {
1497 hs_user = users.list[ii++];
1498 if (hs_user->level != last_level) {
1500 helpserv_notice(user, "HSMSG_USERLIST_ZOOT_LVL", hs->helpserv->nick, helpserv_level_names[last_level]);
1501 table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
1504 last_level = hs_user->level;
1506 tbl.contents[tbl.length] = alloca(tbl.width * sizeof(tbl.contents[0][0]));
1507 tbl.contents[tbl.length++][0] = hs_user->handle->handle;
1510 helpserv_notice(user, "HSMSG_USERLIST_ZOOT_LVL", hs->helpserv->nick, helpserv_level_names[last_level]);
1511 table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
1517 static HELPSERV_FUNC(cmd_helpers) {
1518 return show_helper_range(user, hs, from_opserv, HlTrial, HlOwner);
1521 static HELPSERV_FUNC(cmd_wlist) {
1522 return show_helper_range(user, hs, from_opserv, HlOwner, HlOwner);
1525 static HELPSERV_FUNC(cmd_mlist) {
1526 return show_helper_range(user, hs, from_opserv, HlManager, HlManager);
1529 static HELPSERV_FUNC(cmd_hlist) {
1530 return show_helper_range(user, hs, from_opserv, HlHelper, HlHelper);
1533 static HELPSERV_FUNC(cmd_tlist) {
1534 return show_helper_range(user, hs, from_opserv, HlTrial, HlTrial);
1537 static HELPSERV_FUNC(cmd_addowner) {
1538 return cmd_add_user(hs, from_opserv, user, HlOwner, argc, argv);
1541 static HELPSERV_FUNC(cmd_addmanager) {
1542 return cmd_add_user(hs, from_opserv, user, HlManager, argc, argv);
1545 static HELPSERV_FUNC(cmd_addhelper) {
1546 return cmd_add_user(hs, from_opserv, user, HlHelper, argc, argv);
1549 static HELPSERV_FUNC(cmd_addtrial) {
1550 return cmd_add_user(hs, from_opserv, user, HlTrial, argc, argv);
1553 static HELPSERV_FUNC(cmd_clvl) {
1554 struct helpserv_user *actor=NULL, *victim;
1555 struct handle_info *handle;
1556 enum helpserv_level level;
1561 actor = GetHSUser(hs, user->handle_info);
1562 if (actor->level < HlManager) {
1563 helpserv_notice(user, "HSMSG_CANNOT_CLVL");
1568 if (!(handle = helpserv_get_handle_info(user, argv[1])))
1571 if (!(victim = GetHSUser(hs, handle))) {
1572 helpserv_notice(user, "HSMSG_NOT_IN_USERLIST", handle->handle, hs->helpserv->nick);
1576 if (((level = helpserv_str2level(argv[2])) == HlNone) || level == HlOper) {
1577 helpserv_notice(user, "HSMSG_INVALID_ACCESS", argv[2]);
1581 if (!(from_opserv) && actor) {
1582 if (actor == victim) {
1583 helpserv_notice(user, "HSMSG_NO_SELF_CLVL");
1587 if (actor->level <= victim->level) {
1588 helpserv_notice(user, "MSG_USER_OUTRANKED", victim->handle->handle);
1592 if (level >= actor->level) {
1593 helpserv_notice(user, "HSMSG_NO_BUMP_ACCESS");
1598 victim->level = level;
1599 helpserv_notice(user, "HSMSG_CHANGED_ACCESS", handle->handle, helpserv_level2str(level));
1604 static void free_request(void *data) {
1605 struct helpserv_request *req = data;
1608 if (shutting_down && (req->hs->persist_lengths[PERSIST_T_REQUEST] != PERSIST_CLOSE || !req->handle)) {
1609 helpserv_log_request(req, "srvx shutdown");
1612 /* Clean up from the unhandled queue */
1613 if (req->hs->unhandled) {
1614 if (req->hs->unhandled == req) {
1615 req->hs->unhandled = req->next_unhandled;
1617 struct helpserv_request *uh;
1618 for (uh=req->hs->unhandled; uh->next_unhandled && (uh->next_unhandled != req); uh = uh->next_unhandled);
1619 if (uh->next_unhandled) {
1620 uh->next_unhandled = req->next_unhandled;
1625 /* Clean up the lists */
1626 if (req->parent_nick_list) {
1627 if (req->parent_nick_list->used == 1) {
1628 dict_remove(helpserv_reqs_bynick_dict, req->user->nick);
1630 helpserv_reqlist_remove(req->parent_nick_list, req);
1633 if (req->parent_hand_list) {
1634 if (req->parent_hand_list->used == 1) {
1635 dict_remove(helpserv_reqs_byhand_dict, req->handle->handle);
1637 helpserv_reqlist_remove(req->parent_hand_list, req);
1641 free_string_list(req->text);
1645 static HELPSERV_FUNC(cmd_close) {
1646 struct helpserv_request *req, *newest=NULL;
1647 struct helpserv_reqlist *nick_list, *hand_list;
1648 struct helpserv_user *hs_user=GetHSUser(hs, user->handle_info);
1649 struct userNode *req_user=NULL;
1650 char close_reason[MAXLEN], reqnum[12];
1651 unsigned long old_req;
1659 if (!(req = smart_get_request(hs, hs_user, argv[1], &num_requests))) {
1660 helpserv_notice(user, "HSMSG_REQ_INVALID", argv[1]);
1664 sprintf(reqnum, "%lu", req->id);
1666 if (num_requests > 1)
1667 helpserv_notice(user, "HSMSG_REQ_FOUNDMANY");
1669 if (hs_user->level < HlManager && req->helper != hs_user) {
1671 helpserv_notice(user, "HSMSG_REQ_NOT_YOURS_ASSIGNED_TO", req->id, req->helper->handle->handle);
1673 helpserv_notice(user, "HSMSG_REQ_NOT_YOURS_UNASSIGNED", req->id);
1677 helpserv_notice(user, "HSMSG_REQ_CLOSED", req->id);
1679 req_user = req->user;
1680 helpserv_message(hs, req->user, MSGTYPE_REQ_CLOSED);
1682 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_CLOSE_REQUEST_1", req->id, req->user->nick, req->handle->handle, user->nick);
1684 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_CLOSE_REQUEST_2", req->id, req->user->nick, user->nick);
1687 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_CLOSE_REQUEST_3", req->id, req->handle->handle, user->nick);
1689 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_CLOSE_REQUEST_4", req->id, user->nick);
1692 hs_user->closed[0]++;
1693 hs_user->closed[4]++;
1695 /* Set these to keep track of the lists after the request is gone, but
1696 * not if free_request() will helpserv_reqlist_free() them. */
1697 nick_list = req->parent_nick_list;
1698 if (nick_list && (nick_list->used == 1))
1700 hand_list = req->parent_hand_list;
1701 if (hand_list && (hand_list->used == 1))
1706 snprintf(close_reason, MAXLEN, "Closed by %s: %s", user->handle_info->handle, unsplit_string(argv+2, argc-2, NULL));
1708 sprintf(close_reason, "Closed by %s", user->handle_info->handle);
1710 helpserv_log_request(req, close_reason);
1711 dict_remove(hs->requests, reqnum);
1713 /* Look for other requests associated with them */
1715 for (i=0; i < nick_list->used; i++) {
1716 req = nick_list->list[i];
1720 if (!newest || (newest->opened < req->opened))
1725 helpserv_msguser(newest->user, "HSMSG_REQ_FOUND_ANOTHER", old_req, newest->id);
1728 if (req_user && hs->auto_devoice) {
1729 struct modeNode *mn = GetUserMode(hs->helpchan, req_user);
1730 if ((!newest || !newest->helper) && mn && (mn->modes & MODE_VOICE)) {
1731 struct mod_chanmode change;
1732 mod_chanmode_init(&change);
1734 change.args[0].mode = MODE_REMOVE | MODE_VOICE;
1735 change.args[0].u.member = mn;
1736 mod_chanmode_announce(hs->helpserv, hs->helpchan, &change);
1743 static HELPSERV_FUNC(cmd_list) {
1746 struct helpfile_table tbl;
1747 unsigned int line, total;
1748 struct helpserv_request *req;
1750 if ((argc < 2) || !irccasecmp(argv[1], "unassigned")) {
1751 for (req = hs->unhandled, total=0; req; req = req->next_unhandled, total++) ;
1752 helpserv_notice(user, "HSMSG_REQ_LIST_TOP_UNASSIGNED", total);
1753 searchtype = 1; /* Unassigned */
1754 } else if (!irccasecmp(argv[1], "assigned")) {
1755 for (req = hs->unhandled, total=dict_size(hs->requests); req; req = req->next_unhandled, total--) ;
1756 helpserv_notice(user, "HSMSG_REQ_LIST_TOP_ASSIGNED", total);
1757 searchtype = 2; /* Assigned */
1758 } else if (!irccasecmp(argv[1], "me")) {
1759 for (total = 0, it = dict_first(hs->requests); it; it = iter_next(it)) {
1760 req = iter_data(it);
1761 if (req->helper && (req->helper->handle == user->handle_info))
1764 helpserv_notice(user, "HSMSG_REQ_LIST_TOP_YOUR", total);
1766 } else if (!irccasecmp(argv[1], "all")) {
1767 total = dict_size(hs->requests);
1768 helpserv_notice(user, "HSMSG_REQ_LIST_TOP_ALL", total);
1769 searchtype = 3; /* All */
1771 helpserv_notice(user, "HSMSG_BAD_REQ_TYPE", argv[1]);
1776 helpserv_notice(user, "HSMSG_REQ_LIST_NONE");
1780 tbl.length = total+1;
1782 tbl.flags = TABLE_NO_FREE;
1783 tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
1784 tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
1785 tbl.contents[0][0] = "ID#";
1786 tbl.contents[0][1] = "User";
1787 tbl.contents[0][2] = "Helper";
1788 tbl.contents[0][3] = "Time open";
1789 tbl.contents[0][4] = "User status";
1791 for (it=dict_first(hs->requests), line=0; it; it=iter_next(it)) {
1792 char opentime[INTERVALLEN], reqid[12], username[NICKLEN+2];
1794 req = iter_data(it);
1796 switch (searchtype) {
1809 if (!req->helper || (req->helper->handle != user->handle_info))
1816 tbl.contents[line] = alloca(tbl.width * sizeof(**tbl.contents));
1817 sprintf(reqid, "%lu", req->id);
1818 tbl.contents[line][0] = strdup(reqid);
1820 strcpy(username, req->user->nick);
1823 strcpy(username+1, req->handle->handle);
1825 tbl.contents[line][1] = strdup(username);
1826 tbl.contents[line][2] = req->helper ? req->helper->handle->handle : "(Unassigned)";
1827 intervalString(opentime, now - req->opened, user->handle_info);
1828 tbl.contents[line][3] = strdup(opentime);
1829 tbl.contents[line][4] = ((req->user || req->handle->users) ? "Online" : "Offline");
1832 table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
1834 for (; line > 0; line--) {
1835 free((char *)tbl.contents[line][0]);
1836 free((char *)tbl.contents[line][1]);
1837 free((char *)tbl.contents[line][3]);
1843 static void helpserv_show(int from_opserv, struct helpserv_bot *hs, struct userNode *user, struct helpserv_request *req) {
1845 char buf[MAX_LINE_SIZE];
1846 char buf2[INTERVALLEN];
1851 helpserv_notice(user, "HSMSG_REQ_INFO_2a", req->user->nick, req->handle->handle);
1853 helpserv_notice(user, "HSMSG_REQ_INFO_2b", req->user->nick);
1854 else if (req->handle)
1855 if (req->handle->users)
1856 helpserv_notice(user, "HSMSG_REQ_INFO_2c", req->handle->handle);
1858 helpserv_notice(user, "HSMSG_REQ_INFO_2d", req->handle->handle);
1860 helpserv_notice(user, "HSMSG_REQ_INFO_2e");
1862 strftime(buf, MAX_LINE_SIZE, HSFMT_TIME, localtime(&feh));
1863 intervalString(buf2, now - req->opened, user->handle_info);
1864 helpserv_notice(user, "HSMSG_REQ_INFO_3", buf, buf2);
1865 helpserv_notice(user, "HSMSG_REQ_INFO_4");
1866 for (nn=0; nn < req->text->used; nn++)
1867 helpserv_notice(user, "HSMSG_REQ_INFO_MESSAGE", req->text->list[nn]);
1870 /* actor is the one who executed the command... it should == user except from
1872 static int helpserv_assign(int from_opserv, struct helpserv_bot *hs, struct userNode *user, struct userNode *actor, struct helpserv_request *req) {
1873 struct helpserv_request *req2;
1874 struct helpserv_user *old_helper;
1876 if (!user->handle_info)
1878 if ((hs->persist_lengths[PERSIST_T_HELPER] == PERSIST_PART) && !GetUserMode(hs->helpchan, user)) {
1879 struct helpserv_user *hsuser_actor = GetHSUser(hs, actor->handle_info);
1880 if (hsuser_actor->level < HlManager) {
1881 helpserv_notice(user, "HSMSG_REQ_YOU_NOT_IN_HELPCHAN", hs->helpchan->name);
1883 } else if (user != actor) {
1884 helpserv_notice(user, "HSMSG_REQ_YOU_NOT_IN_OVERRIDE", hs->helpchan->name);
1885 helpserv_notice(actor, "HSMSG_REQ_HIM_NOT_IN_OVERRIDE", user->nick, hs->helpchan->name);
1887 helpserv_notice(user, "HSMSG_REQ_SELF_NOT_IN_OVERRIDE", hs->helpchan->name);
1890 hs->last_active = now;
1891 if ((old_helper = req->helper)) {
1892 /* don't need to remove from the unhandled queue */
1893 } else if (hs->unhandled == req) {
1894 hs->unhandled = req->next_unhandled;
1895 } else for (req2 = hs->unhandled; req2; req2 = req2->next_unhandled) {
1896 if (req2->next_unhandled == req) {
1897 req2->next_unhandled = req->next_unhandled;
1901 req->next_unhandled = NULL;
1902 req->helper = GetHSUser(hs, user->handle_info);
1903 assert(req->helper);
1904 req->assigned = now;
1907 helpserv_notice(user, "HSMSG_REQ_REASSIGNED", req->id, old_helper->handle->handle);
1908 req->helper->reassigned_to[0]++;
1909 req->helper->reassigned_to[4]++;
1910 old_helper->reassigned_from[0]++;
1911 old_helper->reassigned_from[4]++;
1913 helpserv_notice(user, "HSMSG_REQ_ASSIGNED_YOU", req->id);
1914 req->helper->picked_up[0]++;
1915 req->helper->picked_up[4]++;
1917 helpserv_show(from_opserv, hs, user, req);
1919 helpserv_message(hs, req->user, MSGTYPE_REQ_ASSIGNED);
1921 helpserv_msguser(req->user, "HSMSG_REQ_ASSIGNED_AGAIN", req->id, user->handle_info->handle, user->nick);
1923 helpserv_msguser(req->user, "HSMSG_REQ_ASSIGNED", req->id, user->handle_info->handle, user->nick);
1926 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_ASSIGN_REQUEST_1", req->id, req->user->nick, req->handle->handle, user->nick);
1928 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_ASSIGN_REQUEST_2", req->id, req->user->nick, user->nick);
1931 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_ASSIGN_REQUEST_3", req->id, req->handle->handle, user->nick);
1933 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_ASSIGN_REQUEST_4", req->id, user->nick);
1936 if (req->user && hs->auto_voice) {
1937 struct mod_chanmode change;
1938 mod_chanmode_init(&change);
1940 change.args[0].mode = MODE_VOICE;
1941 if ((change.args[0].u.member = GetUserMode(hs->helpchan, req->user)))
1942 mod_chanmode_announce(hs->helpserv, hs->helpchan, &change);
1948 static HELPSERV_FUNC(cmd_next) {
1949 struct helpserv_request *req;
1951 if (!(req = hs->unhandled)) {
1952 helpserv_notice(user, "HSMSG_REQ_NO_UNASSIGNED");
1955 return helpserv_assign(from_opserv, hs, user, user, req);
1958 static HELPSERV_FUNC(cmd_show) {
1959 struct helpserv_request *req;
1960 struct helpserv_user *hs_user=GetHSUser(hs, user->handle_info);
1965 if (!(req = smart_get_request(hs, hs_user, argv[1], &num_requests))) {
1966 helpserv_notice(user, "HSMSG_REQ_INVALID", argv[1]);
1970 if (num_requests > 1)
1971 helpserv_notice(user, "HSMSG_REQ_FOUNDMANY");
1973 helpserv_notice(user, "HSMSG_REQ_INFO_1", req->id);
1974 helpserv_show(from_opserv, hs, user, req);
1978 static HELPSERV_FUNC(cmd_pickup) {
1979 struct helpserv_request *req;
1980 struct helpserv_user *hs_user=GetHSUser(hs, user->handle_info);
1987 if (!(req = smart_get_request(hs, hs_user, argv[1], &num_requests))) {
1988 helpserv_notice(user, "HSMSG_REQ_INVALID", argv[1]);
1992 if (num_requests > 1)
1993 helpserv_notice(user, "HSMSG_REQ_FOUNDMANY");
1995 return helpserv_assign(from_opserv, hs, user, user, req);
1998 static HELPSERV_FUNC(cmd_reassign) {
1999 struct helpserv_request *req;
2000 struct userNode *targetuser;
2001 struct helpserv_user *target;
2002 struct helpserv_user *hs_user=GetHSUser(hs, user->handle_info);
2009 if (!(req = smart_get_request(hs, hs_user, argv[1], &num_requests))) {
2010 helpserv_notice(user, "HSMSG_REQ_INVALID", argv[1]);
2014 if (num_requests > 1)
2015 helpserv_notice(user, "HSMSG_REQ_FOUNDMANY");
2017 if (!(targetuser = GetUserH(argv[2]))) {
2018 helpserv_notice(user, "MSG_NICK_UNKNOWN", argv[2]);
2022 if (!targetuser->handle_info) {
2023 helpserv_notice(user, "MSG_USER_AUTHENTICATE", targetuser->nick);
2027 if (!(target = GetHSUser(hs, targetuser->handle_info))) {
2028 helpserv_notice(user, "HSMSG_NOT_IN_USERLIST", targetuser->nick, hs->helpserv->nick);
2032 if ((hs->persist_lengths[PERSIST_T_HELPER] == PERSIST_PART) && !GetUserMode(hs->helpchan, user) && (hs_user->level < HlManager)) {
2033 helpserv_notice(user, "HSMSG_REQ_HIM_NOT_IN_HELPCHAN", targetuser->nick, hs->helpchan->name);
2037 helpserv_assign(from_opserv, hs, targetuser, user, req);
2041 static HELPSERV_FUNC(cmd_addnote) {
2042 char text[MAX_LINE_SIZE], timestr[MAX_LINE_SIZE], *note;
2043 struct helpserv_request *req;
2044 struct helpserv_user *hs_user=GetHSUser(hs, user->handle_info);
2050 if (!(req = smart_get_request(hs, hs_user, argv[1], &num_requests))) {
2051 helpserv_notice(user, "HSMSG_REQ_INVALID", argv[1]);
2055 if (num_requests > 1)
2056 helpserv_notice(user, "HSMSG_REQ_FOUNDMANY");
2058 note = unsplit_string(argv+2, argc-2, NULL);
2061 strftime(timestr, MAX_LINE_SIZE, HSFMT_TIME, localtime(&feh));
2062 snprintf(text, MAX_LINE_SIZE, "[Helper note at %s]:", timestr);
2063 string_list_append(req->text, strdup(text));
2064 snprintf(text, MAX_LINE_SIZE, " <%s> %s", user->handle_info->handle, note);
2065 string_list_append(req->text, strdup(text));
2067 helpserv_notice(user, "HSMSG_REQMSG_NOTE_ADDED", req->id);
2072 static HELPSERV_FUNC(cmd_page) {
2075 helpserv_page(PGSRC_COMMAND, "HSMSG_PAGE_REQUEST", user->nick, unsplit_string(argv+1, argc-1, NULL));
2080 static HELPSERV_FUNC(cmd_stats) {
2081 struct helpserv_user *target, *hs_user;
2082 struct handle_info *target_handle;
2083 struct helpfile_table tbl;
2085 char intervalstr[INTERVALLEN], buf[16];
2087 hs_user = from_opserv ? NULL : GetHSUser(hs, user->handle_info);
2090 if (!from_opserv && (hs_user->level < HlManager)) {
2091 helpserv_notice(user, "HSMSG_NEED_MANAGER");
2095 if (!(target_handle = helpserv_get_handle_info(user, argv[1]))) {
2099 if (!(target = GetHSUser(hs, target_handle))) {
2100 helpserv_notice(user, "HSMSG_NOT_IN_USERLIST", target_handle->handle, hs->helpserv->nick);
2105 helpserv_notice(user, "HSMSG_OPSERV_NEED_USER");
2111 helpserv_notice(user, "HSMSG_STATS_TOP", hs->helpserv->nick, target->handle->handle, weekday_names[target->week_start]);
2115 tbl.flags = TABLE_NO_FREE;
2116 tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
2117 tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
2118 tbl.contents[0][0] = "";
2119 tbl.contents[0][1] = "Recorded time";
2120 for (i=0; i < 5; i++) {
2121 unsigned int week_time = target->time_per_week[i];
2122 tbl.contents[i+1] = alloca(tbl.width * sizeof(**tbl.contents));
2123 if ((i == 0 || i == 4) && target->join_time)
2124 week_time += now - target->join_time;
2125 helpserv_interval(intervalstr, week_time);
2126 tbl.contents[i+1][1] = strdup(intervalstr);
2128 tbl.contents[1][0] = "This week";
2129 tbl.contents[2][0] = "Last week";
2130 tbl.contents[3][0] = "2 weeks ago";
2131 tbl.contents[4][0] = "3 weeks ago";
2132 tbl.contents[5][0] = "Total";
2134 helpserv_notice(user, "HSMSG_STATS_TIME", hs->helpchan->name);
2135 table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
2137 for (i=1; i <= 5; i++)
2138 free((char *)tbl.contents[i][1]);
2142 tbl.flags = TABLE_NO_FREE;
2143 tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
2144 tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
2145 tbl.contents[1] = alloca(tbl.width * sizeof(**tbl.contents));
2146 tbl.contents[2] = alloca(tbl.width * sizeof(**tbl.contents));
2147 tbl.contents[3] = alloca(tbl.width * sizeof(**tbl.contents));
2148 tbl.contents[4] = alloca(tbl.width * sizeof(**tbl.contents));
2149 tbl.contents[0][0] = "Category";
2150 tbl.contents[0][1] = "This week";
2151 tbl.contents[0][2] = "Last week";
2152 tbl.contents[0][3] = "Total";
2154 tbl.contents[1][0] = "Requests picked up";
2155 for (i=0; i < 3; i++) {
2156 sprintf(buf, "%u", target->picked_up[(i == 2 ? 4 : i)]);
2157 tbl.contents[1][i+1] = strdup(buf);
2159 tbl.contents[2][0] = "Requests closed";
2160 for (i=0; i < 3; i++) {
2161 sprintf(buf, "%u", target->closed[(i == 2 ? 4 : i)]);
2162 tbl.contents[2][i+1] = strdup(buf);
2164 tbl.contents[3][0] = "Reassigned from";
2165 for (i=0; i < 3; i++) {
2166 sprintf(buf, "%u", target->reassigned_from[(i == 2 ? 4 : i)]);
2167 tbl.contents[3][i+1] = strdup(buf);
2169 tbl.contents[4][0] = "Reassigned to";
2170 for (i=0; i < 3; i++) {
2171 sprintf(buf, "%u", target->reassigned_to[(i == 2 ? 4 : i)]);
2172 tbl.contents[4][i+1] = strdup(buf);
2175 helpserv_notice(user, "HSMSG_STATS_REQS");
2176 table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
2178 for (i=1; i < 5; i++) {
2179 free((char *)tbl.contents[i][1]);
2180 free((char *)tbl.contents[i][2]);
2181 free((char *)tbl.contents[i][3]);
2187 static HELPSERV_FUNC(cmd_statsreport) {
2189 struct helpfile_table tbl;
2191 unsigned int line, i;
2192 struct userNode *srcbot = from_opserv ? opserv : hs->helpserv;
2194 if ((argc > 1) && !irccasecmp(argv[1], "NOTICE"))
2197 tbl.length = dict_size(hs->users)+1;
2199 tbl.flags = TABLE_NO_FREE;
2200 tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
2201 tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
2202 tbl.contents[0][0] = "Account";
2203 tbl.contents[0][1] = "Requests";
2204 tbl.contents[0][2] = "Time helping";
2206 for (it=dict_first(hs->users), line=0; it; it=iter_next(it)) {
2207 struct helpserv_user *hs_user=iter_data(it);
2209 tbl.contents[++line] = alloca(tbl.width * sizeof(**tbl.contents));
2210 tbl.contents[line][0] = hs_user->handle->handle;
2211 tbl.contents[line][1] = malloc(12);
2212 tbl.contents[line][2] = malloc(32); /* A bit more than needed */
2215 /* 4 to 1 instead of 3 to 0 because it's unsigned */
2216 for (i=4; i > 0; i--) {
2217 for (it=dict_first(hs->users), line=0; it; it=iter_next(it)) {
2218 struct helpserv_user *hs_user = iter_data(it);
2220 unsigned int week_time = hs_user->time_per_week[i-1];
2221 if ((i==1) && hs_user->join_time)
2222 week_time += now - hs_user->join_time;
2223 helpserv_interval((char *)tbl.contents[++line][2], week_time);
2226 sprintf((char *)tbl.contents[line][1], "%u", hs_user->picked_up[i-1]+hs_user->reassigned_to[i-1]);
2228 send_target_message(use_privmsg, user->nick, srcbot, statsreport_week[i-1]);
2229 table_send(srcbot, user->nick, 0, (use_privmsg ? irc_privmsg : irc_notice), tbl);
2232 for (line=1; line <= dict_size(hs->users); line++) {
2233 free((char *)tbl.contents[line][1]);
2234 free((char *)tbl.contents[line][2]);
2241 helpserv_in_channel(struct helpserv_bot *hs, struct chanNode *channel) {
2242 enum page_source pgsrc;
2243 if (channel == hs->helpchan)
2245 for (pgsrc=0; pgsrc<PGSRC_COUNT; pgsrc++)
2246 if (channel == hs->page_targets[pgsrc])
2251 static HELPSERV_FUNC(cmd_move) {
2253 helpserv_notice(user, "HSMSG_INVALID_BOT");
2259 if (is_valid_nick(argv[1])) {
2260 char *newnick = argv[1], oldnick[NICKLEN], reason[MAXLEN];
2262 strcpy(oldnick, hs->helpserv->nick);
2264 if (GetUserH(newnick)) {
2265 helpserv_notice(user, "HSMSG_NICK_EXISTS", newnick);
2269 dict_remove2(helpserv_bots_dict, hs->helpserv->nick, 1);
2270 NickChange(hs->helpserv, newnick, 0);
2271 dict_insert(helpserv_bots_dict, hs->helpserv->nick, hs);
2273 helpserv_notice(user, "HSMSG_RENAMED", oldnick, newnick);
2275 snprintf(reason, MAXLEN, "HelpServ bot %s (in %s) renamed to %s by %s.", oldnick, hs->helpchan->name, newnick, user->nick);
2276 global_message(MESSAGE_RECIPIENT_OPERS, reason);
2279 } else if (IsChannelName(argv[1])) {
2280 struct chanNode *old_helpchan = hs->helpchan;
2281 char *newchan = argv[1], oldchan[CHANNELLEN], reason[MAXLEN];
2282 struct helpserv_botlist *botlist;
2284 strcpy(oldchan, hs->helpchan->name);
2286 if (!irccasecmp(oldchan, newchan)) {
2287 helpserv_notice(user, "HSMSG_MOVE_SAME_CHANNEL", hs->helpserv->nick);
2291 if (opserv_bad_channel(newchan)) {
2292 helpserv_notice(user, "HSMSG_ILLEGAL_CHANNEL", newchan);
2296 botlist = dict_find(helpserv_bots_bychan_dict, hs->helpchan->name, NULL);
2297 helpserv_botlist_remove(botlist, hs);
2298 if (botlist->used == 0) {
2299 dict_remove(helpserv_bots_bychan_dict, hs->helpchan->name);
2302 hs->helpchan = NULL;
2303 if (!helpserv_in_channel(hs, old_helpchan)) {
2304 snprintf(reason, MAXLEN, "Moved to %s by %s.", newchan, user->nick);
2305 DelChannelUser(hs->helpserv, old_helpchan, reason, 0);
2308 if (!(hs->helpchan = GetChannel(newchan))) {
2309 hs->helpchan = AddChannel(newchan, now, NULL, NULL);
2310 AddChannelUser(hs->helpserv, hs->helpchan)->modes |= MODE_CHANOP;
2311 } else if (!helpserv_in_channel(hs, old_helpchan)) {
2312 struct mod_chanmode change;
2313 mod_chanmode_init(&change);
2315 change.args[0].mode = MODE_CHANOP;
2316 change.args[0].u.member = AddChannelUser(hs->helpserv, hs->helpchan);
2317 mod_chanmode_announce(hs->helpserv, hs->helpchan, &change);
2320 if (!(botlist = dict_find(helpserv_bots_bychan_dict, hs->helpchan->name, NULL))) {
2321 botlist = helpserv_botlist_alloc();
2322 dict_insert(helpserv_bots_bychan_dict, hs->helpchan->name, botlist);
2324 helpserv_botlist_append(botlist, hs);
2326 snprintf(reason, MAXLEN, "HelpServ %s (%s) moved to %s by %s.", hs->helpserv->nick, oldchan, newchan, user->nick);
2327 global_message(MESSAGE_RECIPIENT_OPERS, reason);
2331 helpserv_notice(user, "HSMSG_INVALID_MOVE", argv[1]);
2336 static HELPSERV_FUNC(cmd_bots) {
2338 struct helpfile_table tbl;
2341 helpserv_notice(user, "HSMSG_BOTLIST_HEADER");
2343 tbl.length = dict_size(helpserv_bots_dict)+1;
2345 tbl.flags = TABLE_NO_FREE;
2346 tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
2347 tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
2348 tbl.contents[0][0] = "Bot";
2349 tbl.contents[0][1] = "Channel";
2350 tbl.contents[0][2] = "Owner";
2351 tbl.contents[0][3] = "Inactivity";
2353 for (it=dict_first(helpserv_bots_dict), i=1; it; it=iter_next(it), i++) {
2354 dict_iterator_t it2;
2355 struct helpserv_bot *bot;
2356 struct helpserv_user *owner=NULL;
2358 bot = iter_data(it);
2360 for (it2=dict_first(bot->users); it2; it2=iter_next(it2)) {
2361 if (((struct helpserv_user *)iter_data(it2))->level == HlOwner) {
2362 owner = iter_data(it2);
2367 tbl.contents[i] = alloca(tbl.width * sizeof(**tbl.contents));
2368 tbl.contents[i][0] = iter_key(it);
2369 tbl.contents[i][1] = bot->helpchan->name;
2370 tbl.contents[i][2] = owner ? owner->handle->handle : "None";
2371 tbl.contents[i][3] = alloca(INTERVALLEN);
2372 intervalString((char*)tbl.contents[i][3], now - bot->last_active, user->handle_info);
2375 table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
2380 static void helpserv_page_helper_gone(struct helpserv_bot *hs, struct helpserv_request *req, const char *reason) {
2381 const int from_opserv = 0;
2386 /* Let the user know that their request is now unhandled */
2388 struct modeNode *mn = GetUserMode(hs->helpchan, req->user);
2389 helpserv_msguser(req->user, "HSMSG_REQ_UNASSIGNED", req->id, reason);
2390 if (hs->auto_devoice && mn && (mn->modes & MODE_VOICE)) {
2391 struct mod_chanmode change;
2392 mod_chanmode_init(&change);
2394 change.args[0].mode = MODE_REMOVE | MODE_VOICE;
2395 change.args[0].u.member = mn;
2396 mod_chanmode_announce(hs->helpserv, hs->helpchan, &change);
2399 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_HELPER_GONE_1", req->id, req->user->nick, req->handle->handle, req->helper->handle->handle, reason);
2401 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_HELPER_GONE_2", req->id, req->user->nick, req->helper->handle->handle, reason);
2404 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_HELPER_GONE_3", req->id, req->handle->handle, req->helper->handle->handle, reason);
2406 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_HELPER_GONE_2", req->id, req->helper->handle->handle, reason);
2409 /* Now put it back in the queue */
2410 if (hs->unhandled == NULL) {
2411 /* Nothing there, put it at the front */
2412 hs->unhandled = req;
2413 req->next_unhandled = NULL;
2415 /* Should it be at the front? */
2416 if (hs->unhandled->opened >= req->opened) {
2417 req->next_unhandled = hs->unhandled;
2418 hs->unhandled = req;
2420 struct helpserv_request *unhandled;
2421 /* Find the request that this should be inserted AFTER */
2422 for (unhandled=hs->unhandled; unhandled->next_unhandled && (unhandled->next_unhandled->opened < req->opened); unhandled = unhandled->next_unhandled);
2423 req->next_unhandled = unhandled->next_unhandled;
2424 unhandled->next_unhandled = req;
2431 /* This takes care of WHINE_DELAY and IDLE_DELAY */
2432 static void run_whine_interval(void *data) {
2433 struct helpserv_bot *hs=data;
2434 struct helpfile_table tbl;
2437 /* First, run the WHINE_DELAY */
2438 if (hs->intervals[INTERVAL_WHINE_DELAY]
2439 && (hs->page_types[PGSRC_ALERT] != PAGE_NONE)
2440 && (hs->page_targets[PGSRC_ALERT] != NULL)
2441 && (!hs->intervals[INTERVAL_EMPTY_INTERVAL] || !hs->helpchan_empty)) {
2442 struct helpserv_request *unh;
2443 struct helpserv_reqlist reqlist;
2444 unsigned int queuesize=0;
2446 helpserv_reqlist_init(&reqlist);
2448 for (unh = hs->unhandled; unh; unh = unh->next_unhandled) {
2450 if ((now - unh->opened) >= hs->intervals[INTERVAL_WHINE_DELAY]) {
2451 helpserv_reqlist_append(&reqlist, unh);
2456 char strwhinedelay[INTERVALLEN];
2458 intervalString(strwhinedelay, hs->intervals[INTERVAL_WHINE_DELAY], NULL);
2459 #if ANNOYING_ALERT_PAGES
2460 tbl.length = reqlist.used + 1;
2462 tbl.flags = TABLE_NO_FREE;
2463 tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
2464 tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
2465 tbl.contents[0][0] = "ID#";
2466 tbl.contents[0][1] = "Nick";
2467 tbl.contents[0][2] = "Account";
2468 tbl.contents[0][3] = "Waiting time";
2470 for (i=1; i <= reqlist.used; i++) {
2471 char reqid[12], unh_time[INTERVALLEN];
2472 unh = reqlist.list[i-1];
2474 tbl.contents[i] = alloca(tbl.width * sizeof(**tbl.contents));
2475 sprintf(reqid, "%lu", unh->id);
2476 tbl.contents[i][0] = strdup(reqid);
2477 tbl.contents[i][1] = unh->user ? unh->user->nick : "Not online";
2478 tbl.contents[i][2] = unh->handle ? unh->handle->handle : "Not authed";
2479 intervalString(unh_time, now - unh->opened, NULL);
2480 tbl.contents[i][3] = strdup(unh_time);
2483 helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_WHINE_HEADER", reqlist.used, strwhinedelay, queuesize, dict_size(hs->requests));
2484 table_send(hs->helpserv, hs->page_targets[PGSRC_ALERT]->name, 0, page_type_funcs[hs->page_types[PGSRC_ALERT]], tbl);
2486 for (i=1; i <= reqlist.used; i++) {
2487 free((char *)tbl.contents[i][0]);
2488 free((char *)tbl.contents[i][3]);
2491 helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_WHINE_HEADER", reqlist.used, strwhinedelay, queuesize, dict_size(hs->requests));
2495 helpserv_reqlist_clean(&reqlist);
2498 /* Next run IDLE_DELAY */
2499 if (hs->intervals[INTERVAL_IDLE_DELAY]
2500 && (hs->page_types[PGSRC_STATUS] != PAGE_NONE)
2501 && (hs->page_targets[PGSRC_STATUS] != NULL)) {
2502 struct modeList mode_list;
2504 modeList_init(&mode_list);
2506 for (i=0; i < hs->helpchan->members.used; i++) {
2507 struct modeNode *mn = hs->helpchan->members.list[i];
2508 /* Ignore ops. Perhaps this should be a set option? */
2509 if (mn->modes & MODE_CHANOP)
2511 /* Check if they've been idle long enough */
2512 if ((unsigned)(now - mn->idle_since) < hs->intervals[INTERVAL_IDLE_DELAY])
2514 /* Add them to the list of idle people.. */
2515 modeList_append(&mode_list, mn);
2518 if (mode_list.used) {
2519 char stridledelay[INTERVALLEN];
2521 tbl.length = mode_list.used + 1;
2523 tbl.flags = TABLE_NO_FREE;
2524 tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
2525 tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
2526 tbl.contents[0][0] = "Nick";
2527 tbl.contents[0][1] = "Account";
2528 tbl.contents[0][2] = "ID#";
2529 tbl.contents[0][3] = "Idle time";
2531 for (i=1; i <= mode_list.used; i++) {
2532 char reqid[12], idle_time[INTERVALLEN];
2533 struct helpserv_reqlist *reqlist;
2534 struct modeNode *mn = mode_list.list[i-1];
2536 tbl.contents[i] = alloca(tbl.width * sizeof(**tbl.contents));
2537 tbl.contents[i][0] = mn->user->nick;
2538 tbl.contents[i][1] = mn->user->handle_info ? mn->user->handle_info->handle : "Not authed";
2540 if ((reqlist = dict_find(helpserv_reqs_bynick_dict, mn->user->nick, NULL))) {
2543 for (j = reqlist->used-1; j >= 0; j--) {
2544 struct helpserv_request *req = reqlist->list[j];
2546 if (req->hs == hs) {
2547 sprintf(reqid, "%lu", req->id);
2553 strcpy(reqid, "None");
2555 strcpy(reqid, "None");
2557 tbl.contents[i][2] = strdup(reqid);
2559 intervalString(idle_time, now - mn->idle_since, NULL);
2560 tbl.contents[i][3] = strdup(idle_time);
2563 intervalString(stridledelay, hs->intervals[INTERVAL_IDLE_DELAY], NULL);
2564 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_IDLE_HEADER", mode_list.used, hs->helpchan->name, stridledelay);
2565 table_send(hs->helpserv, hs->page_targets[PGSRC_STATUS]->name, 0, page_types[hs->page_types[PGSRC_STATUS]].func, tbl);
2567 for (i=1; i <= mode_list.used; i++) {
2568 free((char *)tbl.contents[i][2]);
2569 free((char *)tbl.contents[i][3]);
2573 modeList_clean(&mode_list);
2576 if (hs->intervals[INTERVAL_WHINE_INTERVAL]) {
2577 timeq_add(now + hs->intervals[INTERVAL_WHINE_INTERVAL], run_whine_interval, hs);
2581 /* Returns -1 if there's any helpers,
2582 * 0 if there are no helpers
2583 * >1 if there are trials (number of trials)
2585 static int find_helpchan_helpers(struct helpserv_bot *hs) {
2589 for (it=dict_first(hs->users); it; it=iter_next(it)) {
2590 struct helpserv_user *hs_user=iter_data(it);
2592 if (find_handle_in_channel(hs->helpchan, hs_user->handle, NULL)) {
2593 if (hs_user->level >= HlHelper) {
2594 hs->helpchan_empty = 0;
2601 hs->helpchan_empty = 1;
2606 static void run_empty_interval(void *data) {
2607 struct helpserv_bot *hs=data;
2608 int num_trials=find_helpchan_helpers(hs);
2609 unsigned int num_unh;
2610 struct helpserv_request *unh;
2612 if (num_trials == -1)
2614 if (hs->req_on_join && !hs->unhandled)
2617 for (num_unh=0, unh=hs->unhandled; unh; num_unh++)
2618 unh = unh->next_unhandled;
2621 helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_ONLYTRIALALERT", hs->helpchan->name, num_trials, num_unh);
2623 helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_EMPTYALERT", hs->helpchan->name, num_unh);
2625 if (hs->intervals[INTERVAL_EMPTY_INTERVAL])
2626 timeq_add(now + hs->intervals[INTERVAL_EMPTY_INTERVAL], run_empty_interval, hs);
2629 static void free_user(void *data) {
2630 struct helpserv_user *hs_user = data;
2631 struct helpserv_bot *hs = hs_user->hs;
2632 struct helpserv_userlist *userlist;
2636 for (it=dict_first(hs->requests); it; it=iter_next(it)) {
2637 struct helpserv_request *req = iter_data(it);
2639 if (req->helper == hs_user)
2640 helpserv_page_helper_gone(hs, req, "been deleted");
2644 userlist = dict_find(helpserv_users_byhand_dict, hs_user->handle->handle, NULL);
2645 if (userlist->used == 1) {
2646 dict_remove(helpserv_users_byhand_dict, hs_user->handle->handle);
2648 helpserv_userlist_remove(userlist, hs_user);
2654 static struct helpserv_bot *register_helpserv(const char *nick, const char *help_channel, const char *registrar) {
2655 struct helpserv_bot *hs;
2656 struct helpserv_botlist *botlist;
2658 /* Laziness dictates calloc, since there's a lot to set to NULL or 0, and
2659 * it's a harmless default */
2660 hs = calloc(1, sizeof(struct helpserv_bot));
2662 if (!(hs->helpserv = AddLocalUser(nick, nick, NULL, helpserv_conf.description, NULL))) {
2667 reg_privmsg_func(hs->helpserv, helpserv_botmsg);
2669 if (!(hs->helpchan = GetChannel(help_channel))) {
2670 hs->helpchan = AddChannel(help_channel, now, NULL, NULL);
2671 AddChannelUser(hs->helpserv, hs->helpchan)->modes |= MODE_CHANOP;
2673 struct mod_chanmode change;
2674 mod_chanmode_init(&change);
2676 change.args[0].mode = MODE_CHANOP;
2677 change.args[0].u.member = AddChannelUser(hs->helpserv, hs->helpchan);
2678 mod_chanmode_announce(hs->helpserv, hs->helpchan, &change);
2682 hs->registrar = strdup(registrar);
2684 hs->users = dict_new();
2685 /* Don't free keys - they use the handle_info's handle field */
2686 dict_set_free_data(hs->users, free_user);
2687 hs->requests = dict_new();
2688 dict_set_free_keys(hs->requests, free);
2689 dict_set_free_data(hs->requests, free_request);
2691 dict_insert(helpserv_bots_dict, hs->helpserv->nick, hs);
2693 if (!(botlist = dict_find(helpserv_bots_bychan_dict, hs->helpchan->name, NULL))) {
2694 botlist = helpserv_botlist_alloc();
2695 dict_insert(helpserv_bots_bychan_dict, hs->helpchan->name, botlist);
2697 helpserv_botlist_append(botlist, hs);
2702 static HELPSERV_FUNC(cmd_register) {
2703 char *nick, *helpchan, reason[MAXLEN];
2704 struct handle_info *handle;
2708 if (!is_valid_nick(nick)) {
2709 helpserv_notice(user, "HSMSG_ILLEGAL_NICK", nick);
2712 if (GetUserH(nick)) {
2713 helpserv_notice(user, "HSMSG_NICK_EXISTS", nick);
2717 if (!IsChannelName(helpchan)) {
2718 helpserv_notice(user, "HSMSG_ILLEGAL_CHANNEL", helpchan);
2722 if (opserv_bad_channel(helpchan)) {
2723 helpserv_notice(user, "HSMSG_ILLEGAL_CHANNEL", helpchan);
2726 if (!(handle = helpserv_get_handle_info(user, argv[3])))
2729 if (!(hs = register_helpserv(nick, helpchan, user->handle_info->handle))) {
2730 helpserv_notice(user, "HSMSG_ERROR_ADDING_SERVICE", nick);
2734 hs->registered = now;
2735 helpserv_add_user(hs, handle, HlOwner);
2737 helpserv_notice(user, "HSMSG_REG_SUCCESS", handle->handle, nick);
2739 snprintf(reason, MAXLEN, "HelpServ %s (%s) registered to %s by %s.", nick, hs->helpchan->name, handle->handle, user->nick);
2740 /* Not sent to helpers, since they can't register HelpServ */
2741 global_message(MESSAGE_RECIPIENT_OPERS, reason);
2745 static void unregister_helpserv(struct helpserv_bot *hs) {
2746 enum message_type msgtype;
2748 timeq_del(0, NULL, hs, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_FUNC);
2750 /* Requests before users so that it doesn't spam mentioning now-unhandled
2751 * requests because the users were deleted */
2752 dict_delete(hs->requests);
2753 hs->requests = NULL; /* so we don't try to look up requests in free_user() */
2754 dict_delete(hs->users);
2755 free(hs->registrar);
2757 for (msgtype=0; msgtype<MSGTYPE_COUNT; msgtype++)
2758 free(hs->messages[msgtype]);
2761 static void helpserv_free_bot(void *data) {
2762 unregister_helpserv(data);
2766 static void helpserv_unregister(struct helpserv_bot *bot, const char *quit_fmt, const char *global_fmt, const char *actor) {
2767 char reason[MAXLEN], channame[CHANNELLEN], botname[NICKLEN];
2768 struct helpserv_botlist *botlist;
2771 botlist = dict_find(helpserv_bots_bychan_dict, bot->helpchan->name, NULL);
2772 helpserv_botlist_remove(botlist, bot);
2774 dict_remove(helpserv_bots_bychan_dict, bot->helpchan->name);
2775 len = strlen(bot->helpserv->nick) + 1;
2776 safestrncpy(botname, bot->helpserv->nick, len);
2777 len = strlen(bot->helpchan->name) + 1;
2778 safestrncpy(channame, bot->helpchan->name, len);
2779 snprintf(reason, sizeof(reason), quit_fmt, actor);
2780 DelUser(bot->helpserv, NULL, 1, reason);
2781 dict_remove(helpserv_bots_dict, botname);
2782 snprintf(reason, sizeof(reason), global_fmt, botname, channame, actor);
2783 global_message(MESSAGE_RECIPIENT_OPERS, reason);
2786 static HELPSERV_FUNC(cmd_unregister) {
2788 if (argc < 2 || strcmp(argv[1], "CONFIRM")) {
2789 helpserv_notice(user, "HSMSG_NEED_UNREG_CONFIRM");
2792 log_audit(HS_LOG, LOG_COMMAND, user, hs->helpserv, hs->helpchan->name, 0, "unregister CONFIRM");
2795 helpserv_unregister(hs, "Unregistered by %s", "HelpServ %s (%s) unregistered by %s.", user->nick);
2799 static HELPSERV_FUNC(cmd_expire) {
2800 struct helpserv_botlist victims;
2801 struct helpserv_bot *bot;
2802 dict_iterator_t it, next;
2803 unsigned int count = 0;
2805 memset(&victims, 0, sizeof(victims));
2806 for (it = dict_first(helpserv_bots_dict); it; it = next) {
2807 bot = iter_data(it);
2808 next = iter_next(it);
2809 if ((unsigned int)(now - bot->last_active) < helpserv_conf.expire_age)
2811 helpserv_unregister(bot, "Registration expired due to inactivity", "HelpServ %s (%s) expired at request of %s.", user->nick);
2814 helpserv_notice(user, "HSMSG_EXPIRATION_DONE", count);
2818 static HELPSERV_FUNC(cmd_giveownership) {
2819 struct handle_info *hi;
2820 struct helpserv_user *new_owner, *old_owner, *hs_user;
2822 char reason[MAXLEN];
2824 if (!from_opserv && ((argc < 3) || strcmp(argv[2], "CONFIRM"))) {
2825 helpserv_notice(user, "HSMSG_NEED_GIVEOWNERSHIP_CONFIRM");
2828 hi = helpserv_get_handle_info(user, argv[1]);
2831 new_owner = GetHSUser(hs, hi);
2833 helpserv_notice(user, "HSMSG_NOT_IN_USERLIST", hi->handle, hs->helpserv->nick);
2837 old_owner = GetHSUser(hs, user->handle_info);
2838 else for (it = dict_first(hs->users), old_owner = NULL; it; it = iter_next(it)) {
2839 hs_user = iter_data(it);
2840 if (hs_user->level != HlOwner)
2843 helpserv_notice(user, "HSMSG_MULTIPLE_OWNERS", hs->helpserv->nick);
2846 old_owner = hs_user;
2848 if (!from_opserv && (new_owner->handle == user->handle_info)) {
2849 helpserv_notice(user, "HSMSG_NO_TRANSFER_SELF");
2853 old_owner->level = HlManager;
2854 new_owner->level = HlOwner;
2855 helpserv_notice(user, "HSMSG_OWNERSHIP_GIVEN", hs->helpserv->nick, new_owner->handle->handle);
2856 sprintf(reason, "%s (%s) ownership transferred to %s by %s.", hs->helpserv->nick, hs->helpchan->name, new_owner->handle->handle, user->handle_info->handle);
2860 static HELPSERV_FUNC(cmd_weekstart) {
2861 struct handle_info *hi;
2862 struct helpserv_user *actor, *victim;
2866 actor = from_opserv ? NULL : GetHSUser(hs, user->handle_info);
2867 if (!(hi = helpserv_get_handle_info(user, argv[1])))
2869 if (!(victim = GetHSUser(hs, hi))) {
2870 helpserv_notice(user, "HSMSG_NOT_IN_USERLIST", hi->handle, hs->helpserv->nick);
2873 if (actor && (actor->level <= victim->level) && (actor != victim)) {
2874 helpserv_notice(user, "MSG_USER_OUTRANKED", victim->handle->handle);
2877 if (argc > 2 && (!actor || actor->level >= HlManager)) {
2879 switch (argv[2][0]) {
2881 if ((argv[2][1] == 'u') || (argv[2][1] == 'U'))
2883 else if ((argv[2][1] == 'a') || (argv[2][1] == 'A'))
2886 case 'm': case 'M': new_day = 1; break;
2888 if ((argv[2][1] == 'u') || (argv[2][1] == 'U'))
2890 else if ((argv[2][1] == 'h') || (argv[2][1] == 'H'))
2893 case 'w': case 'W': new_day = 3; break;
2894 case 'f': case 'F': new_day = 5; break;
2897 helpserv_notice(user, "HSMSG_BAD_WEEKDAY", argv[2]);
2900 victim->week_start = new_day;
2903 helpserv_notice(user, "HSMSG_WEEK_STARTS", victim->handle->handle, weekday_names[victim->week_start]);
2907 static HELPSERV_FUNC(cmd_modstats) {
2908 struct handle_info *hi;
2909 struct helpserv_user *victim;
2910 const char *field_name;
2912 unsigned int *field = NULL;
2916 if (!oper_has_access(user, (from_opserv ? opserv : hs->helpserv), helpserv_conf.modstats_level, 0))
2918 if (!(hi = helpserv_get_handle_info(user, argv[1])))
2920 if (!(victim = GetHSUser(hs, hi))) {
2921 helpserv_notice(user, "HSMSG_NOT_IN_USERLIST", hi->handle, hs->helpserv->nick);
2925 field_name = argv[2];
2926 if (!strcasecmp(argv[3], "total"))
2928 else if(!strcasecmp(argv[3], "current"))
2931 week = strtoul(argv[3], &errptr, 0);
2932 if (*errptr != '\0') {
2933 helpserv_notice(user, "HSMSG_MODSTATS_BAD_WEEK");
2937 mod = strtol(argv[4], NULL, 0);
2939 if (week < 0 || week > 4) {
2940 helpserv_notice(user, "HSMSG_MODSTATS_BAD_WEEK");
2944 if (!strcasecmp(field_name, "time")) {
2945 if (victim->join_time && (week == 0 || week == 4)) {
2946 victim->time_per_week[0] += now - victim->join_time;
2947 victim->time_per_week[4] += now - victim->join_time;
2948 victim->join_time = now;
2950 field = victim->time_per_week;
2952 else if (!strcasecmp(field_name, "picked") || !strcasecmp(field_name, "picked_up") || !strcasecmp(field_name, "reqs"))
2953 field = victim->picked_up;
2954 else if (!strcasecmp(field_name, "closed"))
2955 field = victim->closed;
2956 else if (!strcasecmp(field_name, "ra_from") || !strcasecmp(field_name, "reassigned_from"))
2957 field = victim->reassigned_from;
2958 else if (!strcasecmp(field_name, "ra_to") || !strcasecmp(field_name, "reassigned_to"))
2959 field = victim->reassigned_to;
2961 helpserv_notice(user, "HSMSG_MODSTATS_BAD_FIELD");
2965 if (mod < 0 && mod < -(int)field[week]) {
2966 helpserv_notice(user, "HSMSG_MODSTATS_NEGATIVE");
2971 helpserv_notice(user, "HSMSG_MODSTATS_SUCCESS", victim->handle->handle);
2976 static void set_page_target(struct helpserv_bot *hs, enum page_source idx, const char *target) {
2977 struct chanNode *new_target, *old_target;
2980 if (!IsChannelName(target)) {
2981 log_module(HS_LOG, LOG_ERROR, "%s has an invalid page target.", hs->helpserv->nick);
2984 new_target = GetChannel(target);
2986 new_target = AddChannel(target, now, NULL, NULL);
2987 AddChannelUser(hs->helpserv, new_target);
2992 if (new_target == hs->page_targets[idx])
2994 old_target = hs->page_targets[idx];
2995 hs->page_targets[idx] = NULL;
2996 if (old_target && !helpserv_in_channel(hs, old_target))
2997 DelChannelUser(hs->helpserv, old_target, "Changing page target.", 0);
2998 if (new_target && !helpserv_in_channel(hs, new_target)) {
2999 struct mod_chanmode change;
3000 mod_chanmode_init(&change);
3002 change.args[0].mode = MODE_CHANOP;
3003 change.args[0].u.member = AddChannelUser(hs->helpserv, new_target);
3004 mod_chanmode_announce(hs->helpserv, new_target, &change);
3006 hs->page_targets[idx] = new_target;
3009 static int opt_page_target(struct userNode *user, struct helpserv_bot *hs, int from_opserv, int argc, char *argv[], enum page_source idx) {
3013 if (!IsOper(user)) {
3014 helpserv_notice(user, "HSMSG_SET_NEED_OPER");
3017 if (!strcmp(argv[0], "*")) {
3018 set_page_target(hs, idx, NULL);
3020 } else if (!IsChannelName(argv[0])) {
3021 helpserv_notice(user, "MSG_NOT_CHANNEL_NAME");
3024 set_page_target(hs, idx, argv[0]);
3028 if (hs->page_targets[idx])
3029 helpserv_notice(user, page_sources[idx].print_target, hs->page_targets[idx]->name);
3031 helpserv_notice(user, page_sources[idx].print_target, user_find_message(user, "MSG_NONE"));
3035 static HELPSERV_OPTION(opt_pagetarget_command) {
3036 return opt_page_target(user, hs, from_opserv, argc, argv, PGSRC_COMMAND);
3039 static HELPSERV_OPTION(opt_pagetarget_alert) {
3040 return opt_page_target(user, hs, from_opserv, argc, argv, PGSRC_ALERT);
3043 static HELPSERV_OPTION(opt_pagetarget_status) {
3044 return opt_page_target(user, hs, from_opserv, argc, argv, PGSRC_STATUS);
3047 static enum page_type page_type_from_name(const char *name) {
3048 enum page_type type;
3049 for (type=0; type<PAGE_COUNT; type++)
3050 if (!irccasecmp(page_types[type].db_name, name))
3055 static int opt_page_type(struct userNode *user, struct helpserv_bot *hs, int from_opserv, int argc, char *argv[], enum page_source idx) {
3056 enum page_type new_type;
3060 new_type = page_type_from_name(argv[0]);
3061 if (new_type == PAGE_COUNT) {
3062 helpserv_notice(user, "HSMSG_INVALID_OPTION", argv[0]);
3065 hs->page_types[idx] = new_type;
3068 helpserv_notice(user, page_sources[idx].print_type,
3069 user_find_message(user, page_types[hs->page_types[idx]].print_name));
3073 static HELPSERV_OPTION(opt_pagetype) {
3074 return opt_page_type(user, hs, from_opserv, argc, argv, PGSRC_COMMAND);
3077 static HELPSERV_OPTION(opt_alert_page_type) {
3078 return opt_page_type(user, hs, from_opserv, argc, argv, PGSRC_ALERT);
3081 static HELPSERV_OPTION(opt_status_page_type) {
3082 return opt_page_type(user, hs, from_opserv, argc, argv, PGSRC_STATUS);
3085 static int opt_message(struct userNode *user, struct helpserv_bot *hs, int from_opserv, int argc, char *argv[], enum message_type idx) {
3089 char *msg = unsplit_string(argv, argc, NULL);
3090 free(hs->messages[idx]);
3091 hs->messages[idx] = strcmp(msg, "*") ? strdup(msg) : NULL;
3094 if (hs->messages[idx])
3095 helpserv_notice(user, message_types[idx].print_name, hs->messages[idx]);
3097 helpserv_notice(user, message_types[idx].print_name, user_find_message(user, "MSG_NONE"));
3101 static HELPSERV_OPTION(opt_greeting) {
3102 return opt_message(user, hs, from_opserv, argc, argv, MSGTYPE_GREETING);
3105 static HELPSERV_OPTION(opt_req_opened) {
3106 return opt_message(user, hs, from_opserv, argc, argv, MSGTYPE_REQ_OPENED);
3109 static HELPSERV_OPTION(opt_req_assigned) {
3110 return opt_message(user, hs, from_opserv, argc, argv, MSGTYPE_REQ_ASSIGNED);
3113 static HELPSERV_OPTION(opt_req_closed) {
3114 return opt_message(user, hs, from_opserv, argc, argv, MSGTYPE_REQ_CLOSED);
3117 static int opt_interval(struct userNode *user, struct helpserv_bot *hs, int from_opserv, int argc, char *argv[], enum interval_type idx, unsigned int min) {
3118 char buf[INTERVALLEN];
3122 unsigned long new_int = ParseInterval(argv[0]);
3123 if (!new_int && strcmp(argv[0], "0")) {
3124 helpserv_notice(user, "MSG_INVALID_DURATION", argv[0]);
3127 if (new_int && new_int < min) {
3128 intervalString(buf, min, user->handle_info);
3129 helpserv_notice(user, "HSMSG_INVALID_INTERVAL", user_find_message(user, interval_types[idx].print_name), buf);
3132 hs->intervals[idx] = new_int;
3135 if (hs->intervals[idx]) {
3136 intervalString(buf, hs->intervals[idx], user->handle_info);
3137 helpserv_notice(user, interval_types[idx].print_name, buf);
3139 helpserv_notice(user, interval_types[idx].print_name, user_find_message(user, "HSMSG_0_DISABLED"));
3143 static HELPSERV_OPTION(opt_idle_delay) {
3144 return opt_interval(user, hs, from_opserv, argc, argv, INTERVAL_IDLE_DELAY, 60);
3147 static HELPSERV_OPTION(opt_whine_delay) {
3148 return opt_interval(user, hs, from_opserv, argc, argv, INTERVAL_WHINE_DELAY, 60);
3151 static HELPSERV_OPTION(opt_whine_interval) {
3152 unsigned int old_val = hs->intervals[INTERVAL_WHINE_INTERVAL];
3155 retval = opt_interval(user, hs, from_opserv, argc, argv, INTERVAL_WHINE_INTERVAL, 60);
3157 if (!old_val && hs->intervals[INTERVAL_WHINE_INTERVAL]) {
3158 timeq_add(now + hs->intervals[INTERVAL_WHINE_INTERVAL], run_whine_interval, hs);
3159 } else if (old_val && !hs->intervals[INTERVAL_WHINE_INTERVAL]) {
3160 timeq_del(0, run_whine_interval, hs, TIMEQ_IGNORE_WHEN);
3166 static HELPSERV_OPTION(opt_empty_interval) {
3167 unsigned int old_val = hs->intervals[INTERVAL_EMPTY_INTERVAL];
3170 retval = opt_interval(user, hs, from_opserv, argc, argv, INTERVAL_EMPTY_INTERVAL, 60);
3172 if (!old_val && hs->intervals[INTERVAL_EMPTY_INTERVAL]) {
3173 timeq_add(now + hs->intervals[INTERVAL_EMPTY_INTERVAL], run_empty_interval, hs);
3174 } else if (old_val && !hs->intervals[INTERVAL_EMPTY_INTERVAL]) {
3175 timeq_del(0, run_empty_interval, hs, TIMEQ_IGNORE_WHEN);
3181 static HELPSERV_OPTION(opt_stale_delay) {
3182 return opt_interval(user, hs, from_opserv, argc, argv, INTERVAL_STALE_DELAY, 60);
3185 static enum persistence_length persistence_from_name(const char *name) {
3186 enum persistence_length pers;
3187 for (pers=0; pers<PERSIST_COUNT; pers++)
3188 if (!irccasecmp(name, persistence_lengths[pers].db_name))
3190 return PERSIST_COUNT;
3193 static int opt_persist(struct userNode *user, struct helpserv_bot *hs, int from_opserv, int argc, char *argv[], enum persistence_type idx) {
3197 enum persistence_length new_pers = persistence_from_name(argv[0]);
3198 if (new_pers == PERSIST_COUNT) {
3199 helpserv_notice(user, "HSMSG_INVALID_OPTION", argv[0]);
3202 hs->persist_lengths[idx] = new_pers;
3205 helpserv_notice(user, persistence_lengths[idx].print_name,
3206 user_find_message(user, persistence_lengths[hs->persist_lengths[idx]].print_name));
3210 static HELPSERV_OPTION(opt_request_persistence) {
3211 return opt_persist(user, hs, from_opserv, argc, argv, PERSIST_T_REQUEST);
3214 static HELPSERV_OPTION(opt_helper_persistence) {
3215 return opt_persist(user, hs, from_opserv, argc, argv, PERSIST_T_HELPER);
3218 static enum notification_type notification_from_name(const char *name) {
3219 enum notification_type notify;
3220 for (notify=0; notify<NOTIFY_COUNT; notify++)
3221 if (!irccasecmp(name, notification_types[notify].db_name))
3223 return NOTIFY_COUNT;
3226 static HELPSERV_OPTION(opt_notification) {
3230 enum notification_type new_notify = notification_from_name(argv[0]);
3231 if (new_notify == NOTIFY_COUNT) {
3232 helpserv_notice(user, "HSMSG_INVALID_OPTION", argv[0]);
3235 if (!from_opserv && (new_notify == NOTIFY_HANDLE)) {
3236 helpserv_notice(user, "HSMSG_SET_NEED_OPER");
3239 hs->notify = new_notify;
3242 helpserv_notice(user, "HSMSG_SET_NOTIFICATION", user_find_message(user, notification_types[hs->notify].print_name));
3246 #define OPTION_UINT(var, name) do { \
3249 (var) = strtoul(argv[0], NULL, 0); \
3252 helpserv_notice(user, name, (var)); \
3256 static HELPSERV_OPTION(opt_id_wrap) {
3257 OPTION_UINT(hs->id_wrap, "HSMSG_SET_IDWRAP");
3260 static HELPSERV_OPTION(opt_req_maxlen) {
3261 OPTION_UINT(hs->req_maxlen, "HSMSG_SET_REQMAXLEN");
3264 #define OPTION_BINARY(var, name) do { \
3267 if (enabled_string(argv[0])) { \
3270 } else if (disabled_string(argv[0])) { \
3274 helpserv_notice(user, "MSG_INVALID_BINARY", argv[0]); \
3278 helpserv_notice(user, name, user_find_message(user, (var) ? "MSG_ON" : "MSG_OFF")); \
3282 static HELPSERV_OPTION(opt_privmsg_only) {
3283 OPTION_BINARY(hs->privmsg_only, "HSMSG_SET_PRIVMSGONLY");
3286 static HELPSERV_OPTION(opt_req_on_join) {
3287 OPTION_BINARY(hs->req_on_join, "HSMSG_SET_REQONJOIN");
3290 static HELPSERV_OPTION(opt_auto_voice) {
3291 OPTION_BINARY(hs->auto_voice, "HSMSG_SET_AUTOVOICE");
3294 static HELPSERV_OPTION(opt_auto_devoice) {
3295 OPTION_BINARY(hs->auto_devoice, "HSMSG_SET_AUTODEVOICE");
3298 static HELPSERV_FUNC(cmd_set) {
3299 helpserv_option_func_t *opt;
3303 helpserv_option_func_t *display[] = {
3304 opt_pagetarget_command, opt_pagetarget_alert, opt_pagetarget_status,
3305 opt_pagetype, opt_alert_page_type, opt_status_page_type,
3306 opt_greeting, opt_req_opened, opt_req_assigned, opt_req_closed,
3307 opt_idle_delay, opt_whine_delay, opt_whine_interval,
3308 opt_empty_interval, opt_stale_delay, opt_request_persistence,
3309 opt_helper_persistence, opt_notification, opt_id_wrap,
3310 opt_req_maxlen, opt_privmsg_only, opt_req_on_join, opt_auto_voice,
3314 helpserv_notice(user, "HSMSG_QUEUE_OPTIONS");
3315 for (i=0; i<ArrayLength(display); i++)
3316 display[i](user, hs, from_opserv, 0, argv);
3320 if (!(opt = dict_find(helpserv_option_dict, argv[1], NULL))) {
3321 helpserv_notice(user, "HSMSG_INVALID_OPTION", argv[1]);
3325 if ((argc > 2) && !from_opserv) {
3326 struct helpserv_user *hs_user;
3328 if (!(hs_user = dict_find(hs->users, user->handle_info->handle, NULL))) {
3329 helpserv_notice(user, "HSMSG_WTF_WHO_ARE_YOU", hs->helpserv->nick);
3333 if (hs_user->level < HlManager) {
3334 helpserv_notice(user, "HSMSG_NEED_MANAGER");
3338 return opt(user, hs, from_opserv, argc-2, argv+2);
3341 static int user_write_helper(const char *key, void *data, void *extra) {
3342 struct helpserv_user *hs_user = data;
3343 struct saxdb_context *ctx = extra;
3344 struct string_list strlist;
3345 char str[5][16], *strs[5];
3348 saxdb_start_record(ctx, key, 0);
3349 /* Helper identification. */
3350 saxdb_write_string(ctx, KEY_HELPER_LEVEL, helpserv_level2str(hs_user->level));
3351 saxdb_write_string(ctx, KEY_HELPER_HELPMODE, (hs_user->help_mode ? "1" : "0"));
3352 saxdb_write_int(ctx, KEY_HELPER_WEEKSTART, hs_user->week_start);
3354 saxdb_start_record(ctx, KEY_HELPER_STATS, 0);
3355 for (i=0; i < ArrayLength(strs); ++i)
3357 strlist.list = strs;
3359 /* Time in help channel */
3360 for (i=0; i < strlist.used; i++) {
3361 unsigned int week_time = hs_user->time_per_week[i];
3362 if ((i==0 || i==4) && hs_user->join_time)
3363 week_time += now - hs_user->join_time;
3364 sprintf(str[i], "%u", week_time);
3366 saxdb_write_string_list(ctx, KEY_HELPER_STATS_TIME, &strlist);
3367 /* Requests picked up */
3368 for (i=0; i < strlist.used; i++)
3369 sprintf(str[i], "%u", hs_user->picked_up[i]);
3370 saxdb_write_string_list(ctx, KEY_HELPER_STATS_PICKUP, &strlist);
3371 /* Requests closed */
3372 for (i=0; i < strlist.used; i++)
3373 sprintf(str[i], "%u", hs_user->closed[i]);
3374 saxdb_write_string_list(ctx, KEY_HELPER_STATS_CLOSE, &strlist);
3375 /* Requests reassigned from user */
3376 for (i=0; i < strlist.used; i++)
3377 sprintf(str[i], "%u", hs_user->reassigned_from[i]);
3378 saxdb_write_string_list(ctx, KEY_HELPER_STATS_REASSIGNFROM, &strlist);
3379 /* Requests reassigned to user */
3380 for (i=0; i < strlist.used; i++)
3381 sprintf(str[i], "%u", hs_user->reassigned_to[i]);
3382 saxdb_write_string_list(ctx, KEY_HELPER_STATS_REASSIGNTO, &strlist);
3383 /* End of stats and whole record. */
3384 saxdb_end_record(ctx);
3385 saxdb_end_record(ctx);
3389 static int user_read_helper(const char *key, void *data, void *extra) {
3390 struct record_data *rd = data;
3391 struct helpserv_bot *hs = extra;
3392 struct helpserv_user *hs_user;
3393 struct handle_info *handle;
3395 enum helpserv_level level;
3397 struct string_list *strlist;
3400 if (rd->type != RECDB_OBJECT || !dict_size(rd->d.object)) {
3401 log_module(HS_LOG, LOG_ERROR, "Invalid user %s for %s.", key, hs->helpserv->nick);
3405 if (!(handle = get_handle_info(key))) {
3406 log_module(HS_LOG, LOG_ERROR, "Nonexistant account %s for %s.", key, hs->helpserv->nick);
3409 str = database_get_data(rd->d.object, KEY_HELPER_LEVEL, RECDB_QSTRING);
3411 level = helpserv_str2level(str);
3412 if (level == HlNone) {
3413 log_module(HS_LOG, LOG_ERROR, "Account %s has invalid level %s.", key, str);
3417 log_module(HS_LOG, LOG_ERROR, "Account %s has no level field for %s.", key, hs->helpserv->nick);
3421 hs_user = helpserv_add_user(hs, handle, level);
3423 str = database_get_data(rd->d.object, KEY_HELPER_HELPMODE, RECDB_QSTRING);
3424 hs_user->help_mode = (str && strtol(str, NULL, 0)) ? 1 : 0;
3425 str = database_get_data(rd->d.object, KEY_HELPER_WEEKSTART, RECDB_QSTRING);
3426 hs_user->week_start = str ? strtol(str, NULL, 0) : 0;
3429 stats = database_get_data(GET_RECORD_OBJECT(rd), KEY_HELPER_STATS, RECDB_OBJECT);
3432 /* The tests for strlist->used are for converting the old format to the new one */
3433 strlist = database_get_data(stats, KEY_HELPER_STATS_TIME, RECDB_STRING_LIST);
3435 for (i=0; i < 5 && i < strlist->used; i++)
3436 hs_user->time_per_week[i] = strtoul(strlist->list[i], NULL, 0);
3437 if (strlist->used == 4)
3438 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];
3440 strlist = database_get_data(stats, KEY_HELPER_STATS_PICKUP, RECDB_STRING_LIST);
3442 for (i=0; i < 5 && i < strlist->used; i++)
3443 hs_user->picked_up[i] = strtoul(strlist->list[i], NULL, 0);
3444 if (strlist->used == 2)
3445 hs_user->picked_up[4] = hs_user->picked_up[0]+hs_user->picked_up[1];
3447 strlist = database_get_data(stats, KEY_HELPER_STATS_CLOSE, RECDB_STRING_LIST);
3449 for (i=0; i < 5 && i < strlist->used; i++)
3450 hs_user->closed[i] = strtoul(strlist->list[i], NULL, 0);
3451 if (strlist->used == 2)
3452 hs_user->closed[4] = hs_user->closed[0]+hs_user->closed[1];
3454 strlist = database_get_data(stats, KEY_HELPER_STATS_REASSIGNFROM, RECDB_STRING_LIST);
3456 for (i=0; i < 5 && i < strlist->used; i++)
3457 hs_user->reassigned_from[i] = strtoul(strlist->list[i], NULL, 0);
3458 if (strlist->used == 2)
3459 hs_user->reassigned_from[4] = hs_user->reassigned_from[0]+hs_user->reassigned_from[1];
3461 strlist = database_get_data(stats, KEY_HELPER_STATS_REASSIGNTO, RECDB_STRING_LIST);
3463 for (i=0; i < 5 && i < strlist->used; i++)
3464 hs_user->reassigned_to[i] = strtoul(strlist->list[i], NULL, 0);
3465 if (strlist->used == 2)
3466 hs_user->reassigned_to[4] = hs_user->reassigned_to[0]+hs_user->reassigned_to[1];
3473 static int request_write_helper(const char *key, void *data, void *extra) {
3474 struct helpserv_request *request = data;
3475 struct saxdb_context *ctx = extra;
3477 if (!request->handle)
3480 saxdb_start_record(ctx, key, 0);
3481 if (request->helper) {
3482 saxdb_write_string(ctx, KEY_REQUEST_HELPER, request->helper->handle->handle);
3483 saxdb_write_int(ctx, KEY_REQUEST_ASSIGNED, request->assigned);
3485 saxdb_write_string(ctx, KEY_REQUEST_HANDLE, request->handle->handle);
3486 saxdb_write_int(ctx, KEY_REQUEST_OPENED, request->opened);
3487 saxdb_write_string_list(ctx, KEY_REQUEST_TEXT, request->text);
3488 saxdb_end_record(ctx);
3492 static int request_read_helper(const char *key, void *data, void *extra) {
3493 struct record_data *rd = data;
3494 struct helpserv_bot *hs = extra;
3495 struct helpserv_request *request;
3496 struct string_list *strlist;
3499 if (rd->type != RECDB_OBJECT || !dict_size(rd->d.object)) {
3500 log_module(HS_LOG, LOG_ERROR, "Invalid request %s:%s.", hs->helpserv->nick, key);
3504 request = calloc(1, sizeof(struct helpserv_request));
3506 request->id = strtoul(key, NULL, 0);
3508 request->user = NULL;
3509 request->parent_nick_list = request->parent_hand_list = NULL;
3511 str = database_get_data(rd->d.object, KEY_REQUEST_HANDLE, RECDB_QSTRING);
3512 if (!str || !(request->handle = get_handle_info(str))) {
3513 log_module(HS_LOG, LOG_ERROR, "Request %s:%s has an invalid or nonexistant account.", hs->helpserv->nick, key);
3517 if (!(request->parent_hand_list = dict_find(helpserv_reqs_byhand_dict, request->handle->handle, NULL))) {
3518 request->parent_hand_list = helpserv_reqlist_alloc();
3519 dict_insert(helpserv_reqs_byhand_dict, request->handle->handle, request->parent_hand_list);
3521 helpserv_reqlist_append(request->parent_hand_list, request);
3523 str = database_get_data(rd->d.object, KEY_REQUEST_OPENED, RECDB_QSTRING);
3525 log_module(HS_LOG, LOG_ERROR, "Request %s:%s has a nonexistant opening time. Using time(NULL).", hs->helpserv->nick, key);
3526 request->opened = time(NULL);
3528 request->opened = strtoul(str, NULL, 0);
3531 str = database_get_data(rd->d.object, KEY_REQUEST_ASSIGNED, RECDB_QSTRING);
3533 request->assigned = strtoul(str, NULL, 0);
3535 str = database_get_data(rd->d.object, KEY_REQUEST_HELPER, RECDB_QSTRING);
3537 if (!(request->helper = dict_find(hs->users, str, NULL))) {
3538 log_module(HS_LOG, LOG_ERROR, "Request %s:%s has an invalid or nonexistant helper.", hs->helpserv->nick, key);
3543 if (!hs->unhandled) {
3544 request->next_unhandled = NULL;
3545 hs->unhandled = request;
3546 } else if (hs->unhandled->opened > request->opened) {
3547 request->next_unhandled = hs->unhandled;
3548 hs->unhandled = request;
3550 struct helpserv_request *unh;
3551 for (unh = hs->unhandled; unh->next_unhandled && (unh->next_unhandled->opened < request->opened); unh = unh->next_unhandled);
3552 request->next_unhandled = unh->next_unhandled;
3553 unh->next_unhandled = request;
3557 strlist = database_get_data(rd->d.object, KEY_REQUEST_TEXT, RECDB_STRING_LIST);
3559 log_module(HS_LOG, LOG_ERROR, "Request %s:%s has no text.", hs->helpserv->nick, key);
3563 request->text = string_list_copy(strlist);
3565 dict_insert(hs->requests, strdup(key), request);
3571 helpserv_bot_write(const char *key, void *data, void *extra) {
3572 const struct helpserv_bot *hs = data;
3573 struct saxdb_context *ctx = extra;
3574 enum page_source pagesrc;
3575 enum message_type msgtype;
3576 enum interval_type inttype;
3577 enum persistence_type persisttype;
3578 struct string_list *slist;
3581 saxdb_start_record(ctx, key, 1);
3584 saxdb_start_record(ctx, KEY_HELPERS, 1);
3585 dict_foreach(hs->users, user_write_helper, ctx);
3586 saxdb_end_record(ctx);
3589 if (hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_CLOSE) {
3590 saxdb_start_record(ctx, KEY_REQUESTS, 0);
3591 dict_foreach(hs->requests, request_write_helper, ctx);
3592 saxdb_end_record(ctx);
3595 /* Other settings and state */
3596 saxdb_write_string(ctx, KEY_HELP_CHANNEL, hs->helpchan->name);
3597 slist = alloc_string_list(PGSRC_COUNT);
3598 for (pagesrc=0; pagesrc<PGSRC_COUNT; pagesrc++) {
3599 struct chanNode *target = hs->page_targets[pagesrc];
3600 string_list_append(slist, strdup(target ? target->name : "*"));
3602 saxdb_write_string_list(ctx, KEY_PAGE_DEST, slist);
3603 free_string_list(slist);
3604 for (pagesrc=0; pagesrc<PGSRC_COUNT; pagesrc++) {
3605 const char *src = page_types[hs->page_types[pagesrc]].db_name;
3606 saxdb_write_string(ctx, page_sources[pagesrc].db_name, src);
3608 for (msgtype=0; msgtype<MSGTYPE_COUNT; msgtype++) {
3609 const char *msg = hs->messages[msgtype];
3611 saxdb_write_string(ctx, message_types[msgtype].db_name, msg);
3613 for (inttype=0; inttype<INTERVAL_COUNT; inttype++) {
3614 if (!hs->intervals[inttype])
3616 saxdb_write_int(ctx, interval_types[inttype].db_name, hs->intervals[inttype]);
3618 for (persisttype=0; persisttype<PERSIST_T_COUNT; persisttype++) {
3619 const char *persist = persistence_lengths[hs->persist_lengths[persisttype]].db_name;
3620 saxdb_write_string(ctx, persistence_types[persisttype].db_name, persist);
3622 saxdb_write_string(ctx, KEY_NOTIFICATION, notification_types[hs->notify].db_name);
3623 saxdb_write_int(ctx, KEY_REGISTERED, hs->registered);
3624 saxdb_write_int(ctx, KEY_IDWRAP, hs->id_wrap);
3625 saxdb_write_int(ctx, KEY_REQ_MAXLEN, hs->req_maxlen);
3626 saxdb_write_int(ctx, KEY_LAST_REQUESTID, hs->last_requestid);
3628 saxdb_write_string(ctx, KEY_REGISTRAR, hs->registrar);
3629 saxdb_write_int(ctx, KEY_PRIVMSG_ONLY, hs->privmsg_only);
3630 saxdb_write_int(ctx, KEY_REQ_ON_JOIN, hs->req_on_join);
3631 saxdb_write_int(ctx, KEY_AUTO_VOICE, hs->auto_voice);
3632 saxdb_write_int(ctx, KEY_AUTO_DEVOICE, hs->auto_devoice);
3633 saxdb_write_int(ctx, KEY_LAST_ACTIVE, hs->last_active);
3635 /* End bot record */
3636 saxdb_end_record(ctx);
3641 helpserv_saxdb_write(struct saxdb_context *ctx) {
3642 saxdb_start_record(ctx, KEY_BOTS, 1);
3643 dict_foreach(helpserv_bots_dict, helpserv_bot_write, ctx);
3644 saxdb_end_record(ctx);
3645 saxdb_write_int(ctx, KEY_LAST_STATS_UPDATE, last_stats_update);
3649 static int helpserv_bot_read(const char *key, void *data, UNUSED_ARG(void *extra)) {
3650 struct record_data *br = data, *raw_record;
3651 struct helpserv_bot *hs;
3652 char *registrar, *helpchannel_name, *str;
3653 dict_t users, requests;
3654 enum page_source pagesrc;
3655 enum message_type msgtype;
3656 enum interval_type inttype;
3657 enum persistence_type persisttype;
3659 users = database_get_data(GET_RECORD_OBJECT(br), KEY_HELPERS, RECDB_OBJECT);
3661 log_module(HS_LOG, LOG_ERROR, "%s has no users.", key);
3664 helpchannel_name = database_get_data(GET_RECORD_OBJECT(br), KEY_HELP_CHANNEL, RECDB_QSTRING);
3665 if (!helpchannel_name || !IsChannelName(helpchannel_name)) {
3666 log_module(HS_LOG, LOG_ERROR, "%s has an invalid channel name.", key);
3669 registrar = database_get_data(GET_RECORD_OBJECT(br), KEY_REGISTRAR, RECDB_QSTRING);
3671 hs = register_helpserv(key, helpchannel_name, registrar);
3673 raw_record = dict_find(GET_RECORD_OBJECT(br), KEY_PAGE_DEST, NULL);
3674 switch (raw_record ? raw_record->type : RECDB_INVALID) {
3676 set_page_target(hs, PGSRC_COMMAND, GET_RECORD_QSTRING(raw_record));
3677 pagesrc = PGSRC_COMMAND + 1;
3679 case RECDB_STRING_LIST: {
3680 struct string_list *slist = GET_RECORD_STRING_LIST(raw_record);
3681 for (pagesrc=0; (pagesrc<slist->used) && (pagesrc<PGSRC_COUNT); pagesrc++) {
3682 const char *dest = slist->list[pagesrc];
3683 set_page_target(hs, pagesrc, strcmp(dest, "*") ? dest : NULL);
3688 set_page_target(hs, PGSRC_COMMAND, NULL);
3689 pagesrc = PGSRC_COMMAND + 1;
3692 while (pagesrc < PGSRC_COUNT) {
3693 set_page_target(hs, pagesrc++, hs->page_targets[PGSRC_COMMAND] ? hs->page_targets[PGSRC_COMMAND]->name : NULL);
3696 for (pagesrc=0; pagesrc<PGSRC_COUNT; pagesrc++) {
3697 str = database_get_data(GET_RECORD_OBJECT(br), page_sources[pagesrc].db_name, RECDB_QSTRING);
3698 hs->page_types[pagesrc] = str ? page_type_from_name(str) : PAGE_NONE;
3701 for (msgtype=0; msgtype<MSGTYPE_COUNT; msgtype++) {
3702 str = database_get_data(GET_RECORD_OBJECT(br), message_types[msgtype].db_name, RECDB_QSTRING);
3703 hs->messages[msgtype] = str ? strdup(str) : NULL;
3706 for (inttype=0; inttype<INTERVAL_COUNT; inttype++) {
3707 str = database_get_data(GET_RECORD_OBJECT(br), interval_types[inttype].db_name, RECDB_QSTRING);
3708 hs->intervals[inttype] = str ? ParseInterval(str) : 0;
3710 if (hs->intervals[INTERVAL_WHINE_INTERVAL])
3711 timeq_add(now + hs->intervals[INTERVAL_WHINE_INTERVAL], run_whine_interval, hs);
3712 if (hs->intervals[INTERVAL_EMPTY_INTERVAL])
3713 timeq_add(now + hs->intervals[INTERVAL_EMPTY_INTERVAL], run_empty_interval, hs);
3715 for (persisttype=0; persisttype<PERSIST_T_COUNT; persisttype++) {
3716 str = database_get_data(GET_RECORD_OBJECT(br), persistence_types[persisttype].db_name, RECDB_QSTRING);
3717 hs->persist_lengths[persisttype] = str ? persistence_from_name(str) : PERSIST_QUIT;
3719 str = database_get_data(GET_RECORD_OBJECT(br), KEY_NOTIFICATION, RECDB_QSTRING);
3720 hs->notify = str ? notification_from_name(str) : NOTIFY_NONE;
3721 str = database_get_data(GET_RECORD_OBJECT(br), KEY_REGISTERED, RECDB_QSTRING);
3723 hs->registered = strtol(str, NULL, 0);
3724 str = database_get_data(GET_RECORD_OBJECT(br), KEY_IDWRAP, RECDB_QSTRING);
3726 hs->id_wrap = strtoul(str, NULL, 0);
3727 str = database_get_data(GET_RECORD_OBJECT(br), KEY_REQ_MAXLEN, RECDB_QSTRING);
3729 hs->req_maxlen = strtoul(str, NULL, 0);
3730 str = database_get_data(GET_RECORD_OBJECT(br), KEY_LAST_REQUESTID, RECDB_QSTRING);
3732 hs->last_requestid = strtoul(str, NULL, 0);
3733 str = database_get_data(GET_RECORD_OBJECT(br), KEY_PRIVMSG_ONLY, RECDB_QSTRING);
3734 hs->privmsg_only = str ? enabled_string(str) : 0;
3735 str = database_get_data(GET_RECORD_OBJECT(br), KEY_REQ_ON_JOIN, RECDB_QSTRING);
3736 hs->req_on_join = str ? enabled_string(str) : 0;
3737 str = database_get_data(GET_RECORD_OBJECT(br), KEY_AUTO_VOICE, RECDB_QSTRING);
3738 hs->auto_voice = str ? enabled_string(str) : 0;
3739 str = database_get_data(GET_RECORD_OBJECT(br), KEY_AUTO_DEVOICE, RECDB_QSTRING);
3740 hs->auto_devoice = str ? enabled_string(str) : 0;
3741 str = database_get_data(GET_RECORD_OBJECT(br), KEY_LAST_ACTIVE, RECDB_QSTRING);
3742 hs->last_active = str ? strtoul(str, NULL, 0) : now;
3744 dict_foreach(users, user_read_helper, hs);
3746 requests = database_get_data(GET_RECORD_OBJECT(br), KEY_REQUESTS, RECDB_OBJECT);
3748 dict_foreach(requests, request_read_helper, hs);
3754 helpserv_saxdb_read(struct dict *conf_db) {
3758 if ((object = database_get_data(conf_db, KEY_BOTS, RECDB_OBJECT))) {
3759 dict_foreach(object, helpserv_bot_read, NULL);
3762 str = database_get_data(conf_db, KEY_LAST_STATS_UPDATE, RECDB_QSTRING);
3763 last_stats_update = str ? strtoul(str, NULL, 0) : now;
3767 static void helpserv_conf_read(void) {
3771 if (!(conf_node = conf_get_data(HELPSERV_CONF_NAME, RECDB_OBJECT))) {
3772 log_module(HS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type", HELPSERV_CONF_NAME);
3776 str = database_get_data(conf_node, "db_backup_freq", RECDB_QSTRING);
3777 helpserv_conf.db_backup_frequency = str ? ParseInterval(str) : 7200;
3779 str = database_get_data(conf_node, "description", RECDB_QSTRING);
3780 helpserv_conf.description = str ? str : "Help Queue Manager";
3782 str = database_get_data(conf_node, "reqlogfile", RECDB_QSTRING);
3783 if (str && strlen(str))
3784 helpserv_conf.reqlogfile = str;
3786 helpserv_conf.reqlogfile = NULL;
3788 str = database_get_data(conf_node, "expiration", RECDB_QSTRING);
3789 helpserv_conf.expire_age = ParseInterval(str ? str : "60d");
3790 str = database_get_data(conf_node, "modstats_level", RECDB_QSTRING);
3791 helpserv_conf.modstats_level = str ? strtoul(str, NULL, 0) : 850;
3792 str = database_get_data(conf_node, "user_escape", RECDB_QSTRING);
3793 helpserv_conf.user_escape = str ? str[0] : '@';
3799 if (helpserv_conf.reqlogfile
3800 && !(reqlog_f = fopen(helpserv_conf.reqlogfile, "a"))) {
3801 log_module(HS_LOG, LOG_ERROR, "Unable to open request logfile (%s): %s", helpserv_conf.reqlogfile, strerror(errno));
3805 static struct helpserv_cmd *
3806 helpserv_define_func(const char *name, helpserv_func_t *func, enum helpserv_level level, long flags) {
3807 struct helpserv_cmd *cmd = calloc(1, sizeof(struct helpserv_cmd));
3809 cmd->access = level;
3813 dict_insert(helpserv_func_dict, name, cmd);
3818 /* Drop requests that persist until part when a user leaves the chan */
3819 static void handle_part(struct modeNode *mn, UNUSED_ARG(const char *reason)) {
3820 struct helpserv_botlist *botlist;
3821 struct helpserv_userlist *userlist;
3822 const int from_opserv = 0; /* for helpserv_notice */
3825 if ((botlist = dict_find(helpserv_bots_bychan_dict, mn->channel->name, NULL))) {
3826 for (i=0; i < botlist->used; i++) {
3827 struct helpserv_bot *hs;
3830 hs = botlist->list[i];
3833 if (hs->persist_lengths[PERSIST_T_REQUEST] != PERSIST_PART)
3836 for (it=dict_first(hs->requests); it; it=iter_next(it)) {
3837 struct helpserv_request *req = iter_data(it);
3839 if (mn->user != req->user)
3841 if (req->text->used) {
3842 helpserv_message(hs, mn->user, MSGTYPE_REQ_DROPPED);
3843 helpserv_msguser(mn->user, "HSMSG_REQ_DROPPED_PART", mn->channel->name, req->id);
3844 if (req->helper && (hs->notify >= NOTIFY_DROP))
3845 helpserv_notify(req->helper, "HSMSG_NOTIFY_REQ_DROP_PART", req->id, mn->user->nick);
3847 helpserv_log_request(req, "Dropped");
3848 dict_remove(hs->requests, iter_key(it));
3854 if (mn->user->handle_info && (userlist = dict_find(helpserv_users_byhand_dict, mn->user->handle_info->handle, NULL))) {
3855 for (i=0; i < userlist->used; i++) {
3856 struct helpserv_user *hs_user = userlist->list[i];
3857 struct helpserv_bot *hs = hs_user->hs;
3860 if ((hs->helpserv == NULL) || (hs->helpchan != mn->channel) || find_handle_in_channel(hs->helpchan, mn->user->handle_info, mn->user))
3863 /* In case of the clock being set back for whatever reason,
3864 * minimize penalty. Don't duplicate this in handle_quit because
3865 * when users quit, handle_part is called for every channel first.
3867 if (hs_user->join_time && (hs_user->join_time < now)) {
3868 hs_user->time_per_week[0] += (unsigned int)(now - hs_user->join_time);
3869 hs_user->time_per_week[4] += (unsigned int)(now - hs_user->join_time);
3871 hs_user->join_time = 0;
3873 for (it=dict_first(hs->requests); it; it=iter_next(it)) {
3874 struct helpserv_request *req=iter_data(it);
3876 if ((hs->persist_lengths[PERSIST_T_HELPER] == PERSIST_PART)
3877 && (req->helper == hs_user)) {
3878 char our_reason[CHANNELLEN + 8];
3879 sprintf(our_reason, "parted %s", mn->channel->name);
3880 helpserv_page_helper_gone(hs, req, our_reason);
3884 if (hs->intervals[INTERVAL_EMPTY_INTERVAL] && hs_user->level >= HlHelper) {
3887 if ((num_trials = find_helpchan_helpers(hs)) >= 0) {
3888 unsigned int num_unh;
3889 struct helpserv_request *unh;
3891 for (num_unh=0, unh=hs->unhandled; unh; num_unh++)
3892 unh = unh->next_unhandled;
3895 helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_FIRSTONLYTRIALALERT", hs->helpchan->name, mn->user->nick, num_trials, num_unh);
3897 helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_FIRSTEMPTYALERT", hs->helpchan->name, mn->user->nick, num_unh);
3899 if (num_unh || !hs->req_on_join) {
3900 timeq_del(0, run_empty_interval, hs, TIMEQ_IGNORE_WHEN);
3901 timeq_add(now + hs->intervals[INTERVAL_EMPTY_INTERVAL], run_empty_interval, hs);
3909 /* Drop requests that persist until part or quit when a user quits. Otherwise
3910 * set req->user to null (it's no longer valid) if they have a handle,
3911 * and drop it if they don't (nowhere to store the request).
3913 * Unassign requests where req->helper persists until the helper parts or
3915 static void handle_quit(struct userNode *user, UNUSED_ARG(struct userNode *killer), UNUSED_ARG(const char *why)) {
3916 struct helpserv_reqlist *reqlist;
3917 struct helpserv_userlist *userlist;
3920 if (IsLocal(user)) {
3921 struct helpserv_bot *hs;
3922 if ((hs = dict_find(helpserv_bots_dict, user->nick, NULL))) {
3923 hs->helpserv = NULL;
3928 if ((reqlist = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL))) {
3930 for (i=0; i < n; i++) {
3931 struct helpserv_request *req = reqlist->list[0];
3933 if ((req->hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_QUIT) || !req->handle) {
3935 sprintf(buf, "%lu", req->id);
3937 if (req->helper && (req->hs->notify >= NOTIFY_DROP))
3938 helpserv_notify(req->helper, "HSMSG_NOTIFY_REQ_DROP_QUIT", req->id, req->user->nick);
3940 helpserv_log_request(req, "Dropped");
3941 dict_remove(req->hs->requests, buf);
3944 req->parent_nick_list = NULL;
3945 helpserv_reqlist_remove(reqlist, req);
3947 if (req->helper && (req->hs->notify >= NOTIFY_USER))
3948 helpserv_notify(req->helper, "HSMSG_NOTIFY_USER_QUIT", req->id, user->nick);
3952 dict_remove(helpserv_reqs_bynick_dict, user->nick);
3955 if (user->handle_info && (userlist = dict_find(helpserv_users_byhand_dict, user->handle_info->handle, NULL))) {
3956 for (i=0; i < userlist->used; i++) {
3957 struct helpserv_user *hs_user = userlist->list[i];
3958 struct helpserv_bot *hs = hs_user->hs;
3961 if ((hs->helpserv == NULL) || user->next_authed || (user->handle_info->users != user))
3964 for (it=dict_first(hs->requests); it; it=iter_next(it)) {
3965 struct helpserv_request *req=iter_data(it);
3967 if ((hs->persist_lengths[PERSIST_T_HELPER] == PERSIST_QUIT) && (req->helper == hs_user)) {
3968 helpserv_page_helper_gone(hs, req, "disconnected");
3975 static void associate_requests_bybot(struct helpserv_bot *hs, struct userNode *user, int force_greet) {
3976 struct helpserv_reqlist *reqlist, *hand_reqlist=NULL;
3977 struct helpserv_request *newest=NULL, *nicknewest=NULL;
3979 const int from_opserv = 0; /* For helpserv_notice */
3981 if (!(user->handle_info && (hand_reqlist = dict_find(helpserv_reqs_byhand_dict, user->handle_info->handle, NULL))) && !force_greet) {
3985 reqlist = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL);
3988 for (i=0; i < hand_reqlist->used; i++) {
3989 struct helpserv_request *req=hand_reqlist->list[i];
3991 if (req->user || (req->hs != hs))
3996 reqlist = helpserv_reqlist_alloc();
3997 dict_insert(helpserv_reqs_bynick_dict, user->nick, reqlist);
3999 req->parent_nick_list = reqlist;
4000 helpserv_reqlist_append(reqlist, req);
4002 if (req->helper && (hs->notify >= NOTIFY_USER))
4003 helpserv_notify(req->helper, "HSMSG_NOTIFY_USER_FOUND", req->id, user->nick);
4005 if (!newest || (newest->opened < req->opened))
4010 /* If it's supposed to force a greeting, only bail out if there are no
4011 * requests at all. If it's not supposed to force a greeting, bail out if
4012 * nothing was changed. */
4013 if (!(newest || (force_greet && reqlist)))
4016 /* Possible conditions here:
4017 * 1. newest == NULL, force_greeting == 1, reqlist != NULL
4018 * 2. newest != NULL, force_greeting doesn't matter, reqlist != NULL */
4020 /* Figure out which request will get their next message */
4021 for (i=0; i < reqlist->used; i++) {
4022 struct helpserv_request *req=reqlist->list[i];
4027 if (!nicknewest || (nicknewest->opened < req->opened))
4030 if (hs->auto_voice && req->helper)
4032 struct mod_chanmode change;
4033 mod_chanmode_init(&change);
4035 change.args[0].mode = MODE_VOICE;
4036 if ((change.args[0].u.member = GetUserMode(hs->helpchan, user)))
4037 mod_chanmode_announce(hs->helpserv, hs->helpchan, &change);
4041 if ((force_greet && nicknewest) || (newest && (nicknewest == newest))) {
4042 /* Let the user know. Either the user is forced to be greeted, or the
4043 * above has changed which request will get their next message. */
4044 helpserv_msguser(user, "HSMSG_GREET_EXISTING_REQ", hs->helpchan->name, nicknewest->id);
4048 static void associate_requests_bychan(struct chanNode *chan, struct userNode *user, int force_greet) {
4049 struct helpserv_botlist *botlist;
4052 if (!(botlist = dict_find(helpserv_bots_bychan_dict, chan->name, NULL)))
4055 for (i=0; i < botlist->used; i++)
4056 associate_requests_bybot(botlist->list[i], user, force_greet);
4060 /* Greet users upon joining a helpserv channel (if greeting is set) and set
4061 * req->user to the user joining for all requests owned by the user's handle
4062 * (if any) with a req->user == NULL */
4063 static int handle_join(struct modeNode *mNode) {
4064 struct userNode *user = mNode->user;
4065 struct chanNode *chan = mNode->channel;
4066 struct helpserv_botlist *botlist;
4068 const int from_opserv = 0; /* for helpserv_notice */
4073 if (!(botlist = dict_find(helpserv_bots_bychan_dict, chan->name, NULL)))
4076 for (i=0; i < botlist->used; i++) {
4077 struct helpserv_bot *hs=botlist->list[i];
4079 if (user->handle_info) {
4080 struct helpserv_user *hs_user;
4082 if ((hs_user = dict_find(hs->users, user->handle_info->handle, NULL))) {
4083 if (!hs_user->join_time)
4084 hs_user->join_time = now;
4086 if (hs_user->level >= HlHelper && hs->intervals[INTERVAL_EMPTY_INTERVAL] && hs->helpchan_empty) {
4087 hs->helpchan_empty = 0;
4088 timeq_del(0, run_empty_interval, hs, TIMEQ_IGNORE_WHEN);
4089 helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_EMPTYNOMORE", user->nick, hs->helpchan->name);
4091 continue; /* Don't want helpers to have request-on-join */
4095 if (self->burst && !hs->req_on_join)
4098 associate_requests_bybot(hs, user, 1);
4100 helpserv_message(hs, user, MSGTYPE_GREETING);
4102 /* Make sure this is at the end (because of the continues) */
4103 if (hs->req_on_join) {
4104 struct helpserv_reqlist *reqlist;
4107 if ((reqlist = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL))) {
4108 for (j=0; j < reqlist->used; j++)
4109 if (reqlist->list[j]->hs == hs)
4111 if (j < reqlist->used)
4115 create_request(user, hs, 1);
4121 /* Update helpserv_reqs_bynick_dict upon nick change */
4122 static void handle_nickchange(struct userNode *user, const char *old_nick) {
4123 struct helpserv_reqlist *reqlist;
4126 if (!(reqlist = dict_find(helpserv_reqs_bynick_dict, old_nick, NULL)))
4129 /* Don't free the list when we switch it over to the new nick. */
4130 dict_remove2(helpserv_reqs_bynick_dict, old_nick, 1);
4131 dict_insert(helpserv_reqs_bynick_dict, user->nick, reqlist);
4133 for (i=0; i < reqlist->used; i++) {
4134 struct helpserv_request *req=reqlist->list[i];
4136 if (req->helper && (req->hs->notify >= NOTIFY_USER))
4137 helpserv_notify(req->helper, "HSMSG_NOTIFY_USER_NICK", req->id, old_nick, user->nick);
4141 /* Also update helpserv_reqs_byhand_dict upon handle rename */
4142 static void handle_nickserv_rename(struct handle_info *handle, const char *old_handle) {
4143 struct helpserv_reqlist *reqlist;
4144 struct helpserv_userlist *userlist;
4147 /* First, rename the handle in the requests dict */
4148 if ((reqlist = dict_find(helpserv_reqs_byhand_dict, old_handle, NULL))) {
4149 /* Don't free the list */
4150 dict_remove2(helpserv_reqs_byhand_dict, old_handle, 1);
4151 dict_insert(helpserv_reqs_byhand_dict, handle->handle, reqlist);
4154 /* Second, rename the handle in the users dict */
4155 if ((userlist = dict_find(helpserv_users_byhand_dict, old_handle, NULL))) {
4156 dict_remove2(helpserv_users_byhand_dict, old_handle, 1);
4158 for (i=0; i < userlist->used; i++)
4159 dict_remove2(userlist->list[i]->hs->users, old_handle, 1);
4161 dict_insert(helpserv_users_byhand_dict, handle->handle, userlist);
4162 for (i=0; i < userlist->used; i++)
4163 dict_insert(userlist->list[i]->hs->users, handle->handle, userlist->list[i]);
4167 for (i=0; i < reqlist->used; i++) {
4168 struct helpserv_request *req=reqlist->list[i];
4170 if (req->helper && (req->hs->notify >= NOTIFY_HANDLE))
4171 helpserv_notify(req->helper, "HSMSG_NOTIFY_HAND_RENAME", req->id, old_handle, handle->handle);
4176 /* Deals with two cases:
4177 * 1. No handle -> handle
4178 * - Bots with a request assigned to both the user (w/o handle) and the
4179 * handle can exist in this case. When a message is sent,
4180 * helpserv_usermsg will append it to the most recently opened request.
4181 * - Requests assigned to the handle are NOT assigned to the user, since
4182 * multiple people can auth to the same handle at once. Wait for them to
4183 * join / privmsg before setting req->user.
4184 * 2. Handle -> handle
4185 * - Generally avoided, but sometimes the code may allow this.
4186 * - Requests that persist only until part/quit are brought along to the
4188 * - Requests that persist until closed (stay saved with the handle) are
4189 * left with the old handle. This is to prevent the confusing situation
4190 * where some requests are carried over to the new handle, and some are
4191 * left (because req->handle is the same for all of them, but only some
4192 * have req->user set).
4193 * - In either of the above cases, if a user is on a bot's userlist and has
4194 * requests assigned to them, it will give them a list. */
4195 static void handle_nickserv_auth(struct userNode *user, struct handle_info *old_handle) {
4196 struct helpserv_reqlist *reqlist, *dellist=NULL, *hand_reqlist, *oldhand_reqlist;
4197 struct helpserv_userlist *userlist;
4200 const int from_opserv = 0; /* for helpserv_notice */
4202 if (!user->handle_info)
4203 return; /* Authed user is quitting */
4205 if ((userlist = dict_find(helpserv_users_byhand_dict, user->handle_info->handle, NULL))) {
4206 for (i=0; i < userlist->used; i++) {
4207 struct helpserv_user *hs_user = userlist->list[i];
4208 struct helpserv_bot *hs = hs_user->hs;
4209 struct helpserv_reqlist helper_reqs;
4210 struct helpfile_table tbl;
4212 if (!hs_user->join_time && find_handle_in_channel(hs->helpchan, hs_user->handle, NULL))
4213 hs_user->join_time = now;
4215 helpserv_reqlist_init(&helper_reqs);
4217 for (it=dict_first(hs->requests); it; it=iter_next(it)) {
4218 struct helpserv_request *req=iter_data(it);
4220 if (req->helper == hs_user)
4221 helpserv_reqlist_append(&helper_reqs, req);
4224 if (helper_reqs.used) {
4225 tbl.length = helper_reqs.used+1;
4227 tbl.flags = TABLE_NO_FREE;
4228 tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
4229 tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
4230 tbl.contents[0][0] = "Bot";
4231 tbl.contents[0][1] = "ID#";
4232 tbl.contents[0][2] = "Nick";
4233 tbl.contents[0][3] = "Account";
4234 tbl.contents[0][4] = "Opened";
4236 for (j=1; j <= helper_reqs.used; j++) {
4237 struct helpserv_request *req=helper_reqs.list[j-1];
4238 char reqid[12], timestr[MAX_LINE_SIZE];
4241 tbl.contents[j] = alloca(tbl.width * sizeof(**tbl.contents));
4242 tbl.contents[j][0] = req->hs->helpserv->nick;
4243 sprintf(reqid, "%lu", req->id);
4244 tbl.contents[j][1] = strdup(reqid);
4245 tbl.contents[j][2] = req->user ? req->user->nick : "Not online";
4246 tbl.contents[j][3] = req->handle ? req->handle->handle : "Not authed";
4248 strftime(timestr, MAX_LINE_SIZE, HSFMT_TIME, localtime(&feh));
4249 tbl.contents[j][4] = strdup(timestr);
4252 helpserv_notice(user, "HSMSG_REQLIST_AUTH");
4253 table_send(hs->helpserv, user->nick, 0, NULL, tbl);
4255 for (j=1; j <= helper_reqs.used; j++) {
4256 free((char *)tbl.contents[j][1]);
4257 free((char *)tbl.contents[j][4]);
4261 helpserv_reqlist_clean(&helper_reqs);
4266 if (!(reqlist = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL))) {
4267 for (i=0; i < user->channels.used; i++)
4268 associate_requests_bychan(user->channels.list[i]->channel, user, 0);
4272 if (!(hand_reqlist = dict_find(helpserv_reqs_byhand_dict, user->handle_info->handle, NULL))) {
4273 hand_reqlist = helpserv_reqlist_alloc();
4274 dict_insert(helpserv_reqs_byhand_dict, user->handle_info->handle, hand_reqlist);
4278 dellist = helpserv_reqlist_alloc();
4279 oldhand_reqlist = dict_find(helpserv_reqs_byhand_dict, old_handle->handle, NULL);
4281 oldhand_reqlist = NULL;
4284 for (i=0; i < reqlist->used; i++) {
4285 struct helpserv_request *req = reqlist->list[i];
4286 struct helpserv_bot *hs=req->hs;
4288 if (!old_handle || hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_PART || hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_QUIT) {
4289 /* The request needs to be assigned to the new handle; either it
4290 * only persists until part/quit (so it makes sense to keep it as
4291 * close to the user as possible, and if it's made persistent later
4292 * then it's attached to the new handle) or there is no old handle.
4295 req->handle = user->handle_info;
4297 req->parent_hand_list = hand_reqlist;
4298 helpserv_reqlist_append(hand_reqlist, req);
4300 if (oldhand_reqlist) {
4301 if (oldhand_reqlist->used == 1) {
4302 dict_remove(helpserv_reqs_byhand_dict, old_handle->handle);
4303 oldhand_reqlist = NULL;
4305 helpserv_reqlist_remove(oldhand_reqlist, req);
4310 char buf[CHANNELLEN + 14];
4312 if (hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_PART) {
4313 sprintf(buf, "part channel %s", hs->helpchan->name);
4315 strcpy(buf, "quit irc");
4318 helpserv_msguser(user, "HSMSG_REQ_AUTH_MOVED", user->handle_info->handle, hs->helpchan->name, req->id, old_handle->handle, buf);
4319 if (req->helper && (hs->notify >= NOTIFY_HANDLE))
4320 helpserv_notify(req->helper, "HSMSG_NOTIFY_HAND_MOVE", req->id, user->handle_info->handle, old_handle->handle);
4322 if (req->helper && (hs->notify >= NOTIFY_HANDLE))
4323 helpserv_notify(req->helper, "HSMSG_NOTIFY_HAND_AUTH", req->id, user->nick, user->handle_info->handle);
4327 req->parent_nick_list = NULL;
4328 /* Would rather not mess with the list while iterating through
4330 helpserv_reqlist_append(dellist, req);
4332 helpserv_msguser(user, "HSMSG_REQ_AUTH_STUCK", user->handle_info->handle, hs->helpchan->name, req->id, old_handle->handle);
4333 if (req->helper && (hs->notify >= NOTIFY_HANDLE))
4334 helpserv_notify(req->helper, "HSMSG_NOTIFY_HAND_STUCK", req->id, user->nick, user->handle_info->handle, old_handle->handle);
4339 if (dellist->used) {
4340 if (dellist->used == reqlist->used) {
4341 dict_remove(helpserv_reqs_bynick_dict, user->nick);
4343 for (i=0; i < dellist->used; i++)
4344 helpserv_reqlist_remove(reqlist, dellist->list[i]);
4347 helpserv_reqlist_free(dellist);
4350 for (i=0; i < user->channels.used; i++)
4351 associate_requests_bychan(user->channels.list[i]->channel, user, 0);
4355 /* Disassociate all requests from the handle. If any have req->user == NULL
4356 * then give them to the user doing the unregistration (if not an oper/helper)
4357 * otherwise the first nick it finds authed (it lets them know about this). If
4358 * there are no users authed to the handle online, the requests are lost. This
4359 * may result in the user having >1 request/bot, and messages go to the most
4360 * recently opened request.
4362 * Also, remove the user from all bots that it has access in.
4363 * helpserv_del_user() will take care of unassigning the requests. */
4364 static void handle_nickserv_unreg(struct userNode *user, struct handle_info *handle) {
4365 struct helpserv_reqlist *hand_reqlist;
4366 struct helpserv_userlist *userlist;
4368 const int from_opserv = 0; /* for helpserv_notice */
4369 struct helpserv_bot *hs; /* for helpserv_notice */
4371 if ((userlist = dict_find(helpserv_users_byhand_dict, handle->handle, NULL))) {
4374 /* Each time helpserv_del_user is called, that entry is going to be
4375 * taken out of userlist... so this should cope with that */
4376 for (i=0; i < n; i++) {
4377 struct helpserv_user *hs_user=userlist->list[0];
4378 helpserv_del_user(hs_user->hs, hs_user);
4382 if (!(hand_reqlist = dict_find(helpserv_reqs_byhand_dict, handle->handle, NULL))) {
4386 n = hand_reqlist->used;
4387 for (i=0; i < n; i++) {
4388 struct helpserv_request *req=hand_reqlist->list[0];
4392 req->parent_hand_list = NULL;
4393 helpserv_reqlist_remove(hand_reqlist, req);
4394 if (user && req->helper && (hs->notify >= NOTIFY_HANDLE))
4395 helpserv_notify(req->helper, "HSMSG_NOTIFY_HAND_UNREG", req->id, handle->handle, user->nick);
4399 /* This is probably an expire. Silently remove everything. */
4402 if (req->helper && (hs->notify >= NOTIFY_DROP))
4403 helpserv_notify(req->helper, "HSMSG_NOTIFY_REQ_DROP_UNREGGED", req->id, req->handle->handle);
4404 sprintf(buf, "%lu", req->id);
4405 helpserv_log_request(req, "Account unregistered");
4406 dict_remove(req->hs->requests, buf);
4407 } else if (user->handle_info == handle) {
4409 if (!(req->parent_nick_list = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL))) {
4410 req->parent_nick_list = helpserv_reqlist_alloc();
4411 dict_insert(helpserv_reqs_bynick_dict, user->nick, req->parent_nick_list);
4413 helpserv_reqlist_append(req->parent_nick_list, req);
4415 if (hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_CLOSE)
4416 helpserv_msguser(req->user, "HSMSG_REQ_WARN_UNREG", handle->handle, hs->helpchan->name, req->id);
4418 if (handle->users) {
4419 req->user = handle->users;
4421 if (!(req->parent_nick_list = dict_find(helpserv_reqs_bynick_dict, req->user->nick, NULL))) {
4422 req->parent_nick_list = helpserv_reqlist_alloc();
4423 dict_insert(helpserv_reqs_bynick_dict, req->user->nick, req->parent_nick_list);
4425 helpserv_reqlist_append(req->parent_nick_list, req);
4427 helpserv_msguser(req->user, "HSMSG_REQ_ASSIGNED_UNREG", handle->handle, hs->helpchan->name, req->id);
4428 if (req->helper && (hs->notify >= NOTIFY_USER))
4429 helpserv_notify(req->helper, "HSMSG_NOTIFY_USER_MOVE", req->id, handle->handle, req->user->nick);
4433 helpserv_notice(user, "HSMSG_REQ_DROPPED_UNREG", handle->handle, hs->helpchan->name, req->id);
4434 if (req->helper && (hs->notify >= NOTIFY_DROP))
4435 helpserv_notify(req->helper, "HSMSG_NOTIFY_REQ_DROP_UNREGGED", req->id, req->handle->handle);
4436 sprintf(buf, "%lu", req->id);
4437 helpserv_log_request(req, "Account unregistered");
4438 dict_remove(req->hs->requests, buf);
4444 dict_remove(helpserv_reqs_byhand_dict, handle->handle);
4447 static void handle_nickserv_merge(struct userNode *user, struct handle_info *handle_to, struct handle_info *handle_from) {
4448 struct helpserv_reqlist *reqlist_from, *reqlist_to;
4451 reqlist_to = dict_find(helpserv_reqs_byhand_dict, handle_to->handle, NULL);
4453 if ((reqlist_from = dict_find(helpserv_reqs_byhand_dict, handle_from->handle, NULL))) {
4454 for (i=0; i < reqlist_from->used; i++) {
4455 struct helpserv_request *req=reqlist_from->list[i];
4458 reqlist_to = helpserv_reqlist_alloc();
4459 dict_insert(helpserv_reqs_byhand_dict, handle_to->handle, reqlist_to);
4461 req->parent_hand_list = reqlist_to;
4462 req->handle = handle_to;
4463 helpserv_reqlist_append(reqlist_to, req);
4465 dict_remove(helpserv_reqs_byhand_dict, handle_from->handle);
4469 for (i=0; i < reqlist_to->used; i++) {
4470 struct helpserv_request *req=reqlist_to->list[i];
4472 if (req->helper && (req->hs->notify >= NOTIFY_HANDLE)) {
4473 helpserv_notify(req->helper, "HSMSG_NOTIFY_HAND_MERGE", req->id, handle_to->handle, handle_from->handle, user->nick);
4479 static void handle_nickserv_allowauth(struct userNode *user, struct userNode *target, struct handle_info *handle) {
4480 struct helpserv_reqlist *reqlist;
4483 if ((reqlist = dict_find(helpserv_reqs_bynick_dict, target->nick, NULL))) {
4484 for (i=0; i < reqlist->used; i++) {
4485 struct helpserv_request *req=reqlist->list[i];
4487 if (req->helper && (req->hs->notify >= NOTIFY_HANDLE)) {
4489 helpserv_notify(req->helper, "HSMSG_NOTIFY_ALLOWAUTH", req->id, target->nick, user->nick, handle->handle);
4491 helpserv_notify(req->helper, "HSMSG_NOTIFY_UNALLOWAUTH", req->id, target->nick, user->nick);
4498 static void handle_nickserv_failpw(struct userNode *user, struct handle_info *handle) {
4499 struct helpserv_reqlist *reqlist;
4502 if ((reqlist = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL))) {
4503 for (i=0; i < reqlist->used; i++) {
4504 struct helpserv_request *req=reqlist->list[i];
4505 if (req->helper && (req->hs->notify >= NOTIFY_HANDLE))
4506 helpserv_notify(req->helper, "HSMSG_NOTIFY_FAILPW", req->id, user->nick, handle->handle);
4511 static unsigned long helpserv_next_stats(time_t after_when) {
4512 struct tm *timeinfo = localtime(&after_when);
4514 /* This works because mktime(3) says it will accept out-of-range values
4515 * and fix them for us. tm_wday and tm_yday are ignored. */
4516 timeinfo->tm_mday++;
4518 /* We want to run stats at midnight (local time). */
4519 timeinfo->tm_sec = timeinfo->tm_min = timeinfo->tm_hour = 0;
4521 return mktime(timeinfo);
4524 /* If data != NULL, then don't add to the timeq */
4525 static void helpserv_run_stats(unsigned long when) {
4526 struct helpserv_bot *hs;
4527 struct helpserv_user *hs_user;
4531 dict_iterator_t it, it2;
4533 last_stats_update = when;
4535 day = localtime(&feh)->tm_wday;
4536 for (it=dict_first(helpserv_bots_dict); it; it=iter_next(it)) {
4539 for (it2=dict_first(hs->users); it2; it2=iter_next(it2)) {
4540 hs_user = iter_data(it2);
4542 /* Skip the helper if it's not their week-start day. */
4543 if (hs_user->week_start != day)
4546 /* Adjust their credit if they are in-channel at rollover. */
4547 if (hs_user->join_time) {
4548 hs_user->time_per_week[0] += when - hs_user->join_time;
4549 hs_user->time_per_week[4] += when - hs_user->join_time;
4550 hs_user->join_time = when;
4553 /* Shift everything */
4554 for (i=3; i > 0; i--) {
4555 hs_user->time_per_week[i] = hs_user->time_per_week[i-1];
4556 hs_user->picked_up[i] = hs_user->picked_up[i-1];
4557 hs_user->closed[i] = hs_user->closed[i-1];
4558 hs_user->reassigned_from[i] = hs_user->reassigned_from[i-1];
4559 hs_user->reassigned_to[i] = hs_user->reassigned_to[i-1];
4562 /* Reset it for this week */
4563 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;
4568 static void helpserv_timed_run_stats(UNUSED_ARG(void *data)) {
4569 helpserv_run_stats(now);
4570 timeq_add(helpserv_next_stats(now), helpserv_timed_run_stats, data);
4574 helpserv_define_option(const char *name, helpserv_option_func_t *func) {
4575 dict_insert(helpserv_option_dict, name, func);
4578 static void helpserv_db_cleanup(void) {
4580 unreg_part_func(handle_part);
4581 unreg_del_user_func(handle_quit);
4582 close_helpfile(helpserv_helpfile);
4583 dict_delete(helpserv_func_dict);
4584 dict_delete(helpserv_option_dict);
4585 dict_delete(helpserv_usercmd_dict);
4586 dict_delete(helpserv_bots_dict);
4587 dict_delete(helpserv_bots_bychan_dict);
4588 dict_delete(helpserv_reqs_bynick_dict);
4589 dict_delete(helpserv_reqs_byhand_dict);
4590 dict_delete(helpserv_users_byhand_dict);
4596 int helpserv_init() {
4597 HS_LOG = log_register_type("HelpServ", "file:helpserv.log");
4598 conf_register_reload(helpserv_conf_read);
4600 helpserv_func_dict = dict_new();
4601 dict_set_free_data(helpserv_func_dict, free);
4602 helpserv_define_func("HELP", cmd_help, HlNone, CMD_NOT_OVERRIDE|CMD_IGNORE_EVENT);
4603 helpserv_define_func("LIST", cmd_list, HlTrial, CMD_NEED_BOT|CMD_IGNORE_EVENT);
4604 helpserv_define_func("NEXT", cmd_next, HlTrial, CMD_NEED_BOT|CMD_NEVER_FROM_OPSERV);
4605 helpserv_define_func("PICKUP", cmd_pickup, HlTrial, CMD_NEED_BOT|CMD_NEVER_FROM_OPSERV);
4606 helpserv_define_func("REASSIGN", cmd_reassign, HlManager, CMD_NEED_BOT|CMD_NEVER_FROM_OPSERV);
4607 helpserv_define_func("CLOSE", cmd_close, HlTrial, CMD_NEED_BOT|CMD_NEVER_FROM_OPSERV);
4608 helpserv_define_func("SHOW", cmd_show, HlTrial, CMD_NEED_BOT|CMD_IGNORE_EVENT);
4609 helpserv_define_func("ADDNOTE", cmd_addnote, HlTrial, CMD_NEED_BOT);
4610 helpserv_define_func("ADDOWNER", cmd_addowner, HlOper, CMD_NEED_BOT|CMD_FROM_OPSERV_ONLY);
4611 helpserv_define_func("DELOWNER", cmd_deluser, HlOper, CMD_NEED_BOT|CMD_FROM_OPSERV_ONLY);
4612 helpserv_define_func("ADDTRIAL", cmd_addtrial, HlManager, CMD_NEED_BOT);
4613 helpserv_define_func("ADDHELPER", cmd_addhelper, HlManager, CMD_NEED_BOT);
4614 helpserv_define_func("ADDMANAGER", cmd_addmanager, HlOwner, CMD_NEED_BOT);
4615 helpserv_define_func("GIVEOWNERSHIP", cmd_giveownership, HlOwner, CMD_NEED_BOT);
4616 helpserv_define_func("DELUSER", cmd_deluser, HlManager, CMD_NEED_BOT);
4617 helpserv_define_func("HELPERS", cmd_helpers, HlNone, CMD_NEED_BOT);
4618 helpserv_define_func("WLIST", cmd_wlist, HlNone, CMD_NEED_BOT);
4619 helpserv_define_func("MLIST", cmd_mlist, HlNone, CMD_NEED_BOT);
4620 helpserv_define_func("HLIST", cmd_hlist, HlNone, CMD_NEED_BOT);
4621 helpserv_define_func("TLIST", cmd_tlist, HlNone, CMD_NEED_BOT);
4622 helpserv_define_func("CLVL", cmd_clvl, HlManager, CMD_NEED_BOT);
4623 helpserv_define_func("PAGE", cmd_page, HlTrial, CMD_NEED_BOT);
4624 helpserv_define_func("SET", cmd_set, HlHelper, CMD_NEED_BOT);
4625 helpserv_define_func("STATS", cmd_stats, HlTrial, CMD_NEED_BOT);
4626 helpserv_define_func("STATSREPORT", cmd_statsreport, HlManager, CMD_NEED_BOT);
4627 helpserv_define_func("UNREGISTER", cmd_unregister, HlOwner, CMD_NEED_BOT);
4628 helpserv_define_func("READHELP", cmd_readhelp, HlOper, CMD_FROM_OPSERV_ONLY);
4629 helpserv_define_func("REGISTER", cmd_register, HlOper, CMD_FROM_OPSERV_ONLY);
4630 helpserv_define_func("MOVE", cmd_move, HlOper, CMD_FROM_OPSERV_ONLY|CMD_NEED_BOT);
4631 helpserv_define_func("BOTS", cmd_bots, HlOper, CMD_FROM_OPSERV_ONLY|CMD_IGNORE_EVENT);
4632 helpserv_define_func("EXPIRE", cmd_expire, HlOper, CMD_FROM_OPSERV_ONLY);
4633 helpserv_define_func("WEEKSTART", cmd_weekstart, HlTrial, CMD_NEED_BOT);
4634 helpserv_define_func("MODSTATS", cmd_modstats, HlOwner, CMD_NEED_BOT);
4636 helpserv_option_dict = dict_new();
4637 helpserv_define_option("PAGETARGET", opt_pagetarget_command);
4638 helpserv_define_option("ALERTPAGETARGET", opt_pagetarget_alert);
4639 helpserv_define_option("STATUSPAGETARGET", opt_pagetarget_status);
4640 helpserv_define_option("PAGE", opt_pagetype);
4641 helpserv_define_option("PAGETYPE", opt_pagetype);
4642 helpserv_define_option("ALERTPAGETYPE", opt_alert_page_type);
4643 helpserv_define_option("STATUSPAGETYPE", opt_status_page_type);
4644 helpserv_define_option("GREETING", opt_greeting);
4645 helpserv_define_option("REQOPENED", opt_req_opened);
4646 helpserv_define_option("REQASSIGNED", opt_req_assigned);
4647 helpserv_define_option("REQCLOSED", opt_req_closed);
4648 helpserv_define_option("IDLEDELAY", opt_idle_delay);
4649 helpserv_define_option("WHINEDELAY", opt_whine_delay);
4650 helpserv_define_option("WHINEINTERVAL", opt_whine_interval);
4651 helpserv_define_option("EMPTYINTERVAL", opt_empty_interval);
4652 helpserv_define_option("STALEDELAY", opt_stale_delay);
4653 helpserv_define_option("REQPERSIST", opt_request_persistence);
4654 helpserv_define_option("HELPERPERSIST", opt_helper_persistence);
4655 helpserv_define_option("NOTIFICATION", opt_notification);
4656 helpserv_define_option("REQMAXLEN", opt_req_maxlen);
4657 helpserv_define_option("IDWRAP", opt_id_wrap);
4658 helpserv_define_option("PRIVMSGONLY", opt_privmsg_only);
4659 helpserv_define_option("REQONJOIN", opt_req_on_join);
4660 helpserv_define_option("AUTOVOICE", opt_auto_voice);
4661 helpserv_define_option("AUTODEVOICE", opt_auto_devoice);
4663 helpserv_usercmd_dict = dict_new();
4664 dict_insert(helpserv_usercmd_dict, "WAIT", usercmd_wait);
4666 helpserv_bots_dict = dict_new();
4667 dict_set_free_data(helpserv_bots_dict, helpserv_free_bot);
4669 helpserv_bots_bychan_dict = dict_new();
4670 dict_set_free_data(helpserv_bots_bychan_dict, helpserv_botlist_free);
4672 helpserv_reqs_bynick_dict = dict_new();
4673 dict_set_free_data(helpserv_reqs_bynick_dict, helpserv_reqlist_free);
4674 helpserv_reqs_byhand_dict = dict_new();
4675 dict_set_free_data(helpserv_reqs_byhand_dict, helpserv_reqlist_free);
4677 helpserv_users_byhand_dict = dict_new();
4678 dict_set_free_data(helpserv_users_byhand_dict, helpserv_userlist_free);
4680 saxdb_register("HelpServ", helpserv_saxdb_read, helpserv_saxdb_write);
4681 helpserv_helpfile_read();
4683 /* Make up for downtime... though this will only really affect the
4685 if (last_stats_update && (helpserv_next_stats(last_stats_update) < now)) {
4686 unsigned long statsrun = last_stats_update;
4687 while ((statsrun = helpserv_next_stats(statsrun)) < now)
4688 helpserv_run_stats(statsrun);
4690 timeq_add(helpserv_next_stats(now), helpserv_timed_run_stats, NULL);
4692 reg_join_func(handle_join);
4693 reg_part_func(handle_part); /* also deals with kick */
4694 reg_nick_change_func(handle_nickchange);
4695 reg_del_user_func(handle_quit);
4697 reg_auth_func(handle_nickserv_auth);
4698 reg_handle_rename_func(handle_nickserv_rename);
4699 reg_unreg_func(handle_nickserv_unreg);
4700 reg_allowauth_func(handle_nickserv_allowauth);
4701 reg_failpw_func(handle_nickserv_failpw);
4702 reg_handle_merge_func(handle_nickserv_merge);
4704 reg_exit_func(helpserv_db_cleanup);
4706 helpserv_module = module_register("helpserv", HS_LOG, HELPSERV_HELPFILE_NAME, helpserv_expand_variable);
4707 modcmd_register(helpserv_module, "helpserv", cmd_helpserv, 1, MODCMD_REQUIRE_AUTHED|MODCMD_NO_LOG|MODCMD_NO_DEFAULT_BIND, "level", "800", NULL);
4708 message_register_table(msgtab);
4713 helpserv_finalize(void) {