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_PUBLIC_CHANNEL "public_channel"
57 #define KEY_PAGE_DEST "page_dest"
58 #define KEY_CMDWORD "cmdword"
59 #define KEY_PERSIST_LENGTH "persist_length"
60 #define KEY_REGISTERED "registered"
61 #define KEY_REGISTRAR "registrar"
62 #define KEY_IDWRAP "id_wrap"
63 #define KEY_REQ_MAXLEN "req_maxlen"
64 #define KEY_LAST_REQUESTID "last_requestid"
65 #define KEY_HELPERS "users"
66 #define KEY_HELPER_LEVEL "level"
67 #define KEY_HELPER_HELPMODE "helpmode"
68 #define KEY_HELPER_WEEKSTART "weekstart"
69 #define KEY_HELPER_STATS "stats"
70 #define KEY_HELPER_STATS_TIME "time"
71 #define KEY_HELPER_STATS_PICKUP "picked_up"
72 #define KEY_HELPER_STATS_CLOSE "closed"
73 #define KEY_HELPER_STATS_REASSIGNFROM "reassign_from"
74 #define KEY_HELPER_STATS_REASSIGNTO "reassign_to"
75 #define KEY_REQUESTS "requests"
76 #define KEY_REQUEST_HELPER "helper"
77 #define KEY_REQUEST_ASSIGNED "assigned"
78 #define KEY_REQUEST_HANDLE "handle"
79 #define KEY_REQUEST_TEXT "text"
80 #define KEY_REQUEST_OPENED "opened"
81 #define KEY_REQUEST_NICK "nick"
82 #define KEY_REQUEST_USERHOST "userhost"
83 #define KEY_REQUEST_CLOSED "closed"
84 #define KEY_REQUEST_CLOSEREASON "closereason"
85 #define KEY_NOTIFICATION "notification"
86 #define KEY_PRIVMSG_ONLY "privmsg_only"
87 #define KEY_REQ_ON_JOIN "req_on_join"
88 #define KEY_AUTO_VOICE "auto_voice"
89 #define KEY_AUTO_JOIN "auto_join"
90 #define KEY_AUTO_DEVOICE "auto_devoice"
91 #define KEY_LAST_ACTIVE "last_active"
94 #define HSFMT_TIME "%a, %d %b %Y %H:%M:%S %Z"
95 static const struct message_entry msgtab[] = {
96 { "HSMSG_READHELP_SUCCESS", "Read HelpServ help database in %lu.%03lu seconds." },
97 { "HSMSG_INVALID_BOT", "This command requires a valid HelpServ bot name." },
98 { "HSMSG_ILLEGAL_CHANNEL", "$b%s$b is an illegal channel; cannot use it." },
99 { "HSMSG_INTERNAL_COMMAND", "$b%s$b appears to be an internal HelpServ command, sorry." },
100 { "HSMSG_NOT_IN_USERLIST", "%s lacks access to $b%s$b." },
101 { "HSMSG_LEVEL_TOO_LOW", "You lack access to this command." },
102 { "HSMSG_OPER_CMD", "This command can only be executed via $O." },
103 { "HSMSG_WTF_WHO_ARE_YOU", "$bUnable to find you on the %s userlist.$b This is a bug. Please report it." },
104 { "HSMSG_NO_USE_OPSERV", "This command cannot be used via $O. If you really need to use it, add yourself to the userlist." },
105 { "HSMSG_OPSERV_NEED_USER", "To use this command via $O, you must supply a user to target." },
106 { "HSMSG_PAGE_REQUEST", "Page from $b%s$b: $b%s$b" },
107 { "HSMSG_BAD_REQ_TYPE", "I don't know how to list $b%s$b requests." },
110 { "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." },
111 { "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." },
113 /* User Management */
114 { "HSMSG_CANNOT_ADD", "You do not have permission to add users." },
115 { "HSMSG_CANNOT_DEL", "You do not have permission to delete users." },
116 { "HSMSG_CANNOT_CLVL", "You do not have permission to modify users' access." },
117 { "HSMSG_NO_SELF_CLVL", "You cannot change your own access." },
118 { "HSMSG_NO_BUMP_ACCESS", "You cannot give users the same or more access than yourself." },
119 { "HSMSG_NO_TRANSFER_SELF", "You cannot give ownership to your own account." },
120 { "HSMSG_ADDED_USER", "Added new $b%s$b %s to the user list." },
121 { "HSMSG_DELETED_USER", "Deleted $b%s$b %s from the user list." },
122 { "HSMSG_USER_EXISTS", "$b%s$b is already on the user list." },
123 { "HSMSG_INVALID_ACCESS", "$b%s$b is an invalid access level." },
124 { "HSMSG_CHANGED_ACCESS", "%s now has $b%s$b access." },
125 { "HSMSG_EXPIRATION_DONE", "%d eligible HelpServ bots have retired." },
126 { "HSMSG_BAD_WEEKDAY", "I do not know which day of the week $b%s$b is." },
127 { "HSMSG_WEEK_STARTS", "$b%s$b's weeks start on $b%s$b." },
128 { "HSMSG_MODSTATS_BAD_FIELD", "The specified field does not exist." },
129 { "HSMSG_MODSTATS_BAD_WEEK", "The specified week is invalid." },
130 { "HSMSG_MODSTATS_NEGATIVE", "This modification would result in a negative value." },
131 { "HSMSG_MODSTATS_SUCCESS", "$b%s$b's stats have been modified successfully." },
134 { "HSMSG_ILLEGAL_NICK", "$b%s$b is an illegal nick; cannot use it." },
135 { "HSMSG_NICK_EXISTS", "The nick %s is in use by someone else." },
136 { "HSMSG_REG_SUCCESS", "%s now has ownership of bot %s." },
137 { "HSMSG_NEED_UNREG_CONFIRM", "To unregister this bot, you must /msg $S unregister CONFIRM" },
138 { "HSMSG_ERROR_ADDING_SERVICE", "Error creating new user $b%s$b." },
141 { "HSMSG_RENAMED", "%s has been renamed to $b%s$b." },
142 { "HSMSG_MOVE_SAME_CHANNEL", "You cannot move %s to the same channel it is on." },
143 { "HSMSG_INVALID_MOVE", "$b%s$b is not a valid nick or channel name." },
144 { "HSMSG_NEED_GIVEOWNERSHIP_CONFIRM", "To transfer ownership of this bot, you must /msg $S giveownership newowner CONFIRM" },
145 { "HSMSG_MULTIPLE_OWNERS", "There is more than one owner of %s; please use other commands to change ownership." },
146 { "HSMSG_NO_TRANSFER_SELF", "You cannot give ownership to your own account." },
147 { "HSMSG_OWNERSHIP_GIVEN", "Ownership of $b%s$b has been transferred to account $b%s$b." },
150 { "HSMSG_INVALID_OPTION", "$b%s$b is not a valid option." },
151 { "HSMSG_QUEUE_OPTIONS", "HelpServ Queue Options:" },
152 { "HSMSG_SET_COMMAND_TYPE", "$bPageType $b %s" },
153 { "HSMSG_SET_ALERT_TYPE", "$bAlertPageType $b %s" },
154 { "HSMSG_SET_STATUS_TYPE", "$bStatusPageType $b %s" },
155 { "HSMSG_SET_COMMAND_TARGET", "$bPageTarget $b %s" },
156 { "HSMSG_SET_ALERT_TARGET", "$bAlertPageTarget $b %s" },
157 { "HSMSG_SET_STATUS_TARGET", "$bStatusPageTarget$b %s" },
158 { "HSMSG_SET_GREETING", "$bGreeting $b %s" },
159 { "HSMSG_SET_REQOPENED", "$bReqOpened $b %s" },
160 { "HSMSG_SET_REQASSIGNED", "$bReqAssigned $b %s" },
161 { "HSMSG_SET_REQCLOSED", "$bReqClosed $b %s" },
162 { "HSMSG_SET_IDLEDELAY", "$bIdleDelay $b %s" },
163 { "HSMSG_SET_WHINEDELAY", "$bWhineDelay $b %s" },
164 { "HSMSG_SET_WHINEINTERVAL", "$bWhineInterval $b %s" },
165 { "HSMSG_SET_EMPTYINTERVAL", "$bEmptyInterval $b %s" },
166 { "HSMSG_SET_STALEDELAY", "$bStaleDelay $b %s" },
167 { "HSMSG_SET_REQPERSIST", "$bReqPersist $b %s" },
168 { "HSMSG_SET_HELPERPERSIST", "$bHelperPersist $b %s" },
169 { "HSMSG_SET_NOTIFICATION", "$bNotification $b %s" },
170 { "HSMSG_SET_IDWRAP", "$bIDWrap $b %d" },
171 { "HSMSG_SET_REQMAXLEN", "$bReqMaxLen $b %d" },
172 { "HSMSG_SET_PRIVMSGONLY", "$bPrivmsgOnly $b %s" },
173 { "HSMSG_SET_REQONJOIN", "$bReqOnJoin $b %s" },
174 { "HSMSG_SET_AUTOVOICE", "$bAutoVoice $b %s" },
175 { "HSMSG_SET_AUTOJOIN", "$bAutoJoin $b %s" },
176 { "HSMSG_SET_AUTODEVOICE", "$bAutoDevoice $b %s" },
177 { "HSMSG_SET_PUBLICCHAN", "$bPublicChan $b %s" },
178 { "HSMSG_PAGE_NOTICE", "notice" },
179 { "HSMSG_PAGE_PRIVMSG", "privmsg" },
180 { "HSMSG_PAGE_ONOTICE", "onotice" },
181 { "HSMSG_LENGTH_PART", "part" },
182 { "HSMSG_LENGTH_QUIT", "quit" },
183 { "HSMSG_LENGTH_CLOSE", "close" },
184 { "HSMSG_NOTIFY_DROP", "ReqDrop" },
185 { "HSMSG_NOTIFY_USERCHANGES", "UserChanges" },
186 { "HSMSG_NOTIFY_ACCOUNTCHANGES", "AccountChanges" },
187 { "HSMSG_INVALID_INTERVAL", "Sorry, %s must be at least %s." },
188 { "HSMSG_0_DISABLED", "0 (Disabled)" },
189 { "HSMSG_NEED_MANAGER", "Only managers or higher can do this." },
190 { "HSMSG_SET_NEED_OPER", "This option can only be set by an oper." },
193 { "HSMSG_REQ_INVALID", "$b%s$b is not a valid request ID, or there are no requests for that nick or account." },
194 { "HSMSG_REQ_NOT_YOURS_ASSIGNED_TO", "Request $b%lu$b is not assigned to you; it is currently assigned to %s." },
195 { "HSMSG_REQ_NOT_YOURS_UNASSIGNED", "Request $b%lu$b is not assigned to you; it is currently unassigned." },
196 { "HSMSG_REQ_FOUNDMANY", "The user you entered had multiple requests. The oldest one is being used." },
197 { "HSMSG_REQ_CLOSED", "Request $b%lu$b has been closed." },
198 { "HSMSG_REQ_NO_UNASSIGNED", "There are no unassigned requests." },
199 { "HSMSG_USERCMD_NO_REQUEST", "You must have an open request to use a user command." },
200 { "HSMSG_USERCMD_UNKNOWN", "I do not know the user command $b%s$b." },
201 { "HSMSG_REQ_YOU_NOT_IN_HELPCHAN_OPEN", "You cannot open this request as you are not in %s." },
202 { "HSMSG_REQ_YOU_NOT_IN_HELPCHAN", "You cannot be assigned this request as you are not in %s." },
203 { "HSMSG_REQ_HIM_NOT_IN_HELPCHAN", "%s cannot be assigned this request as they are not in %s." },
204 { "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." },
205 { "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." },
206 { "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." },
207 { "HSMSG_REQ_ASSIGNED_YOU", "You have been assigned request ID#%lu:" },
208 { "HSMSG_REQ_REASSIGNED", "You have been assigned request ID#%lu (reassigned from %s):" },
209 { "HSMSG_REQ_INFO_1", "Request ID#%lu:" },
210 { "HSMSG_REQ_INFO_2a", " - Nick %s / Account %s" },
211 { "HSMSG_REQ_INFO_2b", " - Nick %s / Not authed" },
212 { "HSMSG_REQ_INFO_2c", " - Online somewhere / Account %s" },
213 { "HSMSG_REQ_INFO_2d", " - Not online / Account %s" },
214 { "HSMSG_REQ_INFO_2e", " - Not online / No account" },
215 { "HSMSG_REQ_INFO_3", " - Opened at %s (%s ago)" },
216 { "HSMSG_REQ_INFO_4", " - Message:" },
217 { "HSMSG_REQ_INFO_MESSAGE", " %s" },
218 { "HSMSG_REQ_ASSIGNED", "Your helper for request ID#%lu is %s (Current nick: %s)" },
219 { "HSMSG_REQ_ASSIGNED_AGAIN", "Your helper for request ID#%lu has been changed to %s (Current nick: %s)" },
220 { "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." },
221 { "HSMSG_REQ_NEW", "Your message has been recorded and assigned request ID#%lu. A helper should contact you shortly." },
222 { "HSMSG_REQ_NEWONJOIN", "Welcome to %s. You have been assigned request ID#%lu. A helper should contact you shortly." },
223 { "HSMSG_REQ_UNHANDLED_TIME", "The oldest unhandled request has been waiting for %s." },
224 { "HSMSG_REQ_NO_UNHANDLED", "There are no other unhandled requests." },
225 { "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." },
226 { "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." },
227 { "HSMSG_REQ_PERSIST_HANDLE", "Everything you tell me until you are helped will be recorded." },
228 { "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." },
229 { "HSMSQ_REQ_TEXT_ADDED", "Message from $b%s:$b %s" },
230 { "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." },
232 /* Messages that are inserted into request text */
233 { "HSMSG_REQMSG_NOTE_ADDED", "Your note for request ID#%lu has been recorded." },
235 /* Automatically generated page messages */
236 { "HSMSG_PAGE_NEW_REQUEST_AUTHED", "New request (ID#%lu) from $b%s$b (Account %s)" },
237 { "HSMSG_PAGE_NEW_REQUEST_UNAUTHED", "New request (ID#%lu) from $b%s$b (Not logged in)" },
238 { "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." },
239 { "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." },
240 { "HSMSG_PAGE_CLOSE_REQUEST_1", "Request ID#%lu from $b%s$b (Account %s) has been closed by %s." },
241 { "HSMSG_PAGE_CLOSE_REQUEST_2", "Request ID#%lu from $b%s$b (Not authed) has been closed by %s." },
242 { "HSMSG_PAGE_CLOSE_REQUEST_3", "Request ID#%lu from an offline user (Account %s) has been closed by %s." },
243 { "HSMSG_PAGE_CLOSE_REQUEST_4", "Request ID#%lu from an offline user (no account) has been closed by %s." },
244 { "HSMSG_PAGE_ASSIGN_REQUEST_1", "Request ID#%lu from $b%s$b (Account %s) has been assigned to %s." },
245 { "HSMSG_PAGE_ASSIGN_REQUEST_2", "Request ID#%lu from $b%s$b (Not authed) has been assigned to %s." },
246 { "HSMSG_PAGE_ASSIGN_REQUEST_3", "Request ID#%lu from an offline user (Account %s) has been assigned to %s." },
247 { "HSMSG_PAGE_ASSIGN_REQUEST_4", "Request ID#%lu from an offline user (no account) has been assigned to %s." },
248 /* The last %s is still an I18N lose. Blame whoever decided to overload it so much. */
249 { "HSMSG_PAGE_HELPER_GONE_1", "Request ID#%lu from $b%s$b (Account %s) $bhas been unassigned$b, as its helper, %s has %s." },
250 { "HSMSG_PAGE_HELPER_GONE_2", "Request ID#%lu from $b%s$b (Not authed) $bhas been unassigned$b, as its helper, %s has %s." },
251 { "HSMSG_PAGE_HELPER_GONE_3", "Request ID#%lu from an offline user (Account %s) $bhas been unassigned$b, as its helper, %s has %s." },
252 { "HSMSG_PAGE_HELPER_GONE_4", "Request ID#%lu from an offline user (No account) $bhas been unassigned$b, as its helper, %s has %s." },
253 { "HSMSG_PAGE_WHINE_HEADER", "$b%u unhandled request(s)$b waiting at least $b%s$b (%u unhandled, %u total)" },
254 { "HSMSG_PAGE_IDLE_HEADER", "$b%u users$b in %s $bidle at least %s$b:" },
255 { "HSMSG_PAGE_EMPTYALERT", "$b%s has no helpers present (%u unhandled request(s))$b" },
256 { "HSMSG_PAGE_ONLYTRIALALERT", "$b%s has no full helpers present (%d trial(s) present; %u unhandled request(s))$b" },
257 { "HSMSG_PAGE_FIRSTEMPTYALERT", "$b%s has no helpers present because %s has left (%u unhandled request(s))$b" },
258 { "HSMSG_PAGE_FIRSTONLYTRIALALERT", "$b%s has no full helpers present because %s has left (%d trial(s) present; %u unhandled request(s))$b" },
259 { "HSMSG_PAGE_EMPTYNOMORE", "%s has joined %s; cancelling the \"no helpers present\" alert" },
261 /* Notification messages */
262 { "HSMSG_NOTIFY_USER_QUIT", "The user for request ID#%lu, $b%s$b, has disconnected." },
263 { "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." },
264 { "HSMSG_NOTIFY_USER_NICK", "The user for request ID#%lu has changed their nick from $b%s$b to $b%s$b." },
265 { "HSMSG_NOTIFY_USER_FOUND", "The user for request ID#%lu is now online using nick $b%s$b." },
266 { "HSMSG_NOTIFY_HAND_RENAME", "The account for request ID#%lu has been renamed from $b%s$b to $b%s$b." },
267 { "HSMSG_NOTIFY_HAND_MOVE", "The account for request ID#%lu has been changed to $b%s$b from $b%s$b." },
268 { "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." },
269 { "HSMSG_NOTIFY_HAND_AUTH", "The user for request ID#%lu, $b%s$b, has authenticated to account $b%s$b." },
270 { "HSMSG_NOTIFY_HAND_UNREG", "The account for request ID#%lu, $b%s$b, has been unregistered by $b%s$b." },
271 { "HSMSG_NOTIFY_HAND_MERGE", "The account for request ID#%lu, $b%s$b, has been merged with $b%s$b by %s." },
272 { "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." },
273 { "HSMSG_NOTIFY_UNALLOWAUTH", "The user for request ID#%lu, $b%s$b, has had their ability to authenticate without hostmask checking revoked by %s." },
274 { "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." },
275 { "HSMSG_NOTIFY_REQ_DROP_PART", "Request ID#%lu has been $bdropped$b because %s left the help channel." },
276 { "HSMSG_NOTIFY_REQ_DROP_QUIT", "Request ID#%lu has been $bdropped$b because %s quit IRC." },
277 { "HSMSG_NOTIFY_REQ_DROP_UNREGGED", "Request ID#%lu (account %s) has been $bdropped$b because the account was unregistered." },
279 /* Presence and request-related messages */
280 { "HSMSG_REQ_DROPPED_PART", "You have left $b%s$b. Your help request (ID#%lu) has been deleted." },
281 { "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." },
282 { "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." },
283 { "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." },
284 { "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." },
285 { "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." },
288 { "HSMSG_BOTLIST_HEADER", "$bCurrent HelpServ bots:$b" },
289 { "HSMSG_USERLIST_HEADER", "$b%s users:$b" },
290 { "HSMSG_USERLIST_ZOOT_LVL", "%s $b%ss$b:" },
291 { "HSMSG_REQLIST_AUTH", "You are currently assigned these requests:" },
292 { "HSMSG_REQ_LIST_TOP_UNASSIGNED", "Listing $ball unassigned$b requests (%d in list)." },
293 { "HSMSG_REQ_LIST_TOP_ASSIGNED", "Listing $ball assigned$b requests (%d in list)." },
294 { "HSMSG_REQ_LIST_TOP_YOUR", "Listing $byour$b requests (%d in list)." },
295 { "HSMSG_REQ_LIST_TOP_ALL", "Listing $ball$b requests (%d in list)." },
296 { "HSMSG_REQ_LIST_NONE", "There are no matching requests." },
297 { "HSMSG_STATS_TOP", "Stats for %s user $b%s$b (week starts %s):" },
298 { "HSMSG_STATS_TIME", "$uTime spent helping in %s:$u" },
299 { "HSMSG_STATS_REQS", "$uRequest activity statistics:$u" },
301 /* Status report headers */
302 { "HSMSG_STATS_REPORT_0", "Stats report for current week" },
303 { "HSMSG_STATS_REPORT_1", "Stats report for one week ago" },
304 { "HSMSG_STATS_REPORT_2", "Stats report for two weeks ago" },
305 { "HSMSG_STATS_REPORT_3", "Stats report for three weeks ago" },
307 /* Responses to user commands */
308 { "HSMSG_YOU_BEING_HELPED", "You are already being helped." },
309 { "HSMSG_YOU_BEING_HELPED_BY", "You are already being helped by $b%s$b." },
310 { "HSMSG_WAIT_STATUS", "You are %d of %d in line; the first person has waited %s." },
314 enum helpserv_level {
324 static const char *helpserv_level_names[] = {
342 static const struct {
347 { "none", "MSG_NONE", NULL },
348 { "notice", "HSMSG_PAGE_NOTICE", irc_notice },
349 { "privmsg", "HSMSG_PAGE_PRIVMSG", irc_privmsg },
350 { "onotice", "HSMSG_PAGE_ONOTICE", irc_wallchops },
361 static const struct {
366 { "command", "HSMSG_SET_COMMAND_TARGET", "HSMSG_SET_COMMAND_TYPE" },
367 { "alert", "HSMSG_SET_ALERT_TARGET", "HSMSG_SET_ALERT_TYPE" },
368 { "status", "HSMSG_SET_STATUS_TARGET", "HSMSG_SET_STATUS_TYPE" },
375 MSGTYPE_REQ_ASSIGNED,
381 static const struct {
384 } message_types[] = {
385 { "greeting", "HSMSG_SET_GREETING" },
386 { "reqopened", "HSMSG_SET_REQOPENED" },
387 { "reqassigned", "HSMSG_SET_REQASSIGNED" },
388 { "reqclosed", "HSMSG_SET_REQCLOSED" },
389 { "reqdropped", "HSMSG_SET_REQDROPPED" },
395 INTERVAL_WHINE_DELAY,
396 INTERVAL_WHINE_INTERVAL,
397 INTERVAL_EMPTY_INTERVAL,
398 INTERVAL_STALE_DELAY,
402 static const struct {
405 } interval_types[] = {
406 { "idledelay", "HSMSG_SET_IDLEDELAY" },
407 { "whinedelay", "HSMSG_SET_WHINEDELAY" },
408 { "whineinterval", "HSMSG_SET_WHINEINTERVAL" },
409 { "emptyinterval", "HSMSG_SET_EMPTYINTERVAL" },
410 { "staledelay", "HSMSG_SET_STALEDELAY" },
414 enum persistence_type {
420 static const struct {
423 } persistence_types[] = {
424 { "reqpersist", "HSMSG_SET_REQPERSIST" },
425 { "helperpersist", "HSMSG_SET_HELPERPERSIST" },
429 enum persistence_length {
436 static const struct {
439 } persistence_lengths[] = {
440 { "part", "HSMSG_LENGTH_PART" },
441 { "quit", "HSMSG_LENGTH_QUIT" },
442 { "close", "HSMSG_LENGTH_CLOSE" },
446 enum notification_type {
454 static const struct {
457 } notification_types[] = {
458 { "none", "MSG_NONE" },
459 { "reqdrop", "HSMSG_NOTIFY_DROP" },
460 { "userchanges", "HSMSG_NOTIFY_USERCHANGES" },
461 { "accountchanges", "HSMSG_NOTIFY_ACCOUNTCHANGES" },
465 static const char *weekday_names[] = {
476 static const char *statsreport_week[] = {
477 "HSMSG_STATS_REPORT_0",
478 "HSMSG_STATS_REPORT_1",
479 "HSMSG_STATS_REPORT_2",
480 "HSMSG_STATS_REPORT_3"
484 const char *description;
485 const char *reqlogfile;
486 unsigned long db_backup_frequency;
487 unsigned int expire_age;
488 unsigned long modstats_level;
492 static unsigned long last_stats_update;
493 static int shutting_down;
494 static FILE *reqlog_f;
495 static struct log_type *HS_LOG;
497 #define CMD_NEED_BOT 0x001
498 #define CMD_NOT_OVERRIDE 0x002
499 #define CMD_FROM_OPSERV_ONLY 0x004
500 #define CMD_IGNORE_EVENT 0x008
501 #define CMD_NEVER_FROM_OPSERV 0x010
503 struct helpserv_bot {
504 struct userNode *helpserv;
506 struct chanNode *helpchan;
507 struct chanNode *publicchan;
509 struct chanNode *page_targets[PGSRC_COUNT];
510 enum page_type page_types[PGSRC_COUNT];
511 char *messages[MSGTYPE_COUNT];
512 unsigned long intervals[INTERVAL_COUNT];
513 enum notification_type notify;
515 /* This is a default; it can be changed on a per-request basis */
516 enum persistence_length persist_lengths[PERSIST_T_COUNT];
518 dict_t users; /* indexed by handle */
520 struct helpserv_request *unhandled; /* linked list of unhandled requests */
521 dict_t requests; /* indexed by request id */
522 unsigned long last_requestid;
523 unsigned long id_wrap;
524 unsigned long req_maxlen; /* Maxmimum request length in lines */
526 unsigned int privmsg_only : 1;
527 unsigned int req_on_join : 1;
528 unsigned int auto_voice : 1;
529 unsigned int auto_join : 1;
530 unsigned int auto_devoice : 1;
532 unsigned int helpchan_empty : 1;
534 unsigned long registered;
535 unsigned long last_active;
539 struct helpserv_user {
540 struct handle_info *handle;
541 struct helpserv_bot *hs;
542 unsigned int help_mode : 1;
543 unsigned int week_start : 3;
544 enum helpserv_level level;
546 unsigned long join_time; /* when they joined, or 0 if not in channel */
547 /* [0] through [3] are n weeks ago, [4] is the total of everything before that */
548 unsigned int time_per_week[5]; /* how long they've were in the channel the past 4 weeks */
549 unsigned int picked_up[5]; /* how many requests they have picked up */
550 unsigned int closed[5]; /* how many requests they have closed */
551 unsigned int reassigned_from[5]; /* how many requests reassigned from them to others */
552 unsigned int reassigned_to[5]; /* how many requests reassigned from others to them */
555 struct helpserv_request {
556 struct helpserv_user *helper;
557 struct helpserv_bot *hs;
558 struct string_list *text;
559 struct helpserv_request *next_unhandled;
561 struct helpserv_reqlist *parent_nick_list;
562 struct helpserv_reqlist *parent_hand_list;
564 /* One, but not both, of "user" and "handle" may be NULL,
565 * depending on what we know about the user.
567 * If persist == PERSIST_CLOSE when the user quits, then it
568 * switches to handle instead of user... and stays that way (it's
569 * possible to have >1 nick per handle, so you can't really decide
570 * to reassign a userNode to it unless they send another message
573 struct userNode *user;
574 struct handle_info *handle;
577 unsigned long opened;
578 unsigned long assigned;
579 unsigned long updated;
582 #define DEFINE_LIST_ALLOC(STRUCTNAME) \
583 struct STRUCTNAME * STRUCTNAME##_alloc() {\
584 struct STRUCTNAME *newlist; \
585 newlist = malloc(sizeof(struct STRUCTNAME)); \
586 STRUCTNAME##_init(newlist); \
589 void STRUCTNAME##_free(void *data) {\
590 struct STRUCTNAME *list = data; /* void * to let dict_set_free_data use it */ \
591 STRUCTNAME##_clean(list); \
595 DECLARE_LIST(helpserv_botlist, struct helpserv_bot *);
596 DEFINE_LIST(helpserv_botlist, struct helpserv_bot *)
597 DEFINE_LIST_ALLOC(helpserv_botlist)
599 DECLARE_LIST(helpserv_reqlist, struct helpserv_request *);
600 DEFINE_LIST(helpserv_reqlist, struct helpserv_request *)
601 DEFINE_LIST_ALLOC(helpserv_reqlist)
603 DECLARE_LIST(helpserv_userlist, struct helpserv_user *);
604 DEFINE_LIST(helpserv_userlist, struct helpserv_user *)
605 DEFINE_LIST_ALLOC(helpserv_userlist)
607 struct helpfile *helpserv_helpfile;
608 static struct module *helpserv_module;
609 static dict_t helpserv_func_dict;
610 static dict_t helpserv_usercmd_dict; /* contains helpserv_usercmd_t */
611 static dict_t helpserv_option_dict;
612 static dict_t helpserv_bots_dict; /* indexed by nick */
613 static dict_t helpserv_bots_bychan_dict; /* indexed by chan, holds a struct helpserv_botlist */
614 /* QUESTION: why are these outside of any helpserv_bot struct? */
615 static dict_t helpserv_reqs_bynick_dict; /* indexed by nick, holds a struct helpserv_reqlist */
616 static dict_t helpserv_reqs_byhand_dict; /* indexed by handle, holds a struct helpserv_reqlist */
617 static dict_t helpserv_users_byhand_dict; /* indexed by handle, holds a struct helpserv_userlist */
619 /* This is so that overrides can "speak" from opserv */
620 extern struct userNode *opserv;
622 #define HELPSERV_SYNTAX() helpserv_help(hs, from_opserv, user, argv[0])
623 #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[]))
624 typedef HELPSERV_FUNC(helpserv_func_t);
625 #define HELPSERV_USERCMD(NAME) void NAME(struct helpserv_request *req, UNUSED_ARG(struct userNode *likely_helper), UNUSED_ARG(char *args))
626 typedef HELPSERV_USERCMD(helpserv_usercmd_t);
627 #define HELPSERV_OPTION(NAME) HELPSERV_FUNC(NAME)
628 typedef HELPSERV_OPTION(helpserv_option_func_t);
630 static HELPSERV_FUNC(cmd_help);
632 #define REQUIRE_PARMS(N) if (argc < N) { \
633 helpserv_notice(user, "MSG_MISSING_PARAMS", argv[0]); \
637 /* For messages going to users being helped */
638 #if defined(GCC_VARMACROS)
639 # define helpserv_msguser(target, ARGS...) send_message_type((from_opserv ? 0 : hs->privmsg_only), (target), (from_opserv ? opserv : hs->helpserv), ARGS)
640 # define helpserv_user_reply(ARGS...) send_message_type(req->hs->privmsg_only, req->user, req->hs->helpserv, ARGS)
641 /* For messages going to helpers */
642 # define helpserv_notice(target, ARGS...) send_message((target), (from_opserv ? opserv : hs->helpserv), ARGS)
643 # define helpserv_notify(helper, ARGS...) do { struct userNode *_target; for (_target = (helper)->handle->users; _target; _target = _target->next_authed) { \
644 if(!_target->next_authed || GetUserMode(helper->hs->helpchan, _target)) {\
645 send_message(_target, (helper)->hs->helpserv, ARGS); \
649 # define helpserv_page(TYPE, ARGS...) do { \
650 int msg_type=0; struct chanNode *target=helpserv_get_page_type(hs, (TYPE), &msg_type); \
651 if (target) send_target_message(msg_type, target->name, hs->helpserv, ARGS); \
653 #elif defined(C99_VARMACROS)
654 # define helpserv_msguser(target, ...) send_message_type((from_opserv ? 0 : hs->privmsg_only), (target), (from_opserv ? opserv : hs->helpserv), __VA_ARGS__)
655 # define helpserv_user_reply(...) send_message_type(req->hs->privmsg_only, req->user, req->hs->helpserv, __VA_ARGS__)
656 /* For messages going to helpers */
657 # define helpserv_notice(target, ...) send_message((target), (from_opserv ? opserv : hs->helpserv), __VA_ARGS__)
658 # define helpserv_notify(helper, ...) do { struct userNode *_target; for (_target = (helper)->handle->users; _target; _target = _target->next_authed) { \
659 if(!_target->next_authed || GetUserMode(helper->hs->helpchan, _target)) {\
660 send_message(_target, (helper)->hs->helpserv, __VA_ARGS__); \
664 # define helpserv_page(TYPE, ...) do { \
665 int msg_type=0; struct chanNode *target=helpserv_get_page_type(hs, (TYPE), &msg_type); \
666 if (target) send_target_message(msg_type, target->name, hs->helpserv, __VA_ARGS__); \
669 #define helpserv_message(hs, target, id) do { if ((hs)->messages[id]) { \
671 send_message_type(4, (target), opserv, "%s", (hs)->messages[id]); \
673 send_message_type(4 | hs->privmsg_only, (target), hs->helpserv, "%s", (hs)->messages[id]); \
675 #define helpserv_get_handle_info(user, text) smart_get_handle_info((from_opserv ? opserv : hs->helpserv) , (user), (text))
677 struct helpserv_cmd {
678 enum helpserv_level access;
679 helpserv_func_t *func;
684 static void run_empty_interval(void *data);
686 static void helpserv_interval(char *output, unsigned long interval) {
687 int num_hours = interval / 3600;
688 int num_minutes = (interval % 3600) / 60;
689 sprintf(output, "%u hour%s, %u minute%s", num_hours, num_hours == 1 ? "" : "s", num_minutes, num_minutes == 1 ? "" : "s");
692 static const char * helpserv_level2str(enum helpserv_level level) {
693 if (level <= HlOper) {
694 return helpserv_level_names[level];
696 log_module(HS_LOG, LOG_ERROR, "helpserv_level2str receieved invalid level %d.", level);
701 static enum helpserv_level helpserv_str2level(const char *msg) {
702 enum helpserv_level nn;
703 for (nn=HlNone; nn<=HlOper; nn++) {
704 if (!irccasecmp(msg, helpserv_level_names[nn]))
707 log_module(HS_LOG, LOG_ERROR, "helpserv_str2level received invalid level %s.", msg);
708 return HlNone; /* Error */
711 static struct helpserv_user *GetHSUser(struct helpserv_bot *hs, struct handle_info *hi) {
712 return dict_find(hs->users, hi->handle, NULL);
715 static void helpserv_log_request(struct helpserv_request *req, const char *reason) {
716 char key[27+NICKLEN];
717 char userhost[USERLEN+HOSTLEN+2];
718 struct saxdb_context *ctx;
722 assert(reason != NULL);
723 if (!reqlog_f || !(ctx = saxdb_open_context(reqlog_f)))
725 sprintf(key, "%s-%lu-%lu", req->hs->helpserv->nick, (unsigned long)req->opened, req->id);
726 if ((res = setjmp(*saxdb_jmp_buf(ctx))) != 0) {
727 log_module(HS_LOG, LOG_ERROR, "Unable to log helpserv request: %s.", strerror(res));
729 saxdb_start_record(ctx, key, 1);
731 saxdb_write_string(ctx, KEY_REQUEST_HELPER, req->helper->handle->handle);
732 saxdb_write_int(ctx, KEY_REQUEST_ASSIGNED, req->assigned);
735 saxdb_write_string(ctx, KEY_REQUEST_HANDLE, req->handle->handle);
738 saxdb_write_string(ctx, KEY_REQUEST_NICK, req->user->nick);
739 sprintf(userhost, "%s@%s", req->user->ident, req->user->hostname);
740 saxdb_write_string(ctx, KEY_REQUEST_USERHOST, userhost);
742 saxdb_write_int(ctx, KEY_REQUEST_OPENED, req->opened);
743 saxdb_write_int(ctx, KEY_REQUEST_CLOSED, now);
744 saxdb_write_string(ctx, KEY_REQUEST_CLOSEREASON, reason);
745 saxdb_write_string_list(ctx, KEY_REQUEST_TEXT, req->text);
746 saxdb_end_record(ctx);
747 saxdb_close_context(ctx, 0);
751 static struct chanNode *helpserv_get_page_type(struct helpserv_bot *hs, enum page_source type, int *msg_type)
753 switch (hs->page_types[type]) {
764 log_module(HS_LOG, LOG_ERROR, "helpserv_page() called but %s has an invalid page type %d.", hs->helpserv->nick, type);
765 /* and fall through */
769 return hs->page_targets[type];
772 /* Searches for a request by number, nick, or account (num|nick|*account).
773 * As there can potentially be >1 match, it takes a reqlist. The return
774 * value is the "best" request found (explained in the comment block below).
776 * If num_results is not NULL, it is set to the number of potentially matching
778 * If hs_user is not NULL, requests assigned to this helper will be given
779 * preference (oldest assigned, falling back to oldest if there are none).
781 static struct helpserv_request * smart_get_request(struct helpserv_bot *hs, struct helpserv_user *hs_user, const char *needle, int *num_results) {
782 struct helpserv_reqlist *reqlist, resultlist;
783 struct helpserv_request *req, *oldest=NULL, *oldest_assigned=NULL;
784 struct userNode *user;
790 if (*needle == '*') {
791 /* This test (handle) requires the least processing, so it's first */
792 if (!(reqlist = dict_find(helpserv_reqs_byhand_dict, needle+1, NULL)))
794 helpserv_reqlist_init(&resultlist);
795 for (i=0; i < reqlist->used; i++) {
796 req = reqlist->list[i];
798 helpserv_reqlist_append(&resultlist, req);
803 } else if (!needle[strspn(needle, "0123456789")]) {
804 /* The string is 100% numeric - a request id */
805 if (!(req = dict_find(hs->requests, needle, NULL)))
810 } else if ((user = GetUserH(needle))) {
811 /* And finally, search by nick */
812 if (!(reqlist = dict_find(helpserv_reqs_bynick_dict, needle, NULL)))
814 helpserv_reqlist_init(&resultlist);
816 for (i=0; i < reqlist->used; i++) {
817 req = reqlist->list[i];
819 helpserv_reqlist_append(&resultlist, req);
824 /* If the nick didn't have anything, try their handle */
825 if (!resultlist.used && user->handle_info) {
826 char star_handle[NICKSERV_HANDLE_LEN+2];
828 helpserv_reqlist_clean(&resultlist);
829 sprintf(star_handle, "*%s", user->handle_info->handle);
831 return smart_get_request(hs, hs_user, star_handle, num_results);
837 if (resultlist.used == 0) {
838 helpserv_reqlist_clean(&resultlist);
840 } else if (resultlist.used == 1) {
841 req = resultlist.list[0];
842 helpserv_reqlist_clean(&resultlist);
846 /* In case there is >1 request returned, use the oldest one assigned to
847 * the helper executing the command. Otherwise, use the oldest request.
848 * This may not be the intended result for cmd_pickup (first unhandled
849 * request may be better), or cmd_reassign (first handled request), but
850 * it's close enough, and there really aren't supposed to be multiple
851 * requests per person anyway; they're just side effects of authing. */
853 for (i=0; i < resultlist.used; i++) {
854 req = resultlist.list[i];
855 if (!oldest || req->opened < oldest->opened)
857 if (hs_user && (!oldest_assigned || (req->helper == hs_user && req->opened < oldest_assigned->opened)))
858 oldest_assigned = req;
861 helpserv_reqlist_clean(&resultlist);
863 return oldest_assigned ? oldest_assigned : oldest;
866 static struct helpserv_request * create_request(struct userNode *user, struct helpserv_bot *hs, int from_join) {
867 struct helpserv_request *req = calloc(1, sizeof(struct helpserv_request));
868 char lbuf[3][MAX_LINE_SIZE], req_id[INTERVALLEN];
869 struct helpserv_reqlist *reqlist, *hand_reqlist;
870 const unsigned int from_opserv = 0;
875 req->id = ++hs->last_requestid;
876 sprintf(req_id, "%lu", req->id);
877 dict_insert(hs->requests, strdup(req_id), req);
882 if (hs->last_requestid < hs->id_wrap) {
883 for (i=hs->last_requestid; i < hs->id_wrap; i++) {
884 sprintf(buf, "%lu", i);
885 if (!dict_find(hs->requests, buf, NULL)) {
886 hs->last_requestid = i-1;
891 if (hs->last_requestid >= hs->id_wrap) {
892 for (i=1; i < hs->id_wrap; i++) {
893 sprintf(buf, "%lu", i);
894 if (!dict_find(hs->requests, buf, NULL)) {
895 hs->last_requestid = i-1;
899 if (i >= hs->id_wrap) {
900 log_module(HS_LOG, LOG_INFO, "%s has more requests than its id_wrap.", hs->helpserv->nick);
907 req->text = alloc_string_list(4);
909 req->handle = user->handle_info;
910 if (from_join && self->burst) {
911 extern unsigned long burst_begin;
912 /* We need to keep all the requests during a burst join together,
913 * even if the burst takes more than 1 second. ircu seems to burst
914 * in reverse-join order. */
915 req->opened = burst_begin;
921 if (!hs->unhandled) {
923 req->next_unhandled = NULL;
924 } else if (self->burst && hs->unhandled->opened >= req->opened) {
925 req->next_unhandled = hs->unhandled;
927 } else if (self->burst) {
928 struct helpserv_request *unh;
929 /* Add the request to the beginning of the set of requests with
930 * req->opened having the same value. This makes reqonjoin create
931 * requests in the correct order while bursting. Note that this
932 * does not correct request ids, so they will be in reverse order
933 * though "/msg botname next" will work properly. */
934 for (unh = hs->unhandled; unh->next_unhandled && unh->next_unhandled->opened < req->opened; unh = unh->next_unhandled) ;
935 req->next_unhandled = unh->next_unhandled;
936 unh->next_unhandled = req;
938 struct helpserv_request *unh;
940 for (unh = hs->unhandled; unh->next_unhandled; unh = unh->next_unhandled) ;
941 req->next_unhandled = NULL;
942 unh->next_unhandled = req;
945 if (!(reqlist = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL))) {
946 reqlist = helpserv_reqlist_alloc();
947 dict_insert(helpserv_reqs_bynick_dict, user->nick, reqlist);
949 req->parent_nick_list = reqlist;
950 helpserv_reqlist_append(reqlist, req);
952 if (user->handle_info) {
953 if (!(hand_reqlist = dict_find(helpserv_reqs_byhand_dict, user->handle_info->handle, NULL))) {
954 hand_reqlist = helpserv_reqlist_alloc();
955 dict_insert(helpserv_reqs_byhand_dict, user->handle_info->handle, hand_reqlist);
957 req->parent_hand_list = hand_reqlist;
958 helpserv_reqlist_append(hand_reqlist, req);
960 req->parent_hand_list = NULL;
964 fmt = user_find_message(user, "HSMSG_REQ_NEWONJOIN");
965 sprintf(lbuf[0], fmt, hs->helpchan->name, req->id);
967 fmt = user_find_message(user, "HSMSG_REQ_NEW");
968 sprintf(lbuf[0], fmt, req->id);
970 if (req != hs->unhandled) {
971 intervalString(req_id, now - hs->unhandled->opened, user->handle_info);
972 fmt = user_find_message(user, "HSMSG_REQ_UNHANDLED_TIME");
973 sprintf(lbuf[1], fmt, req_id);
975 fmt = user_find_message(user, "HSMSG_REQ_NO_UNHANDLED");
976 sprintf(lbuf[1], "%s", fmt);
978 switch (hs->persist_lengths[PERSIST_T_REQUEST]) {
980 fmt = user_find_message(user, "HSMSG_REQ_PERSIST_PART");
981 sprintf(lbuf[2], fmt, hs->helpchan->name, hs->helpchan->name);
984 fmt = user_find_message(user, "HSMSG_REQ_PERSIST_QUIT");
985 sprintf(lbuf[2], "%s", fmt);
988 log_module(HS_LOG, LOG_ERROR, "%s has an invalid req_persist.", hs->helpserv->nick);
990 if (user->handle_info) {
991 fmt = user_find_message(user, "HSMSG_REQ_PERSIST_HANDLE");
993 fmt = user_find_message(user, "HSMSG_REQ_PERSIST_QUIT");
995 sprintf(lbuf[2], "%s", fmt);
998 helpserv_message(hs, user, MSGTYPE_REQ_OPENED);
1000 send_message_type(4, user, opserv, "%s %s %s", lbuf[0], lbuf[1], lbuf[2]);
1002 send_message_type(4, user, hs->helpserv, "%s %s %s", lbuf[0], lbuf[1], lbuf[2]);
1004 if (hs->req_on_join && req == hs->unhandled && hs->helpchan_empty && !user->uplink->burst) {
1005 timeq_del(0, run_empty_interval, hs, TIMEQ_IGNORE_WHEN);
1006 run_empty_interval(hs);
1012 /* Handle a message from a user to a HelpServ bot. */
1013 static void helpserv_usermsg(struct userNode *user, struct helpserv_bot *hs, const char *text) {
1014 const int from_opserv = 0; /* for helpserv_notice */
1015 struct helpserv_request *req=NULL, *newest=NULL;
1016 struct helpserv_reqlist *reqlist, *hand_reqlist;
1019 if ((reqlist = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL))) {
1020 for (i=0; i < reqlist->used; i++) {
1021 req = reqlist->list[i];
1024 if (!newest || (newest->opened < req->opened))
1028 /* If nothing was found, this will set req to NULL */
1032 if (user->handle_info) {
1033 hand_reqlist = dict_find(helpserv_reqs_byhand_dict, user->handle_info->handle, NULL);
1034 if (!req && hand_reqlist) {
1035 /* Most recent first again */
1036 for (i=0; i < hand_reqlist->used; i++) {
1037 req = hand_reqlist->list[i];
1038 if ((req->hs != hs) || req->user)
1040 if (!newest || (newest->opened < req->opened))
1048 reqlist = helpserv_reqlist_alloc();
1049 dict_insert(helpserv_reqs_bynick_dict, user->nick, reqlist);
1051 req->parent_nick_list = reqlist;
1052 helpserv_reqlist_append(reqlist, req);
1054 if (req->helper && (hs->notify >= NOTIFY_USER))
1055 helpserv_notify(req->helper, "HSMSG_NOTIFY_USER_FOUND", req->id, user->nick);
1057 helpserv_msguser(user, "HSMSG_GREET_PRIVMSG_EXISTREQ", req->id);
1061 hand_reqlist = NULL;
1065 if (text[0] == helpserv_conf.user_escape) {
1066 helpserv_msguser(user, "HSMSG_USERCMD_NO_REQUEST");
1069 if ((hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_PART) && !GetUserMode(hs->helpchan, user) && (!hs->publicchan || (hs->publicchan && !GetUserMode(hs->publicchan, user)))) {
1071 helpserv_msguser(user, "HSMSG_REQ_YOU_NOT_IN_HELPCHAN_OPEN", hs->publicchan->name);
1073 helpserv_msguser(user, "HSMSG_REQ_YOU_NOT_IN_HELPCHAN_OPEN", hs->helpchan->name);
1077 req = create_request(user, hs, 0);
1078 if (user->handle_info)
1079 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_NEW_REQUEST_AUTHED", req->id, user->nick, user->handle_info->handle);
1081 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_NEW_REQUEST_UNAUTHED", req->id, user->nick);
1082 } else if (text[0] == helpserv_conf.user_escape) {
1083 char cmdname[MAXLEN], *space;
1084 helpserv_usercmd_t *usercmd;
1085 struct userNode *likely_helper;
1087 /* Find somebody likely to be the helper */
1089 likely_helper = NULL;
1090 else if ((likely_helper = req->helper->handle->users) && !likely_helper->next_authed) {
1091 /* only one user it could be :> */
1092 } else for (likely_helper = req->helper->handle->users; likely_helper; likely_helper = likely_helper->next_authed)
1093 if (GetUserMode(hs->helpchan, likely_helper))
1096 /* Parse out command name */
1097 space = strchr(text+1, ' ');
1099 strncpy(cmdname, text+1, space-text-1);
1101 strcpy(cmdname, text+1);
1103 /* Call the user command function */
1104 usercmd = dict_find(helpserv_usercmd_dict, cmdname, NULL);
1106 usercmd(req, likely_helper, space+1);
1108 helpserv_msguser(user, "HSMSG_USERCMD_UNKNOWN", cmdname);
1110 } else if (hs->intervals[INTERVAL_STALE_DELAY]
1111 && (req->updated < now - hs->intervals[INTERVAL_STALE_DELAY])
1112 && (!hs->req_maxlen || req->text->used < hs->req_maxlen)) {
1113 char buf[MAX_LINE_SIZE], updatestr[INTERVALLEN], timestr[MAX_LINE_SIZE];
1117 strftime(timestr, MAX_LINE_SIZE, HSFMT_TIME, localtime(&feh));
1118 intervalString(updatestr, now - req->updated, user->handle_info);
1119 if (req->helper && (hs->notify >= NOTIFY_USER))
1120 if (user->handle_info)
1121 helpserv_notify(req->helper, "HSMSG_PAGE_UPD_REQUEST_AUTHED", req->id, user->nick, user->handle_info->handle, timestr, updatestr);
1123 helpserv_notify(req->helper, "HSMSG_PAGE_UPD_REQUEST_NOT_AUTHED", req->id, user->nick, timestr, updatestr);
1125 if (user->handle_info)
1126 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_UPD_REQUEST_AUTHED", req->id, user->nick, user->handle_info->handle, timestr, updatestr);
1128 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_UPD_REQUEST_NOT_AUTHED", req->id, user->nick, timestr, updatestr);
1130 strftime(timestr, MAX_LINE_SIZE, HSFMT_TIME, localtime(&feh));
1131 snprintf(buf, MAX_LINE_SIZE, "[Stale request updated at %s]", timestr);
1132 string_list_append(req->text, strdup(buf));
1136 if (!hs->req_maxlen || req->text->used < hs->req_maxlen) {
1137 struct userNode *likely_helper;
1139 string_list_append(req->text, strdup(text));
1140 /* Find somebody likely to be the helper */
1142 likely_helper = NULL;
1143 else if ((likely_helper = req->helper->handle->users) && !likely_helper->next_authed) {
1144 /* only one user it could be :> */
1145 } else for (likely_helper = req->helper->handle->users; likely_helper; likely_helper = likely_helper->next_authed)
1146 if (GetUserMode(hs->helpchan, likely_helper))
1150 send_target_message(1, likely_helper->nick, hs->helpserv, "HSMSQ_REQ_TEXT_ADDED", user->nick, text);
1153 helpserv_msguser(user, "HSMSG_REQ_MAXLEN");
1156 /* Handle messages direct to a HelpServ bot. */
1157 static void helpserv_botmsg(struct userNode *user, struct userNode *target, const char *text, UNUSED_ARG(int server_qualified)) {
1158 struct helpserv_bot *hs;
1159 struct helpserv_cmd *cmd;
1160 struct helpserv_user *hs_user;
1161 char *argv[MAXNUMPARAMS];
1162 char tmpline[MAXLEN];
1163 int argc, argv_shift;
1164 const int from_opserv = 0; /* for helpserv_notice */
1166 /* Ignore things consisting of empty lines or from ourselves */
1167 if (!*text || IsLocal(user))
1170 hs = dict_find(helpserv_bots_dict, target->nick, NULL);
1172 /* See if we should listen to their message as a command (helper)
1173 * or a help request (user) */
1174 if (!user->handle_info || !(hs_user = dict_find(hs->users, user->handle_info->handle, NULL))) {
1175 helpserv_usermsg(user, hs, text);
1180 safestrncpy(tmpline, text, sizeof(tmpline));
1181 argc = split_line(tmpline, false, ArrayLength(argv)-argv_shift, argv+argv_shift);
1185 cmd = dict_find(helpserv_func_dict, argv[argv_shift], NULL);
1187 helpserv_notice(user, "MSG_COMMAND_UNKNOWN", argv[argv_shift]);
1190 if (cmd->flags & CMD_FROM_OPSERV_ONLY) {
1191 helpserv_notice(user, "HSMSG_OPER_CMD");
1194 if (cmd->access > hs_user->level) {
1195 helpserv_notice(user, "HSMSG_LEVEL_TOO_LOW");
1199 helpserv_notice(user, "HSMSG_INTERNAL_COMMAND", argv[argv_shift]);
1200 } else if (cmd->func(user, hs, 0, argc, argv+argv_shift)) {
1201 unsplit_string(argv+argv_shift, argc, tmpline);
1202 log_audit(HS_LOG, LOG_COMMAND, user, hs->helpserv, hs->helpchan->name, 0, tmpline);
1206 /* Handle a control command from an IRC operator */
1207 static MODCMD_FUNC(cmd_helpserv) {
1208 struct helpserv_bot *hs = NULL;
1209 struct helpserv_cmd *subcmd;
1210 const int from_opserv = 1; /* for helpserv_notice */
1211 char botnick[NICKLEN+1]; /* in case command is unregister */
1215 send_help(user, opserv, helpserv_helpfile, NULL);
1219 if (!(subcmd = dict_find(helpserv_func_dict, argv[1], NULL))) {
1220 helpserv_notice(user, "MSG_COMMAND_UNKNOWN", argv[1]);
1224 if (!subcmd->func) {
1225 helpserv_notice(user, "HSMSG_INTERNAL_COMMAND", argv[1]);
1229 if ((subcmd->flags & CMD_NEED_BOT) && ((argc < 3) || !(hs = dict_find(helpserv_bots_dict, argv[2], NULL)))) {
1230 helpserv_notice(user, "HSMSG_INVALID_BOT");
1234 if (subcmd->flags & CMD_NEVER_FROM_OPSERV) {
1235 helpserv_notice(user, "HSMSG_NO_USE_OPSERV");
1241 strcpy(botnick, hs->helpserv->nick);
1242 retval = subcmd->func(user, hs, 1, argc-2, argv+2);
1244 strcpy(botnick, "No bot");
1245 retval = subcmd->func(user, hs, 1, argc-1, argv+1);
1251 static void helpserv_help(struct helpserv_bot *hs, int from_opserv, struct userNode *user, const char *topic) {
1252 send_help(user, (from_opserv ? opserv : hs->helpserv), helpserv_helpfile, topic);
1255 static int append_entry(const char *key, UNUSED_ARG(void *data), void *extra) {
1256 struct helpfile_expansion *exp = extra;
1259 row = exp->value.table.length++;
1260 exp->value.table.contents[row] = calloc(1, sizeof(char*));
1261 exp->value.table.contents[row][0] = key;
1265 static struct helpfile_expansion helpserv_expand_variable(const char *variable) {
1266 struct helpfile_expansion exp;
1268 if (!irccasecmp(variable, "index")) {
1269 exp.type = HF_TABLE;
1270 exp.value.table.length = 1;
1271 exp.value.table.width = 1;
1272 exp.value.table.flags = TABLE_REPEAT_ROWS;
1273 exp.value.table.contents = calloc(dict_size(helpserv_func_dict)+1, sizeof(char**));
1274 exp.value.table.contents[0] = calloc(1, sizeof(char*));
1275 exp.value.table.contents[0][0] = "Commands:";
1276 dict_foreach(helpserv_func_dict, append_entry, &exp);
1280 exp.type = HF_STRING;
1281 exp.value.str = NULL;
1285 static void helpserv_helpfile_read(void) {
1286 helpserv_helpfile = open_helpfile(HELPSERV_HELPFILE_NAME, helpserv_expand_variable);
1289 static HELPSERV_USERCMD(usercmd_wait) {
1290 struct helpserv_request *other;
1292 char buf[INTERVALLEN];
1296 helpserv_user_reply("HSMSG_YOU_BEING_HELPED_BY", likely_helper->nick);
1298 helpserv_user_reply("HSMSG_YOU_BEING_HELPED");
1302 for (other = req->hs->unhandled, pos = -1, count = 0;
1304 other = other->next_unhandled, ++count) {
1309 intervalString(buf, now - req->hs->unhandled->opened, req->user->handle_info);
1310 helpserv_user_reply("HSMSG_WAIT_STATUS", pos+1, count, buf);
1313 static HELPSERV_FUNC(cmd_help) {
1319 topic = unsplit_string(argv+1, argc-1, NULL);
1320 helpserv_help(hs, from_opserv, user, topic);
1325 static HELPSERV_FUNC(cmd_readhelp) {
1326 struct timeval start, stop;
1327 struct helpfile *old_helpfile = helpserv_helpfile;
1329 gettimeofday(&start, NULL);
1330 helpserv_helpfile_read();
1331 if (helpserv_helpfile) {
1332 close_helpfile(old_helpfile);
1334 helpserv_helpfile = old_helpfile;
1336 gettimeofday(&stop, NULL);
1337 stop.tv_sec -= start.tv_sec;
1338 stop.tv_usec -= start.tv_usec;
1339 if (stop.tv_usec < 0) {
1341 stop.tv_usec += 1000000;
1343 helpserv_notice(user, "HSMSG_READHELP_SUCCESS", (unsigned long)stop.tv_sec, (unsigned long)stop.tv_usec/1000);
1348 static struct helpserv_user * helpserv_add_user(struct helpserv_bot *hs, struct handle_info *handle, enum helpserv_level level) {
1349 struct helpserv_user *hs_user;
1350 struct helpserv_userlist *userlist;
1352 hs_user = calloc(1, sizeof(struct helpserv_user));
1353 hs_user->handle = handle;
1355 hs_user->help_mode = 0;
1356 hs_user->level = level;
1357 hs_user->join_time = find_handle_in_channel(hs->helpchan, handle, NULL) ? now : 0;
1358 dict_insert(hs->users, handle->handle, hs_user);
1360 if (!(userlist = dict_find(helpserv_users_byhand_dict, handle->handle, NULL))) {
1361 userlist = helpserv_userlist_alloc();
1362 dict_insert(helpserv_users_byhand_dict, handle->handle, userlist);
1364 helpserv_userlist_append(userlist, hs_user);
1369 static void helpserv_del_user(struct helpserv_bot *hs, struct helpserv_user *hs_user) {
1370 dict_remove(hs->users, hs_user->handle->handle);
1373 static int cmd_add_user(struct helpserv_bot *hs, int from_opserv, struct userNode *user, enum helpserv_level level, int argc, char *argv[]) {
1374 struct helpserv_user *actor;
1375 struct handle_info *handle;
1380 actor = GetHSUser(hs, user->handle_info);
1381 if (actor->level < HlManager) {
1382 helpserv_notice(user, "HSMSG_CANNOT_ADD");
1389 if (!(handle = helpserv_get_handle_info(user, argv[1])))
1392 if (GetHSUser(hs, handle)) {
1393 helpserv_notice(user, "HSMSG_USER_EXISTS", handle->handle);
1397 if (!(from_opserv) && actor && (actor->level <= level)) {
1398 helpserv_notice(user, "HSMSG_NO_BUMP_ACCESS");
1402 helpserv_add_user(hs, handle, level);
1404 helpserv_notice(user, "HSMSG_ADDED_USER", helpserv_level2str(level), handle->handle);
1408 static HELPSERV_FUNC(cmd_deluser) {
1409 struct helpserv_user *actor=NULL, *victim;
1410 struct handle_info *handle;
1411 enum helpserv_level level;
1416 actor = GetHSUser(hs, user->handle_info);
1417 if (actor->level < HlManager) {
1418 helpserv_notice(user, "HSMSG_CANNOT_DEL");
1423 if (!(handle = helpserv_get_handle_info(user, argv[1])))
1426 if (!(victim = GetHSUser(hs, handle))) {
1427 helpserv_notice(user, "HSMSG_NOT_IN_USERLIST", handle->handle, hs->helpserv->nick);
1431 if (!from_opserv && actor && (actor->level <= victim->level)) {
1432 helpserv_notice(user, "MSG_USER_OUTRANKED", victim->handle->handle);
1436 level = victim->level;
1437 helpserv_del_user(hs, victim);
1438 helpserv_notice(user, "HSMSG_DELETED_USER", helpserv_level2str(level), handle->handle);
1443 helpserv_user_comp(const void *arg_a, const void *arg_b)
1445 const struct helpserv_user *a = *(struct helpserv_user**)arg_a;
1446 const struct helpserv_user *b = *(struct helpserv_user**)arg_b;
1448 if (a->level != b->level)
1449 res = b->level - a->level;
1451 res = irccasecmp(a->handle->handle, b->handle->handle);
1455 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) {
1456 struct helpserv_userlist users;
1457 struct helpfile_table tbl;
1458 struct helpserv_user *hs_user;
1460 enum helpserv_level last_level;
1464 users.size = dict_size(hs->users);
1465 users.list = alloca(users.size*sizeof(hs->users[0]));
1466 helpserv_notice(user, "HSMSG_USERLIST_HEADER", hs->helpserv->nick);
1467 for (it = dict_first(hs->users); it; it = iter_next(it)) {
1468 hs_user = iter_data(it);
1469 if (hs_user->level < min_lvl)
1471 if (hs_user->level > max_lvl)
1473 users.list[users.used++] = hs_user;
1476 helpserv_notice(user, "MSG_NONE");
1479 qsort(users.list, users.used, sizeof(users.list[0]), helpserv_user_comp);
1480 switch (user->handle_info->userlist_style) {
1482 tbl.length = users.used + 1;
1484 tbl.flags = TABLE_NO_FREE;
1485 tbl.contents = alloca(tbl.length * sizeof(tbl.contents[0]));
1486 tbl.contents[0] = alloca(tbl.width * sizeof(tbl.contents[0][0]));
1487 tbl.contents[0][0] = "Level";
1488 tbl.contents[0][1] = "Handle";
1489 tbl.contents[0][2] = "WeekStart";
1490 for (ii = 0; ii < users.used; ) {
1491 hs_user = users.list[ii++];
1492 tbl.contents[ii] = alloca(tbl.width * sizeof(tbl.contents[0][0]));
1493 tbl.contents[ii][0] = helpserv_level_names[hs_user->level];
1494 tbl.contents[ii][1] = hs_user->handle->handle;
1495 tbl.contents[ii][2] = weekday_names[hs_user->week_start];
1497 table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
1499 case HI_STYLE_ZOOT: default:
1500 last_level = HlNone;
1503 tbl.flags = TABLE_NO_FREE | TABLE_REPEAT_ROWS | TABLE_NO_HEADERS;
1504 tbl.contents = alloca(users.used * sizeof(tbl.contents[0]));
1505 for (ii = 0; ii < users.used; ) {
1506 hs_user = users.list[ii++];
1507 if (hs_user->level != last_level) {
1509 helpserv_notice(user, "HSMSG_USERLIST_ZOOT_LVL", hs->helpserv->nick, helpserv_level_names[last_level]);
1510 table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
1513 last_level = hs_user->level;
1515 tbl.contents[tbl.length] = alloca(tbl.width * sizeof(tbl.contents[0][0]));
1516 tbl.contents[tbl.length++][0] = hs_user->handle->handle;
1519 helpserv_notice(user, "HSMSG_USERLIST_ZOOT_LVL", hs->helpserv->nick, helpserv_level_names[last_level]);
1520 table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
1526 static HELPSERV_FUNC(cmd_helpers) {
1527 return show_helper_range(user, hs, from_opserv, HlTrial, HlOwner);
1530 static HELPSERV_FUNC(cmd_wlist) {
1531 return show_helper_range(user, hs, from_opserv, HlOwner, HlOwner);
1534 static HELPSERV_FUNC(cmd_mlist) {
1535 return show_helper_range(user, hs, from_opserv, HlManager, HlManager);
1538 static HELPSERV_FUNC(cmd_hlist) {
1539 return show_helper_range(user, hs, from_opserv, HlHelper, HlHelper);
1542 static HELPSERV_FUNC(cmd_tlist) {
1543 return show_helper_range(user, hs, from_opserv, HlTrial, HlTrial);
1546 static HELPSERV_FUNC(cmd_addowner) {
1547 return cmd_add_user(hs, from_opserv, user, HlOwner, argc, argv);
1550 static HELPSERV_FUNC(cmd_addmanager) {
1551 return cmd_add_user(hs, from_opserv, user, HlManager, argc, argv);
1554 static HELPSERV_FUNC(cmd_addhelper) {
1555 return cmd_add_user(hs, from_opserv, user, HlHelper, argc, argv);
1558 static HELPSERV_FUNC(cmd_addtrial) {
1559 return cmd_add_user(hs, from_opserv, user, HlTrial, argc, argv);
1562 static HELPSERV_FUNC(cmd_clvl) {
1563 struct helpserv_user *actor=NULL, *victim;
1564 struct handle_info *handle;
1565 enum helpserv_level level;
1570 actor = GetHSUser(hs, user->handle_info);
1571 if (actor->level < HlManager) {
1572 helpserv_notice(user, "HSMSG_CANNOT_CLVL");
1577 if (!(handle = helpserv_get_handle_info(user, argv[1])))
1580 if (!(victim = GetHSUser(hs, handle))) {
1581 helpserv_notice(user, "HSMSG_NOT_IN_USERLIST", handle->handle, hs->helpserv->nick);
1585 if (((level = helpserv_str2level(argv[2])) == HlNone) || level == HlOper) {
1586 helpserv_notice(user, "HSMSG_INVALID_ACCESS", argv[2]);
1590 if (!(from_opserv) && actor) {
1591 if (actor == victim) {
1592 helpserv_notice(user, "HSMSG_NO_SELF_CLVL");
1596 if (actor->level <= victim->level) {
1597 helpserv_notice(user, "MSG_USER_OUTRANKED", victim->handle->handle);
1601 if (level >= actor->level) {
1602 helpserv_notice(user, "HSMSG_NO_BUMP_ACCESS");
1607 victim->level = level;
1608 helpserv_notice(user, "HSMSG_CHANGED_ACCESS", handle->handle, helpserv_level2str(level));
1613 static void free_request(void *data) {
1614 struct helpserv_request *req = data;
1617 if (shutting_down && (req->hs->persist_lengths[PERSIST_T_REQUEST] != PERSIST_CLOSE || !req->handle)) {
1618 helpserv_log_request(req, "srvx shutdown");
1621 /* Clean up from the unhandled queue */
1622 if (req->hs->unhandled) {
1623 if (req->hs->unhandled == req) {
1624 req->hs->unhandled = req->next_unhandled;
1626 struct helpserv_request *uh;
1627 for (uh=req->hs->unhandled; uh->next_unhandled && (uh->next_unhandled != req); uh = uh->next_unhandled);
1628 if (uh->next_unhandled) {
1629 uh->next_unhandled = req->next_unhandled;
1634 /* Clean up the lists */
1635 if (req->parent_nick_list) {
1636 if (req->parent_nick_list->used == 1) {
1637 dict_remove(helpserv_reqs_bynick_dict, req->user->nick);
1639 helpserv_reqlist_remove(req->parent_nick_list, req);
1642 if (req->parent_hand_list) {
1643 if (req->parent_hand_list->used == 1) {
1644 dict_remove(helpserv_reqs_byhand_dict, req->handle->handle);
1646 helpserv_reqlist_remove(req->parent_hand_list, req);
1650 free_string_list(req->text);
1654 static HELPSERV_FUNC(cmd_close) {
1655 struct helpserv_request *req, *newest=NULL;
1656 struct helpserv_reqlist *nick_list, *hand_list;
1657 struct helpserv_user *hs_user=GetHSUser(hs, user->handle_info);
1658 struct userNode *req_user=NULL;
1659 char close_reason[MAXLEN], reqnum[12];
1660 unsigned long old_req;
1668 if (!(req = smart_get_request(hs, hs_user, argv[1], &num_requests))) {
1669 helpserv_notice(user, "HSMSG_REQ_INVALID", argv[1]);
1673 sprintf(reqnum, "%lu", req->id);
1675 if (num_requests > 1)
1676 helpserv_notice(user, "HSMSG_REQ_FOUNDMANY");
1678 if (hs_user->level < HlManager && req->helper != hs_user) {
1680 helpserv_notice(user, "HSMSG_REQ_NOT_YOURS_ASSIGNED_TO", req->id, req->helper->handle->handle);
1682 helpserv_notice(user, "HSMSG_REQ_NOT_YOURS_UNASSIGNED", req->id);
1686 helpserv_notice(user, "HSMSG_REQ_CLOSED", req->id);
1688 req_user = req->user;
1689 helpserv_message(hs, req->user, MSGTYPE_REQ_CLOSED);
1691 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_CLOSE_REQUEST_1", req->id, req->user->nick, req->handle->handle, user->nick);
1693 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_CLOSE_REQUEST_2", req->id, req->user->nick, user->nick);
1696 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_CLOSE_REQUEST_3", req->id, req->handle->handle, user->nick);
1698 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_CLOSE_REQUEST_4", req->id, user->nick);
1701 hs_user->closed[0]++;
1702 hs_user->closed[4]++;
1704 /* Set these to keep track of the lists after the request is gone, but
1705 * not if free_request() will helpserv_reqlist_free() them. */
1706 nick_list = req->parent_nick_list;
1707 if (nick_list && (nick_list->used == 1))
1709 hand_list = req->parent_hand_list;
1710 if (hand_list && (hand_list->used == 1))
1715 snprintf(close_reason, MAXLEN, "Closed by %s: %s", user->handle_info->handle, unsplit_string(argv+2, argc-2, NULL));
1717 sprintf(close_reason, "Closed by %s", user->handle_info->handle);
1719 helpserv_log_request(req, close_reason);
1720 dict_remove(hs->requests, reqnum);
1722 /* Look for other requests associated with them */
1724 for (i=0; i < nick_list->used; i++) {
1725 req = nick_list->list[i];
1729 if (!newest || (newest->opened < req->opened))
1734 helpserv_msguser(newest->user, "HSMSG_REQ_FOUND_ANOTHER", old_req, newest->id);
1737 if (req_user && hs->auto_devoice) {
1738 struct modeNode *mn = GetUserMode(hs->helpchan, req_user);
1739 if ((!newest || !newest->helper) && mn && (mn->modes & MODE_VOICE)) {
1740 struct mod_chanmode change;
1741 mod_chanmode_init(&change);
1743 change.args[0].mode = MODE_REMOVE | MODE_VOICE;
1744 change.args[0].u.member = mn;
1745 mod_chanmode_announce(hs->helpserv, hs->helpchan, &change);
1752 static HELPSERV_FUNC(cmd_list) {
1755 struct helpfile_table tbl;
1756 unsigned int line, total;
1757 struct helpserv_request *req;
1759 if ((argc < 2) || !irccasecmp(argv[1], "unassigned")) {
1760 for (req = hs->unhandled, total=0; req; req = req->next_unhandled, total++) ;
1761 helpserv_notice(user, "HSMSG_REQ_LIST_TOP_UNASSIGNED", total);
1762 searchtype = 1; /* Unassigned */
1763 } else if (!irccasecmp(argv[1], "assigned")) {
1764 for (req = hs->unhandled, total=dict_size(hs->requests); req; req = req->next_unhandled, total--) ;
1765 helpserv_notice(user, "HSMSG_REQ_LIST_TOP_ASSIGNED", total);
1766 searchtype = 2; /* Assigned */
1767 } else if (!irccasecmp(argv[1], "me")) {
1768 for (total = 0, it = dict_first(hs->requests); it; it = iter_next(it)) {
1769 req = iter_data(it);
1770 if (req->helper && (req->helper->handle == user->handle_info))
1773 helpserv_notice(user, "HSMSG_REQ_LIST_TOP_YOUR", total);
1775 } else if (!irccasecmp(argv[1], "all")) {
1776 total = dict_size(hs->requests);
1777 helpserv_notice(user, "HSMSG_REQ_LIST_TOP_ALL", total);
1778 searchtype = 3; /* All */
1780 helpserv_notice(user, "HSMSG_BAD_REQ_TYPE", argv[1]);
1785 helpserv_notice(user, "HSMSG_REQ_LIST_NONE");
1789 tbl.length = total+1;
1791 tbl.flags = TABLE_NO_FREE;
1792 tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
1793 tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
1794 tbl.contents[0][0] = "ID#";
1795 tbl.contents[0][1] = "User";
1796 tbl.contents[0][2] = "Helper";
1797 tbl.contents[0][3] = "Time open";
1798 tbl.contents[0][4] = "User status";
1800 for (it=dict_first(hs->requests), line=0; it; it=iter_next(it)) {
1801 char opentime[INTERVALLEN], reqid[12], username[NICKLEN+2];
1803 req = iter_data(it);
1805 switch (searchtype) {
1818 if (!req->helper || (req->helper->handle != user->handle_info))
1825 tbl.contents[line] = alloca(tbl.width * sizeof(**tbl.contents));
1826 sprintf(reqid, "%lu", req->id);
1827 tbl.contents[line][0] = strdup(reqid);
1829 strcpy(username, req->user->nick);
1832 strcpy(username+1, req->handle->handle);
1834 tbl.contents[line][1] = strdup(username);
1835 tbl.contents[line][2] = req->helper ? req->helper->handle->handle : "(Unassigned)";
1836 intervalString(opentime, now - req->opened, user->handle_info);
1837 tbl.contents[line][3] = strdup(opentime);
1838 tbl.contents[line][4] = ((req->user || req->handle->users) ? "Online" : "Offline");
1841 table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
1843 for (; line > 0; line--) {
1844 free((char *)tbl.contents[line][0]);
1845 free((char *)tbl.contents[line][1]);
1846 free((char *)tbl.contents[line][3]);
1852 static void helpserv_show(int from_opserv, struct helpserv_bot *hs, struct userNode *user, struct helpserv_request *req) {
1854 char buf[MAX_LINE_SIZE];
1855 char buf2[INTERVALLEN];
1860 helpserv_notice(user, "HSMSG_REQ_INFO_2a", req->user->nick, req->handle->handle);
1862 helpserv_notice(user, "HSMSG_REQ_INFO_2b", req->user->nick);
1863 else if (req->handle)
1864 if (req->handle->users)
1865 helpserv_notice(user, "HSMSG_REQ_INFO_2c", req->handle->handle);
1867 helpserv_notice(user, "HSMSG_REQ_INFO_2d", req->handle->handle);
1869 helpserv_notice(user, "HSMSG_REQ_INFO_2e");
1871 strftime(buf, MAX_LINE_SIZE, HSFMT_TIME, localtime(&feh));
1872 intervalString(buf2, now - req->opened, user->handle_info);
1873 helpserv_notice(user, "HSMSG_REQ_INFO_3", buf, buf2);
1874 helpserv_notice(user, "HSMSG_REQ_INFO_4");
1875 for (nn=0; nn < req->text->used; nn++)
1876 helpserv_notice(user, "HSMSG_REQ_INFO_MESSAGE", req->text->list[nn]);
1879 /* actor is the one who executed the command... it should == user except from
1881 static int helpserv_assign(int from_opserv, struct helpserv_bot *hs, struct userNode *user, struct userNode *actor, struct helpserv_request *req) {
1882 struct helpserv_request *req2;
1883 struct helpserv_user *old_helper;
1885 if (!user->handle_info)
1887 if ((hs->persist_lengths[PERSIST_T_HELPER] == PERSIST_PART) && !GetUserMode(hs->helpchan, user)) {
1888 struct helpserv_user *hsuser_actor = GetHSUser(hs, actor->handle_info);
1889 if (hsuser_actor->level < HlManager) {
1890 helpserv_notice(user, "HSMSG_REQ_YOU_NOT_IN_HELPCHAN", hs->helpchan->name);
1892 } else if (user != actor) {
1893 helpserv_notice(user, "HSMSG_REQ_YOU_NOT_IN_OVERRIDE", hs->helpchan->name);
1894 helpserv_notice(actor, "HSMSG_REQ_HIM_NOT_IN_OVERRIDE", user->nick, hs->helpchan->name);
1896 helpserv_notice(user, "HSMSG_REQ_SELF_NOT_IN_OVERRIDE", hs->helpchan->name);
1899 hs->last_active = now;
1900 if ((old_helper = req->helper)) {
1901 /* don't need to remove from the unhandled queue */
1902 } else if (hs->unhandled == req) {
1903 hs->unhandled = req->next_unhandled;
1904 } else for (req2 = hs->unhandled; req2; req2 = req2->next_unhandled) {
1905 if (req2->next_unhandled == req) {
1906 req2->next_unhandled = req->next_unhandled;
1910 req->next_unhandled = NULL;
1911 req->helper = GetHSUser(hs, user->handle_info);
1912 assert(req->helper);
1913 req->assigned = now;
1915 if (req->user && hs->auto_join) {
1916 irc_svsjoin(hs->helpserv,req->user,hs->helpchan);
1920 helpserv_notice(user, "HSMSG_REQ_REASSIGNED", req->id, old_helper->handle->handle);
1921 req->helper->reassigned_to[0]++;
1922 req->helper->reassigned_to[4]++;
1923 old_helper->reassigned_from[0]++;
1924 old_helper->reassigned_from[4]++;
1926 helpserv_notice(user, "HSMSG_REQ_ASSIGNED_YOU", req->id);
1927 req->helper->picked_up[0]++;
1928 req->helper->picked_up[4]++;
1930 helpserv_show(from_opserv, hs, user, req);
1932 helpserv_message(hs, req->user, MSGTYPE_REQ_ASSIGNED);
1934 helpserv_msguser(req->user, "HSMSG_REQ_ASSIGNED_AGAIN", req->id, user->handle_info->handle, user->nick);
1936 helpserv_msguser(req->user, "HSMSG_REQ_ASSIGNED", req->id, user->handle_info->handle, user->nick);
1939 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_ASSIGN_REQUEST_1", req->id, req->user->nick, req->handle->handle, user->nick);
1941 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_ASSIGN_REQUEST_2", req->id, req->user->nick, user->nick);
1944 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_ASSIGN_REQUEST_3", req->id, req->handle->handle, user->nick);
1946 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_ASSIGN_REQUEST_4", req->id, user->nick);
1949 if (req->user && hs->auto_voice) {
1950 struct mod_chanmode change;
1951 mod_chanmode_init(&change);
1953 change.args[0].mode = MODE_VOICE;
1954 if ((change.args[0].u.member = GetUserMode(hs->helpchan, req->user)))
1955 mod_chanmode_announce(hs->helpserv, hs->helpchan, &change);
1961 static HELPSERV_FUNC(cmd_next) {
1962 struct helpserv_request *req;
1964 if (!(req = hs->unhandled)) {
1965 helpserv_notice(user, "HSMSG_REQ_NO_UNASSIGNED");
1968 return helpserv_assign(from_opserv, hs, user, user, req);
1971 static HELPSERV_FUNC(cmd_show) {
1972 struct helpserv_request *req;
1973 struct helpserv_user *hs_user=GetHSUser(hs, user->handle_info);
1978 if (!(req = smart_get_request(hs, hs_user, argv[1], &num_requests))) {
1979 helpserv_notice(user, "HSMSG_REQ_INVALID", argv[1]);
1983 if (num_requests > 1)
1984 helpserv_notice(user, "HSMSG_REQ_FOUNDMANY");
1986 helpserv_notice(user, "HSMSG_REQ_INFO_1", req->id);
1987 helpserv_show(from_opserv, hs, user, req);
1991 static HELPSERV_FUNC(cmd_pickup) {
1992 struct helpserv_request *req;
1993 struct helpserv_user *hs_user=GetHSUser(hs, user->handle_info);
2000 if (!(req = smart_get_request(hs, hs_user, argv[1], &num_requests))) {
2001 helpserv_notice(user, "HSMSG_REQ_INVALID", argv[1]);
2005 if (num_requests > 1)
2006 helpserv_notice(user, "HSMSG_REQ_FOUNDMANY");
2008 return helpserv_assign(from_opserv, hs, user, user, req);
2011 static HELPSERV_FUNC(cmd_reassign) {
2012 struct helpserv_request *req;
2013 struct userNode *targetuser;
2014 struct helpserv_user *target;
2015 struct helpserv_user *hs_user=GetHSUser(hs, user->handle_info);
2022 if (!(req = smart_get_request(hs, hs_user, argv[1], &num_requests))) {
2023 helpserv_notice(user, "HSMSG_REQ_INVALID", argv[1]);
2027 if (num_requests > 1)
2028 helpserv_notice(user, "HSMSG_REQ_FOUNDMANY");
2030 if (!(targetuser = GetUserH(argv[2]))) {
2031 helpserv_notice(user, "MSG_NICK_UNKNOWN", argv[2]);
2035 if (!targetuser->handle_info) {
2036 helpserv_notice(user, "MSG_USER_AUTHENTICATE", targetuser->nick);
2040 if (!(target = GetHSUser(hs, targetuser->handle_info))) {
2041 helpserv_notice(user, "HSMSG_NOT_IN_USERLIST", targetuser->nick, hs->helpserv->nick);
2045 if ((hs->persist_lengths[PERSIST_T_HELPER] == PERSIST_PART) && !GetUserMode(hs->helpchan, user) && (hs_user->level < HlManager)) {
2046 helpserv_notice(user, "HSMSG_REQ_HIM_NOT_IN_HELPCHAN", targetuser->nick, hs->helpchan->name);
2050 helpserv_assign(from_opserv, hs, targetuser, user, req);
2054 static HELPSERV_FUNC(cmd_addnote) {
2055 char text[MAX_LINE_SIZE], timestr[MAX_LINE_SIZE], *note;
2056 struct helpserv_request *req;
2057 struct helpserv_user *hs_user=GetHSUser(hs, user->handle_info);
2063 if (!(req = smart_get_request(hs, hs_user, argv[1], &num_requests))) {
2064 helpserv_notice(user, "HSMSG_REQ_INVALID", argv[1]);
2068 if (num_requests > 1)
2069 helpserv_notice(user, "HSMSG_REQ_FOUNDMANY");
2071 note = unsplit_string(argv+2, argc-2, NULL);
2074 strftime(timestr, MAX_LINE_SIZE, HSFMT_TIME, localtime(&feh));
2075 snprintf(text, MAX_LINE_SIZE, "[Helper note at %s]:", timestr);
2076 string_list_append(req->text, strdup(text));
2077 snprintf(text, MAX_LINE_SIZE, " <%s> %s", user->handle_info->handle, note);
2078 string_list_append(req->text, strdup(text));
2080 helpserv_notice(user, "HSMSG_REQMSG_NOTE_ADDED", req->id);
2085 static HELPSERV_FUNC(cmd_page) {
2088 helpserv_page(PGSRC_COMMAND, "HSMSG_PAGE_REQUEST", user->nick, unsplit_string(argv+1, argc-1, NULL));
2093 static HELPSERV_FUNC(cmd_stats) {
2094 struct helpserv_user *target, *hs_user;
2095 struct handle_info *target_handle;
2096 struct helpfile_table tbl;
2098 char intervalstr[INTERVALLEN], buf[16];
2100 hs_user = from_opserv ? NULL : GetHSUser(hs, user->handle_info);
2103 if (!from_opserv && (hs_user->level < HlManager)) {
2104 helpserv_notice(user, "HSMSG_NEED_MANAGER");
2108 if (!(target_handle = helpserv_get_handle_info(user, argv[1]))) {
2112 if (!(target = GetHSUser(hs, target_handle))) {
2113 helpserv_notice(user, "HSMSG_NOT_IN_USERLIST", target_handle->handle, hs->helpserv->nick);
2118 helpserv_notice(user, "HSMSG_OPSERV_NEED_USER");
2124 helpserv_notice(user, "HSMSG_STATS_TOP", hs->helpserv->nick, target->handle->handle, weekday_names[target->week_start]);
2128 tbl.flags = TABLE_NO_FREE;
2129 tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
2130 tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
2131 tbl.contents[0][0] = "";
2132 tbl.contents[0][1] = "Recorded time";
2133 for (i=0; i < 5; i++) {
2134 unsigned int week_time = target->time_per_week[i];
2135 tbl.contents[i+1] = alloca(tbl.width * sizeof(**tbl.contents));
2136 if ((i == 0 || i == 4) && target->join_time)
2137 week_time += now - target->join_time;
2138 helpserv_interval(intervalstr, week_time);
2139 tbl.contents[i+1][1] = strdup(intervalstr);
2141 tbl.contents[1][0] = "This week";
2142 tbl.contents[2][0] = "Last week";
2143 tbl.contents[3][0] = "2 weeks ago";
2144 tbl.contents[4][0] = "3 weeks ago";
2145 tbl.contents[5][0] = "Total";
2147 helpserv_notice(user, "HSMSG_STATS_TIME", hs->helpchan->name);
2148 table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
2150 for (i=1; i <= 5; i++)
2151 free((char *)tbl.contents[i][1]);
2155 tbl.flags = TABLE_NO_FREE;
2156 tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
2157 tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
2158 tbl.contents[1] = alloca(tbl.width * sizeof(**tbl.contents));
2159 tbl.contents[2] = alloca(tbl.width * sizeof(**tbl.contents));
2160 tbl.contents[3] = alloca(tbl.width * sizeof(**tbl.contents));
2161 tbl.contents[4] = alloca(tbl.width * sizeof(**tbl.contents));
2162 tbl.contents[0][0] = "Category";
2163 tbl.contents[0][1] = "This week";
2164 tbl.contents[0][2] = "Last week";
2165 tbl.contents[0][3] = "Total";
2167 tbl.contents[1][0] = "Requests picked up";
2168 for (i=0; i < 3; i++) {
2169 sprintf(buf, "%u", target->picked_up[(i == 2 ? 4 : i)]);
2170 tbl.contents[1][i+1] = strdup(buf);
2172 tbl.contents[2][0] = "Requests closed";
2173 for (i=0; i < 3; i++) {
2174 sprintf(buf, "%u", target->closed[(i == 2 ? 4 : i)]);
2175 tbl.contents[2][i+1] = strdup(buf);
2177 tbl.contents[3][0] = "Reassigned from";
2178 for (i=0; i < 3; i++) {
2179 sprintf(buf, "%u", target->reassigned_from[(i == 2 ? 4 : i)]);
2180 tbl.contents[3][i+1] = strdup(buf);
2182 tbl.contents[4][0] = "Reassigned to";
2183 for (i=0; i < 3; i++) {
2184 sprintf(buf, "%u", target->reassigned_to[(i == 2 ? 4 : i)]);
2185 tbl.contents[4][i+1] = strdup(buf);
2188 helpserv_notice(user, "HSMSG_STATS_REQS");
2189 table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
2191 for (i=1; i < 5; i++) {
2192 free((char *)tbl.contents[i][1]);
2193 free((char *)tbl.contents[i][2]);
2194 free((char *)tbl.contents[i][3]);
2200 static HELPSERV_FUNC(cmd_statsreport) {
2202 struct helpfile_table tbl;
2204 unsigned int line, i;
2205 struct userNode *srcbot = from_opserv ? opserv : hs->helpserv;
2207 if ((argc > 1) && !irccasecmp(argv[1], "NOTICE"))
2210 tbl.length = dict_size(hs->users)+1;
2212 tbl.flags = TABLE_NO_FREE;
2213 tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
2214 tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
2215 tbl.contents[0][0] = "Account";
2216 tbl.contents[0][1] = "Requests";
2217 tbl.contents[0][2] = "Time helping";
2219 for (it=dict_first(hs->users), line=0; it; it=iter_next(it)) {
2220 struct helpserv_user *hs_user=iter_data(it);
2222 tbl.contents[++line] = alloca(tbl.width * sizeof(**tbl.contents));
2223 tbl.contents[line][0] = hs_user->handle->handle;
2224 tbl.contents[line][1] = malloc(12);
2225 tbl.contents[line][2] = malloc(32); /* A bit more than needed */
2228 /* 4 to 1 instead of 3 to 0 because it's unsigned */
2229 for (i=4; i > 0; i--) {
2230 for (it=dict_first(hs->users), line=0; it; it=iter_next(it)) {
2231 struct helpserv_user *hs_user = iter_data(it);
2233 unsigned int week_time = hs_user->time_per_week[i-1];
2234 if ((i==1) && hs_user->join_time)
2235 week_time += now - hs_user->join_time;
2236 helpserv_interval((char *)tbl.contents[++line][2], week_time);
2239 sprintf((char *)tbl.contents[line][1], "%u", hs_user->picked_up[i-1]+hs_user->reassigned_to[i-1]);
2241 send_target_message(use_privmsg, user->nick, srcbot, statsreport_week[i-1]);
2242 table_send(srcbot, user->nick, 0, (use_privmsg ? irc_privmsg : irc_notice), tbl);
2245 for (line=1; line <= dict_size(hs->users); line++) {
2246 free((char *)tbl.contents[line][1]);
2247 free((char *)tbl.contents[line][2]);
2254 helpserv_in_channel(struct helpserv_bot *hs, struct chanNode *channel) {
2255 enum page_source pgsrc;
2256 if (channel == hs->helpchan)
2258 for (pgsrc=0; pgsrc<PGSRC_COUNT; pgsrc++)
2259 if (channel == hs->page_targets[pgsrc])
2264 static HELPSERV_FUNC(cmd_move) {
2266 helpserv_notice(user, "HSMSG_INVALID_BOT");
2272 if (is_valid_nick(argv[1])) {
2273 char *newnick = argv[1], oldnick[NICKLEN], reason[MAXLEN];
2275 strcpy(oldnick, hs->helpserv->nick);
2277 if (GetUserH(newnick)) {
2278 helpserv_notice(user, "HSMSG_NICK_EXISTS", newnick);
2282 dict_remove2(helpserv_bots_dict, hs->helpserv->nick, 1);
2283 NickChange(hs->helpserv, newnick, 0);
2284 dict_insert(helpserv_bots_dict, hs->helpserv->nick, hs);
2286 helpserv_notice(user, "HSMSG_RENAMED", oldnick, newnick);
2288 snprintf(reason, MAXLEN, "HelpServ bot %s (in %s) renamed to %s by %s.", oldnick, hs->helpchan->name, newnick, user->nick);
2289 global_message(MESSAGE_RECIPIENT_OPERS, reason);
2292 } else if (IsChannelName(argv[1])) {
2293 struct chanNode *old_helpchan = hs->helpchan;
2294 char *newchan = argv[1], oldchan[CHANNELLEN], reason[MAXLEN];
2295 struct helpserv_botlist *botlist;
2297 strcpy(oldchan, hs->helpchan->name);
2299 if (!irccasecmp(oldchan, newchan)) {
2300 helpserv_notice(user, "HSMSG_MOVE_SAME_CHANNEL", hs->helpserv->nick);
2304 if (opserv_bad_channel(newchan)) {
2305 helpserv_notice(user, "HSMSG_ILLEGAL_CHANNEL", newchan);
2309 botlist = dict_find(helpserv_bots_bychan_dict, hs->helpchan->name, NULL);
2310 helpserv_botlist_remove(botlist, hs);
2311 if (botlist->used == 0) {
2312 dict_remove(helpserv_bots_bychan_dict, hs->helpchan->name);
2315 hs->helpchan = NULL;
2316 if (!helpserv_in_channel(hs, old_helpchan)) {
2317 snprintf(reason, MAXLEN, "Moved to %s by %s.", newchan, user->nick);
2318 DelChannelUser(hs->helpserv, old_helpchan, reason, 0);
2321 if (!(hs->helpchan = GetChannel(newchan))) {
2322 hs->helpchan = AddChannel(newchan, now, NULL, NULL);
2323 AddChannelUser(hs->helpserv, hs->helpchan)->modes |= MODE_CHANOP;
2324 } else if (!helpserv_in_channel(hs, old_helpchan)) {
2325 struct mod_chanmode change;
2326 mod_chanmode_init(&change);
2328 change.args[0].mode = MODE_CHANOP;
2329 change.args[0].u.member = AddChannelUser(hs->helpserv, hs->helpchan);
2330 mod_chanmode_announce(hs->helpserv, hs->helpchan, &change);
2333 if (!(botlist = dict_find(helpserv_bots_bychan_dict, hs->helpchan->name, NULL))) {
2334 botlist = helpserv_botlist_alloc();
2335 dict_insert(helpserv_bots_bychan_dict, hs->helpchan->name, botlist);
2337 helpserv_botlist_append(botlist, hs);
2339 snprintf(reason, MAXLEN, "HelpServ %s (%s) moved to %s by %s.", hs->helpserv->nick, oldchan, newchan, user->nick);
2340 global_message(MESSAGE_RECIPIENT_OPERS, reason);
2344 helpserv_notice(user, "HSMSG_INVALID_MOVE", argv[1]);
2349 static HELPSERV_FUNC(cmd_bots) {
2351 struct helpfile_table tbl;
2354 helpserv_notice(user, "HSMSG_BOTLIST_HEADER");
2356 tbl.length = dict_size(helpserv_bots_dict)+1;
2358 tbl.flags = TABLE_NO_FREE;
2359 tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
2360 tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
2361 tbl.contents[0][0] = "Bot";
2362 tbl.contents[0][1] = "Channel";
2363 tbl.contents[0][2] = "Owner";
2364 tbl.contents[0][3] = "Inactivity";
2366 for (it=dict_first(helpserv_bots_dict), i=1; it; it=iter_next(it), i++) {
2367 dict_iterator_t it2;
2368 struct helpserv_bot *bot;
2369 struct helpserv_user *owner=NULL;
2371 bot = iter_data(it);
2373 for (it2=dict_first(bot->users); it2; it2=iter_next(it2)) {
2374 if (((struct helpserv_user *)iter_data(it2))->level == HlOwner) {
2375 owner = iter_data(it2);
2380 tbl.contents[i] = alloca(tbl.width * sizeof(**tbl.contents));
2381 tbl.contents[i][0] = iter_key(it);
2382 tbl.contents[i][1] = bot->helpchan->name;
2383 tbl.contents[i][2] = owner ? owner->handle->handle : "None";
2384 tbl.contents[i][3] = alloca(INTERVALLEN);
2385 intervalString((char*)tbl.contents[i][3], now - bot->last_active, user->handle_info);
2388 table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
2393 static void helpserv_page_helper_gone(struct helpserv_bot *hs, struct helpserv_request *req, const char *reason) {
2394 const int from_opserv = 0;
2399 /* Let the user know that their request is now unhandled */
2401 struct modeNode *mn = GetUserMode(hs->helpchan, req->user);
2402 helpserv_msguser(req->user, "HSMSG_REQ_UNASSIGNED", req->id, reason);
2403 if (hs->auto_devoice && mn && (mn->modes & MODE_VOICE)) {
2404 struct mod_chanmode change;
2405 mod_chanmode_init(&change);
2407 change.args[0].mode = MODE_REMOVE | MODE_VOICE;
2408 change.args[0].u.member = mn;
2409 mod_chanmode_announce(hs->helpserv, hs->helpchan, &change);
2412 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_HELPER_GONE_1", req->id, req->user->nick, req->handle->handle, req->helper->handle->handle, reason);
2414 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_HELPER_GONE_2", req->id, req->user->nick, req->helper->handle->handle, reason);
2417 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_HELPER_GONE_3", req->id, req->handle->handle, req->helper->handle->handle, reason);
2419 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_HELPER_GONE_2", req->id, req->helper->handle->handle, reason);
2422 /* Now put it back in the queue */
2423 if (hs->unhandled == NULL) {
2424 /* Nothing there, put it at the front */
2425 hs->unhandled = req;
2426 req->next_unhandled = NULL;
2428 /* Should it be at the front? */
2429 if (hs->unhandled->opened >= req->opened) {
2430 req->next_unhandled = hs->unhandled;
2431 hs->unhandled = req;
2433 struct helpserv_request *unhandled;
2434 /* Find the request that this should be inserted AFTER */
2435 for (unhandled=hs->unhandled; unhandled->next_unhandled && (unhandled->next_unhandled->opened < req->opened); unhandled = unhandled->next_unhandled);
2436 req->next_unhandled = unhandled->next_unhandled;
2437 unhandled->next_unhandled = req;
2444 /* This takes care of WHINE_DELAY and IDLE_DELAY */
2445 static void run_whine_interval(void *data) {
2446 struct helpserv_bot *hs=data;
2447 struct helpfile_table tbl;
2450 /* First, run the WHINE_DELAY */
2451 if (hs->intervals[INTERVAL_WHINE_DELAY]
2452 && (hs->page_types[PGSRC_ALERT] != PAGE_NONE)
2453 && (hs->page_targets[PGSRC_ALERT] != NULL)
2454 && (!hs->intervals[INTERVAL_EMPTY_INTERVAL] || !hs->helpchan_empty)) {
2455 struct helpserv_request *unh;
2456 struct helpserv_reqlist reqlist;
2457 unsigned int queuesize=0;
2459 helpserv_reqlist_init(&reqlist);
2461 for (unh = hs->unhandled; unh; unh = unh->next_unhandled) {
2463 if ((now - unh->opened) >= hs->intervals[INTERVAL_WHINE_DELAY]) {
2464 helpserv_reqlist_append(&reqlist, unh);
2469 char strwhinedelay[INTERVALLEN];
2471 intervalString(strwhinedelay, hs->intervals[INTERVAL_WHINE_DELAY], NULL);
2472 #if ANNOYING_ALERT_PAGES
2473 tbl.length = reqlist.used + 1;
2475 tbl.flags = TABLE_NO_FREE;
2476 tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
2477 tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
2478 tbl.contents[0][0] = "ID#";
2479 tbl.contents[0][1] = "Nick";
2480 tbl.contents[0][2] = "Account";
2481 tbl.contents[0][3] = "Waiting time";
2483 for (i=1; i <= reqlist.used; i++) {
2484 char reqid[12], unh_time[INTERVALLEN];
2485 unh = reqlist.list[i-1];
2487 tbl.contents[i] = alloca(tbl.width * sizeof(**tbl.contents));
2488 sprintf(reqid, "%lu", unh->id);
2489 tbl.contents[i][0] = strdup(reqid);
2490 tbl.contents[i][1] = unh->user ? unh->user->nick : "Not online";
2491 tbl.contents[i][2] = unh->handle ? unh->handle->handle : "Not authed";
2492 intervalString(unh_time, now - unh->opened, NULL);
2493 tbl.contents[i][3] = strdup(unh_time);
2496 helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_WHINE_HEADER", reqlist.used, strwhinedelay, queuesize, dict_size(hs->requests));
2497 table_send(hs->helpserv, hs->page_targets[PGSRC_ALERT]->name, 0, page_type_funcs[hs->page_types[PGSRC_ALERT]], tbl);
2499 for (i=1; i <= reqlist.used; i++) {
2500 free((char *)tbl.contents[i][0]);
2501 free((char *)tbl.contents[i][3]);
2504 helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_WHINE_HEADER", reqlist.used, strwhinedelay, queuesize, dict_size(hs->requests));
2508 helpserv_reqlist_clean(&reqlist);
2511 /* Next run IDLE_DELAY */
2512 if (hs->intervals[INTERVAL_IDLE_DELAY]
2513 && (hs->page_types[PGSRC_STATUS] != PAGE_NONE)
2514 && (hs->page_targets[PGSRC_STATUS] != NULL)) {
2515 struct modeList mode_list;
2517 modeList_init(&mode_list);
2519 for (i=0; i < hs->helpchan->members.used; i++) {
2520 struct modeNode *mn = hs->helpchan->members.list[i];
2521 /* Ignore ops. Perhaps this should be a set option? */
2522 if (mn->modes & MODE_CHANOP)
2524 /* Check if they've been idle long enough */
2525 if ((unsigned)(now - mn->idle_since) < hs->intervals[INTERVAL_IDLE_DELAY])
2527 /* Add them to the list of idle people.. */
2528 modeList_append(&mode_list, mn);
2531 if (mode_list.used) {
2532 char stridledelay[INTERVALLEN];
2534 tbl.length = mode_list.used + 1;
2536 tbl.flags = TABLE_NO_FREE;
2537 tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
2538 tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
2539 tbl.contents[0][0] = "Nick";
2540 tbl.contents[0][1] = "Account";
2541 tbl.contents[0][2] = "ID#";
2542 tbl.contents[0][3] = "Idle time";
2544 for (i=1; i <= mode_list.used; i++) {
2545 char reqid[12], idle_time[INTERVALLEN];
2546 struct helpserv_reqlist *reqlist;
2547 struct modeNode *mn = mode_list.list[i-1];
2549 tbl.contents[i] = alloca(tbl.width * sizeof(**tbl.contents));
2550 tbl.contents[i][0] = mn->user->nick;
2551 tbl.contents[i][1] = mn->user->handle_info ? mn->user->handle_info->handle : "Not authed";
2553 if ((reqlist = dict_find(helpserv_reqs_bynick_dict, mn->user->nick, NULL))) {
2556 for (j = reqlist->used-1; j >= 0; j--) {
2557 struct helpserv_request *req = reqlist->list[j];
2559 if (req->hs == hs) {
2560 sprintf(reqid, "%lu", req->id);
2566 strcpy(reqid, "None");
2568 strcpy(reqid, "None");
2570 tbl.contents[i][2] = strdup(reqid);
2572 intervalString(idle_time, now - mn->idle_since, NULL);
2573 tbl.contents[i][3] = strdup(idle_time);
2576 intervalString(stridledelay, hs->intervals[INTERVAL_IDLE_DELAY], NULL);
2577 helpserv_page(PGSRC_STATUS, "HSMSG_PAGE_IDLE_HEADER", mode_list.used, hs->helpchan->name, stridledelay);
2578 table_send(hs->helpserv, hs->page_targets[PGSRC_STATUS]->name, 0, page_types[hs->page_types[PGSRC_STATUS]].func, tbl);
2580 for (i=1; i <= mode_list.used; i++) {
2581 free((char *)tbl.contents[i][2]);
2582 free((char *)tbl.contents[i][3]);
2586 modeList_clean(&mode_list);
2589 if (hs->intervals[INTERVAL_WHINE_INTERVAL]) {
2590 timeq_add(now + hs->intervals[INTERVAL_WHINE_INTERVAL], run_whine_interval, hs);
2594 /* Returns -1 if there's any helpers,
2595 * 0 if there are no helpers
2596 * >1 if there are trials (number of trials)
2598 static int find_helpchan_helpers(struct helpserv_bot *hs) {
2602 for (it=dict_first(hs->users); it; it=iter_next(it)) {
2603 struct helpserv_user *hs_user=iter_data(it);
2605 if (find_handle_in_channel(hs->helpchan, hs_user->handle, NULL)) {
2606 if (hs_user->level >= HlHelper) {
2607 hs->helpchan_empty = 0;
2614 hs->helpchan_empty = 1;
2619 static void run_empty_interval(void *data) {
2620 struct helpserv_bot *hs=data;
2621 int num_trials=find_helpchan_helpers(hs);
2622 unsigned int num_unh;
2623 struct helpserv_request *unh;
2625 if (num_trials == -1)
2627 if (hs->req_on_join && !hs->unhandled)
2630 for (num_unh=0, unh=hs->unhandled; unh; num_unh++)
2631 unh = unh->next_unhandled;
2634 helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_ONLYTRIALALERT", hs->helpchan->name, num_trials, num_unh);
2636 helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_EMPTYALERT", hs->helpchan->name, num_unh);
2638 if (hs->intervals[INTERVAL_EMPTY_INTERVAL])
2639 timeq_add(now + hs->intervals[INTERVAL_EMPTY_INTERVAL], run_empty_interval, hs);
2642 static void free_user(void *data) {
2643 struct helpserv_user *hs_user = data;
2644 struct helpserv_bot *hs = hs_user->hs;
2645 struct helpserv_userlist *userlist;
2649 for (it=dict_first(hs->requests); it; it=iter_next(it)) {
2650 struct helpserv_request *req = iter_data(it);
2652 if (req->helper == hs_user)
2653 helpserv_page_helper_gone(hs, req, "been deleted");
2657 userlist = dict_find(helpserv_users_byhand_dict, hs_user->handle->handle, NULL);
2658 if (userlist->used == 1) {
2659 dict_remove(helpserv_users_byhand_dict, hs_user->handle->handle);
2661 helpserv_userlist_remove(userlist, hs_user);
2667 static struct helpserv_bot *register_helpserv(const char *nick, const char *help_channel, const char *registrar) {
2668 struct helpserv_bot *hs;
2669 struct helpserv_botlist *botlist;
2671 /* Laziness dictates calloc, since there's a lot to set to NULL or 0, and
2672 * it's a harmless default */
2673 hs = calloc(1, sizeof(struct helpserv_bot));
2675 if (!(hs->helpserv = AddLocalUser(nick, nick, NULL, helpserv_conf.description, NULL))) {
2680 reg_privmsg_func(hs->helpserv, helpserv_botmsg);
2682 if (!(hs->helpchan = GetChannel(help_channel))) {
2683 hs->helpchan = AddChannel(help_channel, now, NULL, NULL);
2684 AddChannelUser(hs->helpserv, hs->helpchan)->modes |= MODE_CHANOP;
2686 struct mod_chanmode change;
2687 mod_chanmode_init(&change);
2689 change.args[0].mode = MODE_CHANOP;
2690 change.args[0].u.member = AddChannelUser(hs->helpserv, hs->helpchan);
2691 mod_chanmode_announce(hs->helpserv, hs->helpchan, &change);
2695 hs->registrar = strdup(registrar);
2697 hs->users = dict_new();
2698 /* Don't free keys - they use the handle_info's handle field */
2699 dict_set_free_data(hs->users, free_user);
2700 hs->requests = dict_new();
2701 dict_set_free_keys(hs->requests, free);
2702 dict_set_free_data(hs->requests, free_request);
2704 dict_insert(helpserv_bots_dict, hs->helpserv->nick, hs);
2706 if (!(botlist = dict_find(helpserv_bots_bychan_dict, hs->helpchan->name, NULL))) {
2707 botlist = helpserv_botlist_alloc();
2708 dict_insert(helpserv_bots_bychan_dict, hs->helpchan->name, botlist);
2710 helpserv_botlist_append(botlist, hs);
2714 static HELPSERV_FUNC(cmd_register) {
2715 char *nick, *helpchan, reason[MAXLEN];
2716 struct handle_info *handle;
2720 if (!is_valid_nick(nick)) {
2721 helpserv_notice(user, "HSMSG_ILLEGAL_NICK", nick);
2724 if (GetUserH(nick)) {
2725 helpserv_notice(user, "HSMSG_NICK_EXISTS", nick);
2729 if (!IsChannelName(helpchan)) {
2730 helpserv_notice(user, "HSMSG_ILLEGAL_CHANNEL", helpchan);
2734 if (opserv_bad_channel(helpchan)) {
2735 helpserv_notice(user, "HSMSG_ILLEGAL_CHANNEL", helpchan);
2738 if (!(handle = helpserv_get_handle_info(user, argv[3])))
2741 if (!(hs = register_helpserv(nick, helpchan, user->handle_info->handle))) {
2742 helpserv_notice(user, "HSMSG_ERROR_ADDING_SERVICE", nick);
2746 hs->registered = now;
2747 helpserv_add_user(hs, handle, HlOwner);
2749 helpserv_notice(user, "HSMSG_REG_SUCCESS", handle->handle, nick);
2751 snprintf(reason, MAXLEN, "HelpServ %s (%s) registered to %s by %s.", nick, hs->helpchan->name, handle->handle, user->nick);
2752 /* Not sent to helpers, since they can't register HelpServ */
2753 global_message(MESSAGE_RECIPIENT_OPERS, reason);
2757 static void unregister_helpserv(struct helpserv_bot *hs) {
2758 enum message_type msgtype;
2760 timeq_del(0, NULL, hs, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_FUNC);
2762 /* Requests before users so that it doesn't spam mentioning now-unhandled
2763 * requests because the users were deleted */
2764 dict_delete(hs->requests);
2765 hs->requests = NULL; /* so we don't try to look up requests in free_user() */
2766 dict_delete(hs->users);
2767 free(hs->registrar);
2769 for (msgtype=0; msgtype<MSGTYPE_COUNT; msgtype++)
2770 free(hs->messages[msgtype]);
2773 static void helpserv_free_bot(void *data) {
2774 unregister_helpserv(data);
2778 static void helpserv_unregister(struct helpserv_bot *bot, const char *quit_fmt, const char *global_fmt, const char *actor) {
2779 char reason[MAXLEN], channame[CHANNELLEN], *botname;
2780 struct helpserv_botlist *botlist;
2783 botlist = dict_find(helpserv_bots_bychan_dict, bot->helpchan->name, NULL);
2784 helpserv_botlist_remove(botlist, bot);
2786 dict_remove(helpserv_bots_bychan_dict, bot->helpchan->name);
2787 botname=bot->helpserv->nick;
2788 len = strlen(bot->helpchan->name) + 1;
2789 safestrncpy(channame, bot->helpchan->name, len);
2790 snprintf(reason, sizeof(reason), quit_fmt, actor);
2791 DelUser(bot->helpserv, NULL, 1, reason);
2792 dict_remove(helpserv_bots_dict, botname);
2793 snprintf(reason, sizeof(reason), global_fmt, botname, channame, actor);
2794 global_message(MESSAGE_RECIPIENT_OPERS, reason);
2797 static HELPSERV_FUNC(cmd_unregister) {
2799 if (argc < 2 || strcmp(argv[1], "CONFIRM")) {
2800 helpserv_notice(user, "HSMSG_NEED_UNREG_CONFIRM");
2803 log_audit(HS_LOG, LOG_COMMAND, user, hs->helpserv, hs->helpchan->name, 0, "unregister CONFIRM");
2806 helpserv_unregister(hs, "Unregistered by %s", "HelpServ %s (%s) unregistered by %s.", user->nick);
2810 static HELPSERV_FUNC(cmd_expire) {
2811 struct helpserv_botlist victims;
2812 struct helpserv_bot *bot;
2813 dict_iterator_t it, next;
2814 unsigned int count = 0;
2816 memset(&victims, 0, sizeof(victims));
2817 for (it = dict_first(helpserv_bots_dict); it; it = next) {
2818 bot = iter_data(it);
2819 next = iter_next(it);
2820 if ((unsigned int)(now - bot->last_active) < helpserv_conf.expire_age)
2822 helpserv_unregister(bot, "Registration expired due to inactivity", "HelpServ %s (%s) expired at request of %s.", user->nick);
2825 helpserv_notice(user, "HSMSG_EXPIRATION_DONE", count);
2829 static HELPSERV_FUNC(cmd_giveownership) {
2830 struct handle_info *hi;
2831 struct helpserv_user *new_owner, *old_owner, *hs_user;
2833 char reason[MAXLEN];
2835 if (!from_opserv && ((argc < 3) || strcmp(argv[2], "CONFIRM"))) {
2836 helpserv_notice(user, "HSMSG_NEED_GIVEOWNERSHIP_CONFIRM");
2839 hi = helpserv_get_handle_info(user, argv[1]);
2842 new_owner = GetHSUser(hs, hi);
2844 helpserv_notice(user, "HSMSG_NOT_IN_USERLIST", hi->handle, hs->helpserv->nick);
2848 old_owner = GetHSUser(hs, user->handle_info);
2849 else for (it = dict_first(hs->users), old_owner = NULL; it; it = iter_next(it)) {
2850 hs_user = iter_data(it);
2851 if (hs_user->level != HlOwner)
2854 helpserv_notice(user, "HSMSG_MULTIPLE_OWNERS", hs->helpserv->nick);
2857 old_owner = hs_user;
2859 if (!from_opserv && (new_owner->handle == user->handle_info)) {
2860 helpserv_notice(user, "HSMSG_NO_TRANSFER_SELF");
2864 old_owner->level = HlManager;
2865 new_owner->level = HlOwner;
2866 helpserv_notice(user, "HSMSG_OWNERSHIP_GIVEN", hs->helpserv->nick, new_owner->handle->handle);
2867 sprintf(reason, "%s (%s) ownership transferred to %s by %s.", hs->helpserv->nick, hs->helpchan->name, new_owner->handle->handle, user->handle_info->handle);
2871 static HELPSERV_FUNC(cmd_weekstart) {
2872 struct handle_info *hi;
2873 struct helpserv_user *actor, *victim;
2877 actor = from_opserv ? NULL : GetHSUser(hs, user->handle_info);
2878 if (!(hi = helpserv_get_handle_info(user, argv[1])))
2880 if (!(victim = GetHSUser(hs, hi))) {
2881 helpserv_notice(user, "HSMSG_NOT_IN_USERLIST", hi->handle, hs->helpserv->nick);
2884 if (actor && (actor->level <= victim->level) && (actor != victim)) {
2885 helpserv_notice(user, "MSG_USER_OUTRANKED", victim->handle->handle);
2888 if (argc > 2 && (!actor || actor->level >= HlManager)) {
2890 switch (argv[2][0]) {
2892 if ((argv[2][1] == 'u') || (argv[2][1] == 'U'))
2894 else if ((argv[2][1] == 'a') || (argv[2][1] == 'A'))
2897 case 'm': case 'M': new_day = 1; break;
2899 if ((argv[2][1] == 'u') || (argv[2][1] == 'U'))
2901 else if ((argv[2][1] == 'h') || (argv[2][1] == 'H'))
2904 case 'w': case 'W': new_day = 3; break;
2905 case 'f': case 'F': new_day = 5; break;
2908 helpserv_notice(user, "HSMSG_BAD_WEEKDAY", argv[2]);
2911 victim->week_start = new_day;
2914 helpserv_notice(user, "HSMSG_WEEK_STARTS", victim->handle->handle, weekday_names[victim->week_start]);
2918 static HELPSERV_FUNC(cmd_modstats) {
2919 struct handle_info *hi;
2920 struct helpserv_user *victim;
2921 const char *field_name;
2923 unsigned int *field = NULL;
2927 if (!oper_has_access(user, (from_opserv ? opserv : hs->helpserv), helpserv_conf.modstats_level, 0))
2929 if (!(hi = helpserv_get_handle_info(user, argv[1])))
2931 if (!(victim = GetHSUser(hs, hi))) {
2932 helpserv_notice(user, "HSMSG_NOT_IN_USERLIST", hi->handle, hs->helpserv->nick);
2936 field_name = argv[2];
2937 if (!strcasecmp(argv[3], "total"))
2939 else if(!strcasecmp(argv[3], "current"))
2942 week = strtoul(argv[3], &errptr, 0);
2943 if (*errptr != '\0') {
2944 helpserv_notice(user, "HSMSG_MODSTATS_BAD_WEEK");
2948 mod = strtol(argv[4], NULL, 0);
2950 if (week < 0 || week > 4) {
2951 helpserv_notice(user, "HSMSG_MODSTATS_BAD_WEEK");
2955 if (!strcasecmp(field_name, "time")) {
2956 if (victim->join_time && (week == 0 || week == 4)) {
2957 victim->time_per_week[0] += now - victim->join_time;
2958 victim->time_per_week[4] += now - victim->join_time;
2959 victim->join_time = now;
2961 field = victim->time_per_week;
2963 else if (!strcasecmp(field_name, "picked") || !strcasecmp(field_name, "picked_up") || !strcasecmp(field_name, "reqs"))
2964 field = victim->picked_up;
2965 else if (!strcasecmp(field_name, "closed"))
2966 field = victim->closed;
2967 else if (!strcasecmp(field_name, "ra_from") || !strcasecmp(field_name, "reassigned_from"))
2968 field = victim->reassigned_from;
2969 else if (!strcasecmp(field_name, "ra_to") || !strcasecmp(field_name, "reassigned_to"))
2970 field = victim->reassigned_to;
2972 helpserv_notice(user, "HSMSG_MODSTATS_BAD_FIELD");
2976 if (mod < 0 && mod < -(int)field[week]) {
2977 helpserv_notice(user, "HSMSG_MODSTATS_NEGATIVE");
2982 helpserv_notice(user, "HSMSG_MODSTATS_SUCCESS", victim->handle->handle);
2987 static void set_page_target(struct helpserv_bot *hs, enum page_source idx, const char *target) {
2988 struct chanNode *new_target, *old_target;
2991 if (!IsChannelName(target)) {
2992 log_module(HS_LOG, LOG_ERROR, "%s has an invalid page target.", hs->helpserv->nick);
2995 new_target = GetChannel(target);
2997 new_target = AddChannel(target, now, NULL, NULL);
2998 AddChannelUser(hs->helpserv, new_target);
3003 if (new_target == hs->page_targets[idx])
3005 old_target = hs->page_targets[idx];
3006 hs->page_targets[idx] = NULL;
3007 if (old_target && !helpserv_in_channel(hs, old_target))
3008 DelChannelUser(hs->helpserv, old_target, "Changing page target.", 0);
3009 if (new_target && !helpserv_in_channel(hs, new_target)) {
3010 struct mod_chanmode change;
3011 mod_chanmode_init(&change);
3013 change.args[0].mode = MODE_CHANOP;
3014 change.args[0].u.member = AddChannelUser(hs->helpserv, new_target);
3015 mod_chanmode_announce(hs->helpserv, new_target, &change);
3017 hs->page_targets[idx] = new_target;
3020 static int opt_page_target(struct userNode *user, struct helpserv_bot *hs, int from_opserv, int argc, char *argv[], enum page_source idx) {
3024 if (!IsOper(user)) {
3025 helpserv_notice(user, "HSMSG_SET_NEED_OPER");
3028 if (!strcmp(argv[0], "*")) {
3029 set_page_target(hs, idx, NULL);
3031 } else if (!IsChannelName(argv[0])) {
3032 helpserv_notice(user, "MSG_NOT_CHANNEL_NAME");
3035 set_page_target(hs, idx, argv[0]);
3039 if (hs->page_targets[idx])
3040 helpserv_notice(user, page_sources[idx].print_target, hs->page_targets[idx]->name);
3042 helpserv_notice(user, page_sources[idx].print_target, user_find_message(user, "MSG_NONE"));
3046 static HELPSERV_OPTION(opt_pagetarget_command) {
3047 return opt_page_target(user, hs, from_opserv, argc, argv, PGSRC_COMMAND);
3050 static HELPSERV_OPTION(opt_pagetarget_alert) {
3051 return opt_page_target(user, hs, from_opserv, argc, argv, PGSRC_ALERT);
3054 static HELPSERV_OPTION(opt_pagetarget_status) {
3055 return opt_page_target(user, hs, from_opserv, argc, argv, PGSRC_STATUS);
3058 static enum page_type page_type_from_name(const char *name) {
3059 enum page_type type;
3060 for (type=0; type<PAGE_COUNT; type++)
3061 if (!irccasecmp(page_types[type].db_name, name))
3066 static int opt_page_type(struct userNode *user, struct helpserv_bot *hs, int from_opserv, int argc, char *argv[], enum page_source idx) {
3067 enum page_type new_type;
3071 new_type = page_type_from_name(argv[0]);
3072 if (new_type == PAGE_COUNT) {
3073 helpserv_notice(user, "HSMSG_INVALID_OPTION", argv[0]);
3076 hs->page_types[idx] = new_type;
3079 helpserv_notice(user, page_sources[idx].print_type,
3080 user_find_message(user, page_types[hs->page_types[idx]].print_name));
3084 static HELPSERV_OPTION(opt_pagetype) {
3085 return opt_page_type(user, hs, from_opserv, argc, argv, PGSRC_COMMAND);
3088 static HELPSERV_OPTION(opt_alert_page_type) {
3089 return opt_page_type(user, hs, from_opserv, argc, argv, PGSRC_ALERT);
3092 static HELPSERV_OPTION(opt_status_page_type) {
3093 return opt_page_type(user, hs, from_opserv, argc, argv, PGSRC_STATUS);
3096 static int opt_message(struct userNode *user, struct helpserv_bot *hs, int from_opserv, int argc, char *argv[], enum message_type idx) {
3100 char *msg = unsplit_string(argv, argc, NULL);
3101 free(hs->messages[idx]);
3102 hs->messages[idx] = strcmp(msg, "*") ? strdup(msg) : NULL;
3105 if (hs->messages[idx])
3106 helpserv_notice(user, message_types[idx].print_name, hs->messages[idx]);
3108 helpserv_notice(user, message_types[idx].print_name, user_find_message(user, "MSG_NONE"));
3112 static HELPSERV_OPTION(opt_greeting) {
3113 return opt_message(user, hs, from_opserv, argc, argv, MSGTYPE_GREETING);
3116 static HELPSERV_OPTION(opt_req_opened) {
3117 return opt_message(user, hs, from_opserv, argc, argv, MSGTYPE_REQ_OPENED);
3120 static HELPSERV_OPTION(opt_req_assigned) {
3121 return opt_message(user, hs, from_opserv, argc, argv, MSGTYPE_REQ_ASSIGNED);
3124 static HELPSERV_OPTION(opt_req_closed) {
3125 return opt_message(user, hs, from_opserv, argc, argv, MSGTYPE_REQ_CLOSED);
3128 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) {
3129 char buf[INTERVALLEN];
3133 unsigned long new_int = ParseInterval(argv[0]);
3134 if (!new_int && strcmp(argv[0], "0")) {
3135 helpserv_notice(user, "MSG_INVALID_DURATION", argv[0]);
3138 if (new_int && new_int < min) {
3139 intervalString(buf, min, user->handle_info);
3140 helpserv_notice(user, "HSMSG_INVALID_INTERVAL", user_find_message(user, interval_types[idx].print_name), buf);
3143 hs->intervals[idx] = new_int;
3146 if (hs->intervals[idx]) {
3147 intervalString(buf, hs->intervals[idx], user->handle_info);
3148 helpserv_notice(user, interval_types[idx].print_name, buf);
3150 helpserv_notice(user, interval_types[idx].print_name, user_find_message(user, "HSMSG_0_DISABLED"));
3154 static HELPSERV_OPTION(opt_idle_delay) {
3155 return opt_interval(user, hs, from_opserv, argc, argv, INTERVAL_IDLE_DELAY, 60);
3158 static HELPSERV_OPTION(opt_whine_delay) {
3159 return opt_interval(user, hs, from_opserv, argc, argv, INTERVAL_WHINE_DELAY, 60);
3162 static HELPSERV_OPTION(opt_whine_interval) {
3163 unsigned int old_val = hs->intervals[INTERVAL_WHINE_INTERVAL];
3166 retval = opt_interval(user, hs, from_opserv, argc, argv, INTERVAL_WHINE_INTERVAL, 60);
3168 if (!old_val && hs->intervals[INTERVAL_WHINE_INTERVAL]) {
3169 timeq_add(now + hs->intervals[INTERVAL_WHINE_INTERVAL], run_whine_interval, hs);
3170 } else if (old_val && !hs->intervals[INTERVAL_WHINE_INTERVAL]) {
3171 timeq_del(0, run_whine_interval, hs, TIMEQ_IGNORE_WHEN);
3177 static HELPSERV_OPTION(opt_empty_interval) {
3178 unsigned int old_val = hs->intervals[INTERVAL_EMPTY_INTERVAL];
3181 retval = opt_interval(user, hs, from_opserv, argc, argv, INTERVAL_EMPTY_INTERVAL, 60);
3183 if (!old_val && hs->intervals[INTERVAL_EMPTY_INTERVAL]) {
3184 timeq_add(now + hs->intervals[INTERVAL_EMPTY_INTERVAL], run_empty_interval, hs);
3185 } else if (old_val && !hs->intervals[INTERVAL_EMPTY_INTERVAL]) {
3186 timeq_del(0, run_empty_interval, hs, TIMEQ_IGNORE_WHEN);
3192 static HELPSERV_OPTION(opt_stale_delay) {
3193 return opt_interval(user, hs, from_opserv, argc, argv, INTERVAL_STALE_DELAY, 60);
3196 static enum persistence_length persistence_from_name(const char *name) {
3197 enum persistence_length pers;
3198 for (pers=0; pers<PERSIST_COUNT; pers++)
3199 if (!irccasecmp(name, persistence_lengths[pers].db_name))
3201 return PERSIST_COUNT;
3204 static int opt_persist(struct userNode *user, struct helpserv_bot *hs, int from_opserv, int argc, char *argv[], enum persistence_type idx) {
3208 enum persistence_length new_pers = persistence_from_name(argv[0]);
3209 if (new_pers == PERSIST_COUNT) {
3210 helpserv_notice(user, "HSMSG_INVALID_OPTION", argv[0]);
3213 hs->persist_lengths[idx] = new_pers;
3216 helpserv_notice(user, persistence_lengths[idx].print_name,
3217 user_find_message(user, persistence_lengths[hs->persist_lengths[idx]].print_name));
3221 static HELPSERV_OPTION(opt_request_persistence) {
3222 return opt_persist(user, hs, from_opserv, argc, argv, PERSIST_T_REQUEST);
3225 static HELPSERV_OPTION(opt_helper_persistence) {
3226 return opt_persist(user, hs, from_opserv, argc, argv, PERSIST_T_HELPER);
3229 static enum notification_type notification_from_name(const char *name) {
3230 enum notification_type notify;
3231 for (notify=0; notify<NOTIFY_COUNT; notify++)
3232 if (!irccasecmp(name, notification_types[notify].db_name))
3234 return NOTIFY_COUNT;
3237 static HELPSERV_OPTION(opt_notification) {
3241 enum notification_type new_notify = notification_from_name(argv[0]);
3242 if (new_notify == NOTIFY_COUNT) {
3243 helpserv_notice(user, "HSMSG_INVALID_OPTION", argv[0]);
3246 if (!from_opserv && (new_notify == NOTIFY_HANDLE)) {
3247 helpserv_notice(user, "HSMSG_SET_NEED_OPER");
3250 hs->notify = new_notify;
3253 helpserv_notice(user, "HSMSG_SET_NOTIFICATION", user_find_message(user, notification_types[hs->notify].print_name));
3257 #define OPTION_UINT(var, name) do { \
3260 (var) = strtoul(argv[0], NULL, 0); \
3263 helpserv_notice(user, name, (var)); \
3267 static HELPSERV_OPTION(opt_id_wrap) {
3268 OPTION_UINT(hs->id_wrap, "HSMSG_SET_IDWRAP");
3271 static HELPSERV_OPTION(opt_req_maxlen) {
3272 OPTION_UINT(hs->req_maxlen, "HSMSG_SET_REQMAXLEN");
3275 #define OPTION_BINARY(var, name) do { \
3278 if (enabled_string(argv[0])) { \
3281 } else if (disabled_string(argv[0])) { \
3285 helpserv_notice(user, "MSG_INVALID_BINARY", argv[0]); \
3289 helpserv_notice(user, name, user_find_message(user, (var) ? "MSG_ON" : "MSG_OFF")); \
3293 static HELPSERV_OPTION(opt_privmsg_only) {
3294 OPTION_BINARY(hs->privmsg_only, "HSMSG_SET_PRIVMSGONLY");
3297 static HELPSERV_OPTION(opt_req_on_join) {
3298 OPTION_BINARY(hs->req_on_join, "HSMSG_SET_REQONJOIN");
3301 static HELPSERV_OPTION(opt_auto_voice) {
3302 OPTION_BINARY(hs->auto_voice, "HSMSG_SET_AUTOVOICE");
3305 static HELPSERV_OPTION(opt_auto_join) {
3306 OPTION_BINARY(hs->auto_join, "HSMSG_SET_AUTOJOIN");
3309 static HELPSERV_OPTION(opt_auto_devoice) {
3310 OPTION_BINARY(hs->auto_devoice, "HSMSG_SET_AUTODEVOICE");
3313 static HELPSERV_OPTION(opt_publicchan) {
3317 publicchan = argv[0];
3318 if(strcmp(publicchan, "*")) {
3319 if (!IsChannelName(publicchan)) {
3320 helpserv_notice(user, "HSMSG_ILLEGAL_CHANNEL", publicchan);
3324 if (opserv_bad_channel(publicchan)) {
3325 helpserv_notice(user, "HSMSG_ILLEGAL_CHANNEL", publicchan);
3329 if (!hs->publicchan || (hs->publicchan && irccasecmp(hs->publicchan->name, publicchan))) {
3330 if(hs->publicchan) {
3331 //there is another public chan o.O
3333 DelChannelUser(hs->helpserv, hs->publicchan, "unregistered.", 0);
3334 hs->publicchan = NULL;
3337 if(strcmp(publicchan, "*")) {
3338 if (!(hs->publicchan = GetChannel(publicchan))) {
3339 hs->publicchan = AddChannel(publicchan, now, NULL, NULL);
3340 AddChannelUser(hs->helpserv, hs->publicchan)->modes |= MODE_CHANOP;
3342 struct mod_chanmode change;
3343 mod_chanmode_init(&change);
3345 change.args[0].mode = MODE_CHANOP;
3346 change.args[0].u.member = AddChannelUser(hs->helpserv, hs->publicchan);
3347 mod_chanmode_announce(hs->helpserv, hs->publicchan, &change);
3354 helpserv_notice(user, "HSMSG_SET_PUBLICCHAN", (hs->publicchan) ? hs->publicchan->name : user_find_message(user,"MSG_NONE")); \
3358 static HELPSERV_FUNC(cmd_set) {
3359 helpserv_option_func_t *opt;
3363 helpserv_option_func_t *display[] = {
3364 opt_publicchan, opt_pagetarget_command, opt_pagetarget_alert, opt_pagetarget_status,
3365 opt_pagetype, opt_alert_page_type, opt_status_page_type,
3366 opt_greeting, opt_req_opened, opt_req_assigned, opt_req_closed,
3367 opt_idle_delay, opt_whine_delay, opt_whine_interval,
3368 opt_empty_interval, opt_stale_delay, opt_request_persistence,
3369 opt_helper_persistence, opt_notification, opt_id_wrap,
3370 opt_req_maxlen, opt_privmsg_only, opt_req_on_join, opt_auto_voice, opt_auto_join,
3374 helpserv_notice(user, "HSMSG_QUEUE_OPTIONS");
3375 for (i=0; i<ArrayLength(display); i++)
3376 display[i](user, hs, from_opserv, 0, argv);
3380 if (!(opt = dict_find(helpserv_option_dict, argv[1], NULL))) {
3381 helpserv_notice(user, "HSMSG_INVALID_OPTION", argv[1]);
3385 if ((argc > 2) && !from_opserv) {
3386 struct helpserv_user *hs_user;
3388 if (!(hs_user = dict_find(hs->users, user->handle_info->handle, NULL))) {
3389 helpserv_notice(user, "HSMSG_WTF_WHO_ARE_YOU", hs->helpserv->nick);
3393 if (hs_user->level < HlManager) {
3394 helpserv_notice(user, "HSMSG_NEED_MANAGER");
3398 return opt(user, hs, from_opserv, argc-2, argv+2);
3401 static int user_write_helper(const char *key, void *data, void *extra) {
3402 struct helpserv_user *hs_user = data;
3403 struct saxdb_context *ctx = extra;
3404 struct string_list strlist;
3405 char str[5][16], *strs[5];
3408 saxdb_start_record(ctx, key, 0);
3409 /* Helper identification. */
3410 saxdb_write_string(ctx, KEY_HELPER_LEVEL, helpserv_level2str(hs_user->level));
3411 saxdb_write_string(ctx, KEY_HELPER_HELPMODE, (hs_user->help_mode ? "1" : "0"));
3412 saxdb_write_int(ctx, KEY_HELPER_WEEKSTART, hs_user->week_start);
3414 saxdb_start_record(ctx, KEY_HELPER_STATS, 0);
3415 for (i=0; i < ArrayLength(strs); ++i)
3417 strlist.list = strs;
3419 /* Time in help channel */
3420 for (i=0; i < strlist.used; i++) {
3421 unsigned int week_time = hs_user->time_per_week[i];
3422 if ((i==0 || i==4) && hs_user->join_time)
3423 week_time += now - hs_user->join_time;
3424 sprintf(str[i], "%u", week_time);
3426 saxdb_write_string_list(ctx, KEY_HELPER_STATS_TIME, &strlist);
3427 /* Requests picked up */
3428 for (i=0; i < strlist.used; i++)
3429 sprintf(str[i], "%u", hs_user->picked_up[i]);
3430 saxdb_write_string_list(ctx, KEY_HELPER_STATS_PICKUP, &strlist);
3431 /* Requests closed */
3432 for (i=0; i < strlist.used; i++)
3433 sprintf(str[i], "%u", hs_user->closed[i]);
3434 saxdb_write_string_list(ctx, KEY_HELPER_STATS_CLOSE, &strlist);
3435 /* Requests reassigned from user */
3436 for (i=0; i < strlist.used; i++)
3437 sprintf(str[i], "%u", hs_user->reassigned_from[i]);
3438 saxdb_write_string_list(ctx, KEY_HELPER_STATS_REASSIGNFROM, &strlist);
3439 /* Requests reassigned to user */
3440 for (i=0; i < strlist.used; i++)
3441 sprintf(str[i], "%u", hs_user->reassigned_to[i]);
3442 saxdb_write_string_list(ctx, KEY_HELPER_STATS_REASSIGNTO, &strlist);
3443 /* End of stats and whole record. */
3444 saxdb_end_record(ctx);
3445 saxdb_end_record(ctx);
3449 static int user_read_helper(const char *key, void *data, void *extra) {
3450 struct record_data *rd = data;
3451 struct helpserv_bot *hs = extra;
3452 struct helpserv_user *hs_user;
3453 struct handle_info *handle;
3455 enum helpserv_level level;
3457 struct string_list *strlist;
3460 if (rd->type != RECDB_OBJECT || !dict_size(rd->d.object)) {
3461 log_module(HS_LOG, LOG_ERROR, "Invalid user %s for %s.", key, hs->helpserv->nick);
3465 if (!(handle = get_handle_info(key))) {
3466 log_module(HS_LOG, LOG_ERROR, "Nonexistant account %s for %s.", key, hs->helpserv->nick);
3469 str = database_get_data(rd->d.object, KEY_HELPER_LEVEL, RECDB_QSTRING);
3471 level = helpserv_str2level(str);
3472 if (level == HlNone) {
3473 log_module(HS_LOG, LOG_ERROR, "Account %s has invalid level %s.", key, str);
3477 log_module(HS_LOG, LOG_ERROR, "Account %s has no level field for %s.", key, hs->helpserv->nick);
3481 hs_user = helpserv_add_user(hs, handle, level);
3483 str = database_get_data(rd->d.object, KEY_HELPER_HELPMODE, RECDB_QSTRING);
3484 hs_user->help_mode = (str && strtol(str, NULL, 0)) ? 1 : 0;
3485 str = database_get_data(rd->d.object, KEY_HELPER_WEEKSTART, RECDB_QSTRING);
3486 hs_user->week_start = str ? strtol(str, NULL, 0) : 0;
3489 stats = database_get_data(GET_RECORD_OBJECT(rd), KEY_HELPER_STATS, RECDB_OBJECT);
3492 /* The tests for strlist->used are for converting the old format to the new one */
3493 strlist = database_get_data(stats, KEY_HELPER_STATS_TIME, RECDB_STRING_LIST);
3495 for (i=0; i < 5 && i < strlist->used; i++)
3496 hs_user->time_per_week[i] = strtoul(strlist->list[i], NULL, 0);
3497 if (strlist->used == 4)
3498 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];
3500 strlist = database_get_data(stats, KEY_HELPER_STATS_PICKUP, RECDB_STRING_LIST);
3502 for (i=0; i < 5 && i < strlist->used; i++)
3503 hs_user->picked_up[i] = strtoul(strlist->list[i], NULL, 0);
3504 if (strlist->used == 2)
3505 hs_user->picked_up[4] = hs_user->picked_up[0]+hs_user->picked_up[1];
3507 strlist = database_get_data(stats, KEY_HELPER_STATS_CLOSE, RECDB_STRING_LIST);
3509 for (i=0; i < 5 && i < strlist->used; i++)
3510 hs_user->closed[i] = strtoul(strlist->list[i], NULL, 0);
3511 if (strlist->used == 2)
3512 hs_user->closed[4] = hs_user->closed[0]+hs_user->closed[1];
3514 strlist = database_get_data(stats, KEY_HELPER_STATS_REASSIGNFROM, RECDB_STRING_LIST);
3516 for (i=0; i < 5 && i < strlist->used; i++)
3517 hs_user->reassigned_from[i] = strtoul(strlist->list[i], NULL, 0);
3518 if (strlist->used == 2)
3519 hs_user->reassigned_from[4] = hs_user->reassigned_from[0]+hs_user->reassigned_from[1];
3521 strlist = database_get_data(stats, KEY_HELPER_STATS_REASSIGNTO, RECDB_STRING_LIST);
3523 for (i=0; i < 5 && i < strlist->used; i++)
3524 hs_user->reassigned_to[i] = strtoul(strlist->list[i], NULL, 0);
3525 if (strlist->used == 2)
3526 hs_user->reassigned_to[4] = hs_user->reassigned_to[0]+hs_user->reassigned_to[1];
3533 static int request_write_helper(const char *key, void *data, void *extra) {
3534 struct helpserv_request *request = data;
3535 struct saxdb_context *ctx = extra;
3537 if (!request->handle)
3540 saxdb_start_record(ctx, key, 0);
3541 if (request->helper) {
3542 saxdb_write_string(ctx, KEY_REQUEST_HELPER, request->helper->handle->handle);
3543 saxdb_write_int(ctx, KEY_REQUEST_ASSIGNED, request->assigned);
3545 saxdb_write_string(ctx, KEY_REQUEST_HANDLE, request->handle->handle);
3546 saxdb_write_int(ctx, KEY_REQUEST_OPENED, request->opened);
3547 saxdb_write_string_list(ctx, KEY_REQUEST_TEXT, request->text);
3548 saxdb_end_record(ctx);
3552 static int request_read_helper(const char *key, void *data, void *extra) {
3553 struct record_data *rd = data;
3554 struct helpserv_bot *hs = extra;
3555 struct helpserv_request *request;
3556 struct string_list *strlist;
3559 if (rd->type != RECDB_OBJECT || !dict_size(rd->d.object)) {
3560 log_module(HS_LOG, LOG_ERROR, "Invalid request %s:%s.", hs->helpserv->nick, key);
3564 request = calloc(1, sizeof(struct helpserv_request));
3566 request->id = strtoul(key, NULL, 0);
3568 request->user = NULL;
3569 request->parent_nick_list = request->parent_hand_list = NULL;
3571 str = database_get_data(rd->d.object, KEY_REQUEST_HANDLE, RECDB_QSTRING);
3572 if (!str || !(request->handle = get_handle_info(str))) {
3573 log_module(HS_LOG, LOG_ERROR, "Request %s:%s has an invalid or nonexistant account.", hs->helpserv->nick, key);
3577 if (!(request->parent_hand_list = dict_find(helpserv_reqs_byhand_dict, request->handle->handle, NULL))) {
3578 request->parent_hand_list = helpserv_reqlist_alloc();
3579 dict_insert(helpserv_reqs_byhand_dict, request->handle->handle, request->parent_hand_list);
3581 helpserv_reqlist_append(request->parent_hand_list, request);
3583 str = database_get_data(rd->d.object, KEY_REQUEST_OPENED, RECDB_QSTRING);
3585 log_module(HS_LOG, LOG_ERROR, "Request %s:%s has a nonexistant opening time. Using time(NULL).", hs->helpserv->nick, key);
3586 request->opened = time(NULL);
3588 request->opened = strtoul(str, NULL, 0);
3591 str = database_get_data(rd->d.object, KEY_REQUEST_ASSIGNED, RECDB_QSTRING);
3593 request->assigned = strtoul(str, NULL, 0);
3595 str = database_get_data(rd->d.object, KEY_REQUEST_HELPER, RECDB_QSTRING);
3597 if (!(request->helper = dict_find(hs->users, str, NULL))) {
3598 log_module(HS_LOG, LOG_ERROR, "Request %s:%s has an invalid or nonexistant helper.", hs->helpserv->nick, key);
3603 if (!hs->unhandled) {
3604 request->next_unhandled = NULL;
3605 hs->unhandled = request;
3606 } else if (hs->unhandled->opened > request->opened) {
3607 request->next_unhandled = hs->unhandled;
3608 hs->unhandled = request;
3610 struct helpserv_request *unh;
3611 for (unh = hs->unhandled; unh->next_unhandled && (unh->next_unhandled->opened < request->opened); unh = unh->next_unhandled);
3612 request->next_unhandled = unh->next_unhandled;
3613 unh->next_unhandled = request;
3617 strlist = database_get_data(rd->d.object, KEY_REQUEST_TEXT, RECDB_STRING_LIST);
3619 log_module(HS_LOG, LOG_ERROR, "Request %s:%s has no text.", hs->helpserv->nick, key);
3623 request->text = string_list_copy(strlist);
3625 dict_insert(hs->requests, strdup(key), request);
3631 helpserv_bot_write(const char *key, void *data, void *extra) {
3632 const struct helpserv_bot *hs = data;
3633 struct saxdb_context *ctx = extra;
3634 enum page_source pagesrc;
3635 enum message_type msgtype;
3636 enum interval_type inttype;
3637 enum persistence_type persisttype;
3638 struct string_list *slist;
3641 saxdb_start_record(ctx, key, 1);
3644 saxdb_start_record(ctx, KEY_HELPERS, 1);
3645 dict_foreach(hs->users, user_write_helper, ctx);
3646 saxdb_end_record(ctx);
3649 if (hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_CLOSE) {
3650 saxdb_start_record(ctx, KEY_REQUESTS, 0);
3651 dict_foreach(hs->requests, request_write_helper, ctx);
3652 saxdb_end_record(ctx);
3655 /* Other settings and state */
3656 saxdb_write_string(ctx, KEY_HELP_CHANNEL, hs->helpchan->name);
3657 if(hs->publicchan) saxdb_write_string(ctx, KEY_PUBLIC_CHANNEL, hs->publicchan->name);
3658 slist = alloc_string_list(PGSRC_COUNT);
3659 for (pagesrc=0; pagesrc<PGSRC_COUNT; pagesrc++) {
3660 struct chanNode *target = hs->page_targets[pagesrc];
3661 string_list_append(slist, strdup(target ? target->name : "*"));
3663 saxdb_write_string_list(ctx, KEY_PAGE_DEST, slist);
3664 free_string_list(slist);
3665 for (pagesrc=0; pagesrc<PGSRC_COUNT; pagesrc++) {
3666 const char *src = page_types[hs->page_types[pagesrc]].db_name;
3667 saxdb_write_string(ctx, page_sources[pagesrc].db_name, src);
3669 for (msgtype=0; msgtype<MSGTYPE_COUNT; msgtype++) {
3670 const char *msg = hs->messages[msgtype];
3672 saxdb_write_string(ctx, message_types[msgtype].db_name, msg);
3674 for (inttype=0; inttype<INTERVAL_COUNT; inttype++) {
3675 if (!hs->intervals[inttype])
3677 saxdb_write_int(ctx, interval_types[inttype].db_name, hs->intervals[inttype]);
3679 for (persisttype=0; persisttype<PERSIST_T_COUNT; persisttype++) {
3680 const char *persist = persistence_lengths[hs->persist_lengths[persisttype]].db_name;
3681 saxdb_write_string(ctx, persistence_types[persisttype].db_name, persist);
3683 saxdb_write_string(ctx, KEY_NOTIFICATION, notification_types[hs->notify].db_name);
3684 saxdb_write_int(ctx, KEY_REGISTERED, hs->registered);
3685 saxdb_write_int(ctx, KEY_IDWRAP, hs->id_wrap);
3686 saxdb_write_int(ctx, KEY_REQ_MAXLEN, hs->req_maxlen);
3687 saxdb_write_int(ctx, KEY_LAST_REQUESTID, hs->last_requestid);
3689 saxdb_write_string(ctx, KEY_REGISTRAR, hs->registrar);
3690 saxdb_write_int(ctx, KEY_PRIVMSG_ONLY, hs->privmsg_only);
3691 saxdb_write_int(ctx, KEY_REQ_ON_JOIN, hs->req_on_join);
3692 saxdb_write_int(ctx, KEY_AUTO_VOICE, hs->auto_voice);
3693 saxdb_write_int(ctx, KEY_AUTO_JOIN, hs->auto_join);
3694 saxdb_write_int(ctx, KEY_AUTO_DEVOICE, hs->auto_devoice);
3695 saxdb_write_int(ctx, KEY_LAST_ACTIVE, hs->last_active);
3697 /* End bot record */
3698 saxdb_end_record(ctx);
3703 helpserv_saxdb_write(struct saxdb_context *ctx) {
3704 saxdb_start_record(ctx, KEY_BOTS, 1);
3705 dict_foreach(helpserv_bots_dict, helpserv_bot_write, ctx);
3706 saxdb_end_record(ctx);
3707 saxdb_write_int(ctx, KEY_LAST_STATS_UPDATE, last_stats_update);
3711 static int helpserv_bot_read(const char *key, void *data, UNUSED_ARG(void *extra)) {
3712 struct record_data *br = data, *raw_record;
3713 struct helpserv_bot *hs;
3714 char *registrar, *helpchannel_name, *publicchannel_name, *str;
3715 dict_t users, requests;
3716 enum page_source pagesrc;
3717 enum message_type msgtype;
3718 enum interval_type inttype;
3719 enum persistence_type persisttype;
3721 users = database_get_data(GET_RECORD_OBJECT(br), KEY_HELPERS, RECDB_OBJECT);
3723 log_module(HS_LOG, LOG_ERROR, "%s has no users.", key);
3726 helpchannel_name = database_get_data(GET_RECORD_OBJECT(br), KEY_HELP_CHANNEL, RECDB_QSTRING);
3727 if (!helpchannel_name || !IsChannelName(helpchannel_name)) {
3728 log_module(HS_LOG, LOG_ERROR, "%s has an invalid channel name.", key);
3731 registrar = database_get_data(GET_RECORD_OBJECT(br), KEY_REGISTRAR, RECDB_QSTRING);
3733 hs = register_helpserv(key, helpchannel_name, registrar);
3735 publicchannel_name = database_get_data(GET_RECORD_OBJECT(br), KEY_PUBLIC_CHANNEL, RECDB_QSTRING);
3736 if (publicchannel_name) {
3737 if(!IsChannelName(publicchannel_name)) {
3738 log_module(HS_LOG, LOG_ERROR, "%s has an invalid channel name.", key);
3741 if (!(hs->publicchan = GetChannel(publicchannel_name))) {
3742 hs->publicchan = AddChannel(publicchannel_name, now, NULL, NULL);
3743 AddChannelUser(hs->helpserv, hs->publicchan)->modes |= MODE_CHANOP;
3745 struct mod_chanmode change;
3746 mod_chanmode_init(&change);
3748 change.args[0].mode = MODE_CHANOP;
3749 change.args[0].u.member = AddChannelUser(hs->helpserv, hs->publicchan);
3750 mod_chanmode_announce(hs->helpserv, hs->publicchan, &change);
3754 raw_record = dict_find(GET_RECORD_OBJECT(br), KEY_PAGE_DEST, NULL);
3755 switch (raw_record ? raw_record->type : RECDB_INVALID) {
3757 set_page_target(hs, PGSRC_COMMAND, GET_RECORD_QSTRING(raw_record));
3758 pagesrc = PGSRC_COMMAND + 1;
3760 case RECDB_STRING_LIST: {
3761 struct string_list *slist = GET_RECORD_STRING_LIST(raw_record);
3762 for (pagesrc=0; (pagesrc<slist->used) && (pagesrc<PGSRC_COUNT); pagesrc++) {
3763 const char *dest = slist->list[pagesrc];
3764 set_page_target(hs, pagesrc, strcmp(dest, "*") ? dest : NULL);
3769 set_page_target(hs, PGSRC_COMMAND, NULL);
3770 pagesrc = PGSRC_COMMAND + 1;
3773 while (pagesrc < PGSRC_COUNT) {
3774 set_page_target(hs, pagesrc++, hs->page_targets[PGSRC_COMMAND] ? hs->page_targets[PGSRC_COMMAND]->name : NULL);
3777 for (pagesrc=0; pagesrc<PGSRC_COUNT; pagesrc++) {
3778 str = database_get_data(GET_RECORD_OBJECT(br), page_sources[pagesrc].db_name, RECDB_QSTRING);
3779 hs->page_types[pagesrc] = str ? page_type_from_name(str) : PAGE_NONE;
3782 for (msgtype=0; msgtype<MSGTYPE_COUNT; msgtype++) {
3783 str = database_get_data(GET_RECORD_OBJECT(br), message_types[msgtype].db_name, RECDB_QSTRING);
3784 hs->messages[msgtype] = str ? strdup(str) : NULL;
3787 for (inttype=0; inttype<INTERVAL_COUNT; inttype++) {
3788 str = database_get_data(GET_RECORD_OBJECT(br), interval_types[inttype].db_name, RECDB_QSTRING);
3789 hs->intervals[inttype] = str ? ParseInterval(str) : 0;
3791 if (hs->intervals[INTERVAL_WHINE_INTERVAL])
3792 timeq_add(now + hs->intervals[INTERVAL_WHINE_INTERVAL], run_whine_interval, hs);
3793 if (hs->intervals[INTERVAL_EMPTY_INTERVAL])
3794 timeq_add(now + hs->intervals[INTERVAL_EMPTY_INTERVAL], run_empty_interval, hs);
3796 for (persisttype=0; persisttype<PERSIST_T_COUNT; persisttype++) {
3797 str = database_get_data(GET_RECORD_OBJECT(br), persistence_types[persisttype].db_name, RECDB_QSTRING);
3798 hs->persist_lengths[persisttype] = str ? persistence_from_name(str) : PERSIST_QUIT;
3800 str = database_get_data(GET_RECORD_OBJECT(br), KEY_NOTIFICATION, RECDB_QSTRING);
3801 hs->notify = str ? notification_from_name(str) : NOTIFY_NONE;
3802 str = database_get_data(GET_RECORD_OBJECT(br), KEY_REGISTERED, RECDB_QSTRING);
3804 hs->registered = strtol(str, NULL, 0);
3805 str = database_get_data(GET_RECORD_OBJECT(br), KEY_IDWRAP, RECDB_QSTRING);
3807 hs->id_wrap = strtoul(str, NULL, 0);
3808 str = database_get_data(GET_RECORD_OBJECT(br), KEY_REQ_MAXLEN, RECDB_QSTRING);
3810 hs->req_maxlen = strtoul(str, NULL, 0);
3811 str = database_get_data(GET_RECORD_OBJECT(br), KEY_LAST_REQUESTID, RECDB_QSTRING);
3813 hs->last_requestid = strtoul(str, NULL, 0);
3814 str = database_get_data(GET_RECORD_OBJECT(br), KEY_PRIVMSG_ONLY, RECDB_QSTRING);
3815 hs->privmsg_only = str ? enabled_string(str) : 0;
3816 str = database_get_data(GET_RECORD_OBJECT(br), KEY_REQ_ON_JOIN, RECDB_QSTRING);
3817 hs->req_on_join = str ? enabled_string(str) : 0;
3818 str = database_get_data(GET_RECORD_OBJECT(br), KEY_AUTO_VOICE, RECDB_QSTRING);
3819 hs->auto_voice = str ? enabled_string(str) : 0;
3820 str = database_get_data(GET_RECORD_OBJECT(br), KEY_AUTO_JOIN, RECDB_QSTRING);
3821 hs->auto_join = str ? enabled_string(str) : 0;
3822 str = database_get_data(GET_RECORD_OBJECT(br), KEY_AUTO_DEVOICE, RECDB_QSTRING);
3823 hs->auto_devoice = str ? enabled_string(str) : 0;
3824 str = database_get_data(GET_RECORD_OBJECT(br), KEY_LAST_ACTIVE, RECDB_QSTRING);
3825 hs->last_active = str ? strtoul(str, NULL, 0) : now;
3827 dict_foreach(users, user_read_helper, hs);
3829 requests = database_get_data(GET_RECORD_OBJECT(br), KEY_REQUESTS, RECDB_OBJECT);
3831 dict_foreach(requests, request_read_helper, hs);
3837 helpserv_saxdb_read(struct dict *conf_db) {
3841 if ((object = database_get_data(conf_db, KEY_BOTS, RECDB_OBJECT))) {
3842 dict_foreach(object, helpserv_bot_read, NULL);
3845 str = database_get_data(conf_db, KEY_LAST_STATS_UPDATE, RECDB_QSTRING);
3846 last_stats_update = str ? strtoul(str, NULL, 0) : now;
3850 static void helpserv_conf_read(void) {
3854 if (!(conf_node = conf_get_data(HELPSERV_CONF_NAME, RECDB_OBJECT))) {
3855 log_module(HS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type", HELPSERV_CONF_NAME);
3859 str = database_get_data(conf_node, "db_backup_freq", RECDB_QSTRING);
3860 helpserv_conf.db_backup_frequency = str ? ParseInterval(str) : 7200;
3862 str = database_get_data(conf_node, "description", RECDB_QSTRING);
3863 helpserv_conf.description = str ? str : "Help Queue Manager";
3865 str = database_get_data(conf_node, "reqlogfile", RECDB_QSTRING);
3866 if (str && strlen(str))
3867 helpserv_conf.reqlogfile = str;
3869 helpserv_conf.reqlogfile = NULL;
3871 str = database_get_data(conf_node, "expiration", RECDB_QSTRING);
3872 helpserv_conf.expire_age = ParseInterval(str ? str : "60d");
3873 str = database_get_data(conf_node, "modstats_level", RECDB_QSTRING);
3874 helpserv_conf.modstats_level = str ? strtoul(str, NULL, 0) : 850;
3875 str = database_get_data(conf_node, "user_escape", RECDB_QSTRING);
3876 helpserv_conf.user_escape = str ? str[0] : '@';
3882 if (helpserv_conf.reqlogfile
3883 && !(reqlog_f = fopen(helpserv_conf.reqlogfile, "a"))) {
3884 log_module(HS_LOG, LOG_ERROR, "Unable to open request logfile (%s): %s", helpserv_conf.reqlogfile, strerror(errno));
3888 static struct helpserv_cmd *
3889 helpserv_define_func(const char *name, helpserv_func_t *func, enum helpserv_level level, long flags) {
3890 struct helpserv_cmd *cmd = calloc(1, sizeof(struct helpserv_cmd));
3892 cmd->access = level;
3896 dict_insert(helpserv_func_dict, name, cmd);
3901 /* Drop requests that persist until part when a user leaves the chan */
3902 static void handle_part(struct modeNode *mn, UNUSED_ARG(const char *reason)) {
3903 struct helpserv_botlist *botlist;
3904 struct helpserv_userlist *userlist;
3905 const int from_opserv = 0; /* for helpserv_notice */
3908 if ((botlist = dict_find(helpserv_bots_bychan_dict, mn->channel->name, NULL))) {
3909 for (i=0; i < botlist->used; i++) {
3910 struct helpserv_bot *hs;
3913 hs = botlist->list[i];
3916 if (hs->persist_lengths[PERSIST_T_REQUEST] != PERSIST_PART)
3919 for (it=dict_first(hs->requests); it; it=iter_next(it)) {
3920 struct helpserv_request *req = iter_data(it);
3922 if (mn->user != req->user)
3924 if (GetUserMode(hs->helpchan, mn->user)) //publicchan
3926 if (req->text->used) {
3927 helpserv_message(hs, mn->user, MSGTYPE_REQ_DROPPED);
3928 helpserv_msguser(mn->user, "HSMSG_REQ_DROPPED_PART", mn->channel->name, req->id);
3929 if (req->helper && (hs->notify >= NOTIFY_DROP))
3930 helpserv_notify(req->helper, "HSMSG_NOTIFY_REQ_DROP_PART", req->id, mn->user->nick);
3932 helpserv_log_request(req, "Dropped");
3933 dict_remove(hs->requests, iter_key(it));
3939 if (mn->user->handle_info && (userlist = dict_find(helpserv_users_byhand_dict, mn->user->handle_info->handle, NULL))) {
3940 for (i=0; i < userlist->used; i++) {
3941 struct helpserv_user *hs_user = userlist->list[i];
3942 struct helpserv_bot *hs = hs_user->hs;
3945 if ((hs->helpserv == NULL) || (hs->helpchan != mn->channel) || find_handle_in_channel(hs->helpchan, mn->user->handle_info, mn->user))
3948 /* In case of the clock being set back for whatever reason,
3949 * minimize penalty. Don't duplicate this in handle_quit because
3950 * when users quit, handle_part is called for every channel first.
3952 if (hs_user->join_time && (hs_user->join_time < now)) {
3953 hs_user->time_per_week[0] += (unsigned int)(now - hs_user->join_time);
3954 hs_user->time_per_week[4] += (unsigned int)(now - hs_user->join_time);
3956 hs_user->join_time = 0;
3958 for (it=dict_first(hs->requests); it; it=iter_next(it)) {
3959 struct helpserv_request *req=iter_data(it);
3961 if ((hs->persist_lengths[PERSIST_T_HELPER] == PERSIST_PART)
3962 && (req->helper == hs_user)) {
3963 char our_reason[CHANNELLEN + 8];
3964 sprintf(our_reason, "parted %s", mn->channel->name);
3965 helpserv_page_helper_gone(hs, req, our_reason);
3969 if (hs->intervals[INTERVAL_EMPTY_INTERVAL] && hs_user->level >= HlHelper) {
3972 if ((num_trials = find_helpchan_helpers(hs)) >= 0) {
3973 unsigned int num_unh;
3974 struct helpserv_request *unh;
3976 for (num_unh=0, unh=hs->unhandled; unh; num_unh++)
3977 unh = unh->next_unhandled;
3980 helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_FIRSTONLYTRIALALERT", hs->helpchan->name, mn->user->nick, num_trials, num_unh);
3982 helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_FIRSTEMPTYALERT", hs->helpchan->name, mn->user->nick, num_unh);
3984 if (num_unh || !hs->req_on_join) {
3985 timeq_del(0, run_empty_interval, hs, TIMEQ_IGNORE_WHEN);
3986 timeq_add(now + hs->intervals[INTERVAL_EMPTY_INTERVAL], run_empty_interval, hs);
3994 /* Drop requests that persist until part or quit when a user quits. Otherwise
3995 * set req->user to null (it's no longer valid) if they have a handle,
3996 * and drop it if they don't (nowhere to store the request).
3998 * Unassign requests where req->helper persists until the helper parts or
4000 static void handle_quit(struct userNode *user, UNUSED_ARG(struct userNode *killer), UNUSED_ARG(const char *why)) {
4001 struct helpserv_reqlist *reqlist;
4002 struct helpserv_userlist *userlist;
4005 if (IsLocal(user)) {
4006 struct helpserv_bot *hs;
4007 if ((hs = dict_find(helpserv_bots_dict, user->nick, NULL))) {
4008 hs->helpserv = NULL;
4013 if ((reqlist = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL))) {
4015 for (i=0; i < n; i++) {
4016 struct helpserv_request *req = reqlist->list[0];
4018 if ((req->hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_QUIT) || !req->handle) {
4020 sprintf(buf, "%lu", req->id);
4022 if (req->helper && (req->hs->notify >= NOTIFY_DROP))
4023 helpserv_notify(req->helper, "HSMSG_NOTIFY_REQ_DROP_QUIT", req->id, req->user->nick);
4025 helpserv_log_request(req, "Dropped");
4026 dict_remove(req->hs->requests, buf);
4029 req->parent_nick_list = NULL;
4030 helpserv_reqlist_remove(reqlist, req);
4032 if (req->helper && (req->hs->notify >= NOTIFY_USER))
4033 helpserv_notify(req->helper, "HSMSG_NOTIFY_USER_QUIT", req->id, user->nick);
4037 dict_remove(helpserv_reqs_bynick_dict, user->nick);
4040 if (user->handle_info && (userlist = dict_find(helpserv_users_byhand_dict, user->handle_info->handle, NULL))) {
4041 for (i=0; i < userlist->used; i++) {
4042 struct helpserv_user *hs_user = userlist->list[i];
4043 struct helpserv_bot *hs = hs_user->hs;
4046 if ((hs->helpserv == NULL) || user->next_authed || (user->handle_info->users != user))
4049 for (it=dict_first(hs->requests); it; it=iter_next(it)) {
4050 struct helpserv_request *req=iter_data(it);
4052 if ((hs->persist_lengths[PERSIST_T_HELPER] == PERSIST_QUIT) && (req->helper == hs_user)) {
4053 helpserv_page_helper_gone(hs, req, "disconnected");
4060 static void associate_requests_bybot(struct helpserv_bot *hs, struct userNode *user, int force_greet) {
4061 struct helpserv_reqlist *reqlist, *hand_reqlist=NULL;
4062 struct helpserv_request *newest=NULL, *nicknewest=NULL;
4065 if (!(user->handle_info && (hand_reqlist = dict_find(helpserv_reqs_byhand_dict, user->handle_info->handle, NULL))) && !force_greet) {
4069 reqlist = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL);
4072 for (i=0; i < hand_reqlist->used; i++) {
4073 struct helpserv_request *req=hand_reqlist->list[i];
4075 if (req->user || (req->hs != hs))
4080 reqlist = helpserv_reqlist_alloc();
4081 dict_insert(helpserv_reqs_bynick_dict, user->nick, reqlist);
4083 req->parent_nick_list = reqlist;
4084 helpserv_reqlist_append(reqlist, req);
4086 if (req->helper && (hs->notify >= NOTIFY_USER))
4087 helpserv_notify(req->helper, "HSMSG_NOTIFY_USER_FOUND", req->id, user->nick);
4089 if (!newest || (newest->opened < req->opened))
4094 /* If it's supposed to force a greeting, only bail out if there are no
4095 * requests at all. If it's not supposed to force a greeting, bail out if
4096 * nothing was changed. */
4097 if (!(newest || (force_greet && reqlist)))
4100 /* Possible conditions here:
4101 * 1. newest == NULL, force_greeting == 1, reqlist != NULL
4102 * 2. newest != NULL, force_greeting doesn't matter, reqlist != NULL */
4104 /* Figure out which request will get their next message */
4105 for (i=0; i < reqlist->used; i++) {
4106 struct helpserv_request *req=reqlist->list[i];
4111 if (!nicknewest || (nicknewest->opened < req->opened))
4115 if (hs->auto_voice && req->helper)
4117 struct mod_chanmode change;
4118 mod_chanmode_init(&change);
4120 change.args[0].mode = MODE_VOICE;
4121 if ((change.args[0].u.member = GetUserMode(hs->helpchan, user)))
4122 mod_chanmode_announce(hs->helpserv, hs->helpchan, &change);
4126 if ((force_greet && nicknewest) || (newest && (nicknewest == newest))) {
4127 /* Let the user know. Either the user is forced to be greeted, or the
4128 * above has changed which request will get their next message. */
4129 //helpserv_msguser(user, "HSMSG_GREET_EXISTING_REQ", hs->helpchan->name, nicknewest->id);
4133 static void associate_requests_bychan(struct chanNode *chan, struct userNode *user, int force_greet) {
4134 struct helpserv_botlist *botlist;
4137 if (!(botlist = dict_find(helpserv_bots_bychan_dict, chan->name, NULL)))
4140 for (i=0; i < botlist->used; i++)
4141 associate_requests_bybot(botlist->list[i], user, force_greet);
4145 /* Greet users upon joining a helpserv channel (if greeting is set) and set
4146 * req->user to the user joining for all requests owned by the user's handle
4147 * (if any) with a req->user == NULL */
4148 static int handle_join(struct modeNode *mNode) {
4149 struct userNode *user = mNode->user;
4150 struct chanNode *chan = mNode->channel;
4151 struct helpserv_botlist *botlist;
4153 const int from_opserv = 0; /* for helpserv_notice */
4158 if (!(botlist = dict_find(helpserv_bots_bychan_dict, chan->name, NULL)))
4161 for (i=0; i < botlist->used; i++) {
4162 struct helpserv_bot *hs=botlist->list[i];
4164 if (user->handle_info) {
4165 struct helpserv_user *hs_user;
4167 if ((hs_user = dict_find(hs->users, user->handle_info->handle, NULL))) {
4168 if (!hs_user->join_time)
4169 hs_user->join_time = now;
4171 if (hs_user->level >= HlHelper && hs->intervals[INTERVAL_EMPTY_INTERVAL] && hs->helpchan_empty) {
4172 hs->helpchan_empty = 0;
4173 timeq_del(0, run_empty_interval, hs, TIMEQ_IGNORE_WHEN);
4174 helpserv_page(PGSRC_ALERT, "HSMSG_PAGE_EMPTYNOMORE", user->nick, hs->helpchan->name);
4176 continue; /* Don't want helpers to have request-on-join */
4180 if (self->burst && !hs->req_on_join)
4183 associate_requests_bybot(hs, user, 1);
4185 helpserv_message(hs, user, MSGTYPE_GREETING);
4187 /* Make sure this is at the end (because of the continues) */
4188 if (hs->req_on_join) {
4189 struct helpserv_reqlist *reqlist;
4192 if ((reqlist = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL))) {
4193 for (j=0; j < reqlist->used; j++)
4194 if (reqlist->list[j]->hs == hs)
4196 if (j < reqlist->used)
4200 create_request(user, hs, 1);
4206 /* Update helpserv_reqs_bynick_dict upon nick change */
4207 static void handle_nickchange(struct userNode *user, const char *old_nick) {
4208 struct helpserv_reqlist *reqlist;
4211 if (!(reqlist = dict_find(helpserv_reqs_bynick_dict, old_nick, NULL)))
4214 /* Don't free the list when we switch it over to the new nick. */
4215 dict_remove2(helpserv_reqs_bynick_dict, old_nick, 1);
4216 dict_insert(helpserv_reqs_bynick_dict, user->nick, reqlist);
4218 for (i=0; i < reqlist->used; i++) {
4219 struct helpserv_request *req=reqlist->list[i];
4221 if (req->helper && (req->hs->notify >= NOTIFY_USER))
4222 helpserv_notify(req->helper, "HSMSG_NOTIFY_USER_NICK", req->id, old_nick, user->nick);
4226 /* Also update helpserv_reqs_byhand_dict upon handle rename */
4227 static void handle_nickserv_rename(struct handle_info *handle, const char *old_handle) {
4228 struct helpserv_reqlist *reqlist;
4229 struct helpserv_userlist *userlist;
4232 /* First, rename the handle in the requests dict */
4233 if ((reqlist = dict_find(helpserv_reqs_byhand_dict, old_handle, NULL))) {
4234 /* Don't free the list */
4235 dict_remove2(helpserv_reqs_byhand_dict, old_handle, 1);
4236 dict_insert(helpserv_reqs_byhand_dict, handle->handle, reqlist);
4239 /* Second, rename the handle in the users dict */
4240 if ((userlist = dict_find(helpserv_users_byhand_dict, old_handle, NULL))) {
4241 dict_remove2(helpserv_users_byhand_dict, old_handle, 1);
4243 for (i=0; i < userlist->used; i++)
4244 dict_remove2(userlist->list[i]->hs->users, old_handle, 1);
4246 dict_insert(helpserv_users_byhand_dict, handle->handle, userlist);
4247 for (i=0; i < userlist->used; i++)
4248 dict_insert(userlist->list[i]->hs->users, handle->handle, userlist->list[i]);
4252 for (i=0; i < reqlist->used; i++) {
4253 struct helpserv_request *req=reqlist->list[i];
4255 if (req->helper && (req->hs->notify >= NOTIFY_HANDLE))
4256 helpserv_notify(req->helper, "HSMSG_NOTIFY_HAND_RENAME", req->id, old_handle, handle->handle);
4261 /* Deals with two cases:
4262 * 1. No handle -> handle
4263 * - Bots with a request assigned to both the user (w/o handle) and the
4264 * handle can exist in this case. When a message is sent,
4265 * helpserv_usermsg will append it to the most recently opened request.
4266 * - Requests assigned to the handle are NOT assigned to the user, since
4267 * multiple people can auth to the same handle at once. Wait for them to
4268 * join / privmsg before setting req->user.
4269 * 2. Handle -> handle
4270 * - Generally avoided, but sometimes the code may allow this.
4271 * - Requests that persist only until part/quit are brought along to the
4273 * - Requests that persist until closed (stay saved with the handle) are
4274 * left with the old handle. This is to prevent the confusing situation
4275 * where some requests are carried over to the new handle, and some are
4276 * left (because req->handle is the same for all of them, but only some
4277 * have req->user set).
4278 * - In either of the above cases, if a user is on a bot's userlist and has
4279 * requests assigned to them, it will give them a list. */
4280 static void handle_nickserv_auth(struct userNode *user, struct handle_info *old_handle) {
4281 struct helpserv_reqlist *reqlist, *dellist=NULL, *hand_reqlist, *oldhand_reqlist;
4282 struct helpserv_userlist *userlist;
4285 const int from_opserv = 0; /* for helpserv_notice */
4287 if (!user->handle_info)
4288 return; /* Authed user is quitting */
4290 if ((userlist = dict_find(helpserv_users_byhand_dict, user->handle_info->handle, NULL))) {
4291 for (i=0; i < userlist->used; i++) {
4292 struct helpserv_user *hs_user = userlist->list[i];
4293 struct helpserv_bot *hs = hs_user->hs;
4294 struct helpserv_reqlist helper_reqs;
4295 struct helpfile_table tbl;
4297 if (!hs_user->join_time && find_handle_in_channel(hs->helpchan, hs_user->handle, NULL))
4298 hs_user->join_time = now;
4300 helpserv_reqlist_init(&helper_reqs);
4302 for (it=dict_first(hs->requests); it; it=iter_next(it)) {
4303 struct helpserv_request *req=iter_data(it);
4305 if (req->helper == hs_user)
4306 helpserv_reqlist_append(&helper_reqs, req);
4309 if (helper_reqs.used) {
4310 tbl.length = helper_reqs.used+1;
4312 tbl.flags = TABLE_NO_FREE;
4313 tbl.contents = alloca(tbl.length * sizeof(*tbl.contents));
4314 tbl.contents[0] = alloca(tbl.width * sizeof(**tbl.contents));
4315 tbl.contents[0][0] = "Bot";
4316 tbl.contents[0][1] = "ID#";
4317 tbl.contents[0][2] = "Nick";
4318 tbl.contents[0][3] = "Account";
4319 tbl.contents[0][4] = "Opened";
4321 for (j=1; j <= helper_reqs.used; j++) {
4322 struct helpserv_request *req=helper_reqs.list[j-1];
4323 char reqid[12], timestr[MAX_LINE_SIZE];
4326 tbl.contents[j] = alloca(tbl.width * sizeof(**tbl.contents));
4327 tbl.contents[j][0] = req->hs->helpserv->nick;
4328 sprintf(reqid, "%lu", req->id);
4329 tbl.contents[j][1] = strdup(reqid);
4330 tbl.contents[j][2] = req->user ? req->user->nick : "Not online";
4331 tbl.contents[j][3] = req->handle ? req->handle->handle : "Not authed";
4333 strftime(timestr, MAX_LINE_SIZE, HSFMT_TIME, localtime(&feh));
4334 tbl.contents[j][4] = strdup(timestr);
4337 helpserv_notice(user, "HSMSG_REQLIST_AUTH");
4338 table_send(hs->helpserv, user->nick, 0, NULL, tbl);
4340 for (j=1; j <= helper_reqs.used; j++) {
4341 free((char *)tbl.contents[j][1]);
4342 free((char *)tbl.contents[j][4]);
4346 helpserv_reqlist_clean(&helper_reqs);
4351 if (!(reqlist = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL))) {
4352 for (i=0; i < user->channels.used; i++)
4353 associate_requests_bychan(user->channels.list[i]->channel, user, 0);
4357 if (!(hand_reqlist = dict_find(helpserv_reqs_byhand_dict, user->handle_info->handle, NULL))) {
4358 hand_reqlist = helpserv_reqlist_alloc();
4359 dict_insert(helpserv_reqs_byhand_dict, user->handle_info->handle, hand_reqlist);
4363 dellist = helpserv_reqlist_alloc();
4364 oldhand_reqlist = dict_find(helpserv_reqs_byhand_dict, old_handle->handle, NULL);
4366 oldhand_reqlist = NULL;
4369 for (i=0; i < reqlist->used; i++) {
4370 struct helpserv_request *req = reqlist->list[i];
4371 struct helpserv_bot *hs=req->hs;
4373 if (!old_handle || hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_PART || hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_QUIT) {
4374 /* The request needs to be assigned to the new handle; either it
4375 * only persists until part/quit (so it makes sense to keep it as
4376 * close to the user as possible, and if it's made persistent later
4377 * then it's attached to the new handle) or there is no old handle.
4380 req->handle = user->handle_info;
4382 req->parent_hand_list = hand_reqlist;
4383 helpserv_reqlist_append(hand_reqlist, req);
4385 if (oldhand_reqlist) {
4386 if (oldhand_reqlist->used == 1) {
4387 dict_remove(helpserv_reqs_byhand_dict, old_handle->handle);
4388 oldhand_reqlist = NULL;
4390 helpserv_reqlist_remove(oldhand_reqlist, req);
4395 char buf[CHANNELLEN + 14];
4397 if (hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_PART) {
4398 sprintf(buf, "part channel %s", hs->helpchan->name);
4400 strcpy(buf, "quit irc");
4403 helpserv_msguser(user, "HSMSG_REQ_AUTH_MOVED", user->handle_info->handle, hs->helpchan->name, req->id, old_handle->handle, buf);
4404 if (req->helper && (hs->notify >= NOTIFY_HANDLE))
4405 helpserv_notify(req->helper, "HSMSG_NOTIFY_HAND_MOVE", req->id, user->handle_info->handle, old_handle->handle);
4407 if (req->helper && (hs->notify >= NOTIFY_HANDLE))
4408 helpserv_notify(req->helper, "HSMSG_NOTIFY_HAND_AUTH", req->id, user->nick, user->handle_info->handle);
4412 req->parent_nick_list = NULL;
4413 /* Would rather not mess with the list while iterating through
4415 helpserv_reqlist_append(dellist, req);
4417 helpserv_msguser(user, "HSMSG_REQ_AUTH_STUCK", user->handle_info->handle, hs->helpchan->name, req->id, old_handle->handle);
4418 if (req->helper && (hs->notify >= NOTIFY_HANDLE))
4419 helpserv_notify(req->helper, "HSMSG_NOTIFY_HAND_STUCK", req->id, user->nick, user->handle_info->handle, old_handle->handle);
4424 if (dellist->used) {
4425 if (dellist->used == reqlist->used) {
4426 dict_remove(helpserv_reqs_bynick_dict, user->nick);
4428 for (i=0; i < dellist->used; i++)
4429 helpserv_reqlist_remove(reqlist, dellist->list[i]);
4432 helpserv_reqlist_free(dellist);
4435 for (i=0; i < user->channels.used; i++)
4436 associate_requests_bychan(user->channels.list[i]->channel, user, 0);
4440 /* Disassociate all requests from the handle. If any have req->user == NULL
4441 * then give them to the user doing the unregistration (if not an oper/helper)
4442 * otherwise the first nick it finds authed (it lets them know about this). If
4443 * there are no users authed to the handle online, the requests are lost. This
4444 * may result in the user having >1 request/bot, and messages go to the most
4445 * recently opened request.
4447 * Also, remove the user from all bots that it has access in.
4448 * helpserv_del_user() will take care of unassigning the requests. */
4449 static void handle_nickserv_unreg(struct userNode *user, struct handle_info *handle) {
4450 struct helpserv_reqlist *hand_reqlist;
4451 struct helpserv_userlist *userlist;
4453 const int from_opserv = 0; /* for helpserv_notice */
4454 struct helpserv_bot *hs; /* for helpserv_notice */
4456 if ((userlist = dict_find(helpserv_users_byhand_dict, handle->handle, NULL))) {
4459 /* Each time helpserv_del_user is called, that entry is going to be
4460 * taken out of userlist... so this should cope with that */
4461 for (i=0; i < n; i++) {
4462 struct helpserv_user *hs_user=userlist->list[0];
4463 helpserv_del_user(hs_user->hs, hs_user);
4467 if (!(hand_reqlist = dict_find(helpserv_reqs_byhand_dict, handle->handle, NULL))) {
4471 n = hand_reqlist->used;
4472 for (i=0; i < n; i++) {
4473 struct helpserv_request *req=hand_reqlist->list[0];
4477 req->parent_hand_list = NULL;
4478 helpserv_reqlist_remove(hand_reqlist, req);
4479 if (user && req->helper && (hs->notify >= NOTIFY_HANDLE))
4480 helpserv_notify(req->helper, "HSMSG_NOTIFY_HAND_UNREG", req->id, handle->handle, user->nick);
4484 /* This is probably an expire. Silently remove everything. */
4487 if (req->helper && (hs->notify >= NOTIFY_DROP))
4488 helpserv_notify(req->helper, "HSMSG_NOTIFY_REQ_DROP_UNREGGED", req->id, req->handle->handle);
4489 sprintf(buf, "%lu", req->id);
4490 helpserv_log_request(req, "Account unregistered");
4491 dict_remove(req->hs->requests, buf);
4492 } else if (user->handle_info == handle) {
4494 if (!(req->parent_nick_list = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL))) {
4495 req->parent_nick_list = helpserv_reqlist_alloc();
4496 dict_insert(helpserv_reqs_bynick_dict, user->nick, req->parent_nick_list);
4498 helpserv_reqlist_append(req->parent_nick_list, req);
4500 if (hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_CLOSE)
4501 helpserv_msguser(req->user, "HSMSG_REQ_WARN_UNREG", handle->handle, hs->helpchan->name, req->id);
4503 if (handle->users) {
4504 req->user = handle->users;
4506 if (!(req->parent_nick_list = dict_find(helpserv_reqs_bynick_dict, req->user->nick, NULL))) {
4507 req->parent_nick_list = helpserv_reqlist_alloc();
4508 dict_insert(helpserv_reqs_bynick_dict, req->user->nick, req->parent_nick_list);
4510 helpserv_reqlist_append(req->parent_nick_list, req);
4512 helpserv_msguser(req->user, "HSMSG_REQ_ASSIGNED_UNREG", handle->handle, hs->helpchan->name, req->id);
4513 if (req->helper && (hs->notify >= NOTIFY_USER))
4514 helpserv_notify(req->helper, "HSMSG_NOTIFY_USER_MOVE", req->id, handle->handle, req->user->nick);
4518 helpserv_notice(user, "HSMSG_REQ_DROPPED_UNREG", handle->handle, hs->helpchan->name, req->id);
4519 if (req->helper && (hs->notify >= NOTIFY_DROP))
4520 helpserv_notify(req->helper, "HSMSG_NOTIFY_REQ_DROP_UNREGGED", req->id, req->handle->handle);
4521 sprintf(buf, "%lu", req->id);
4522 helpserv_log_request(req, "Account unregistered");
4523 dict_remove(req->hs->requests, buf);
4529 dict_remove(helpserv_reqs_byhand_dict, handle->handle);
4532 static void handle_nickserv_merge(struct userNode *user, struct handle_info *handle_to, struct handle_info *handle_from) {
4533 struct helpserv_reqlist *reqlist_from, *reqlist_to;
4536 reqlist_to = dict_find(helpserv_reqs_byhand_dict, handle_to->handle, NULL);
4538 if ((reqlist_from = dict_find(helpserv_reqs_byhand_dict, handle_from->handle, NULL))) {
4539 for (i=0; i < reqlist_from->used; i++) {
4540 struct helpserv_request *req=reqlist_from->list[i];
4543 reqlist_to = helpserv_reqlist_alloc();
4544 dict_insert(helpserv_reqs_byhand_dict, handle_to->handle, reqlist_to);
4546 req->parent_hand_list = reqlist_to;
4547 req->handle = handle_to;
4548 helpserv_reqlist_append(reqlist_to, req);
4550 dict_remove(helpserv_reqs_byhand_dict, handle_from->handle);
4554 for (i=0; i < reqlist_to->used; i++) {
4555 struct helpserv_request *req=reqlist_to->list[i];
4557 if (req->helper && (req->hs->notify >= NOTIFY_HANDLE)) {
4558 helpserv_notify(req->helper, "HSMSG_NOTIFY_HAND_MERGE", req->id, handle_to->handle, handle_from->handle, user->nick);
4564 static void handle_nickserv_allowauth(struct userNode *user, struct userNode *target, struct handle_info *handle) {
4565 struct helpserv_reqlist *reqlist;
4568 if ((reqlist = dict_find(helpserv_reqs_bynick_dict, target->nick, NULL))) {
4569 for (i=0; i < reqlist->used; i++) {
4570 struct helpserv_request *req=reqlist->list[i];
4572 if (req->helper && (req->hs->notify >= NOTIFY_HANDLE)) {
4574 helpserv_notify(req->helper, "HSMSG_NOTIFY_ALLOWAUTH", req->id, target->nick, user->nick, handle->handle);
4576 helpserv_notify(req->helper, "HSMSG_NOTIFY_UNALLOWAUTH", req->id, target->nick, user->nick);
4583 static void handle_nickserv_failpw(struct userNode *user, struct handle_info *handle) {
4584 struct helpserv_reqlist *reqlist;
4587 if ((reqlist = dict_find(helpserv_reqs_bynick_dict, user->nick, NULL))) {
4588 for (i=0; i < reqlist->used; i++) {
4589 struct helpserv_request *req=reqlist->list[i];
4590 if (req->helper && (req->hs->notify >= NOTIFY_HANDLE))
4591 helpserv_notify(req->helper, "HSMSG_NOTIFY_FAILPW", req->id, user->nick, handle->handle);
4596 static unsigned long helpserv_next_stats(time_t after_when) {
4597 struct tm *timeinfo = localtime(&after_when);
4599 /* This works because mktime(3) says it will accept out-of-range values
4600 * and fix them for us. tm_wday and tm_yday are ignored. */
4601 timeinfo->tm_mday++;
4603 /* We want to run stats at midnight (local time). */
4604 timeinfo->tm_sec = timeinfo->tm_min = timeinfo->tm_hour = 0;
4606 return mktime(timeinfo);
4609 /* If data != NULL, then don't add to the timeq */
4610 static void helpserv_run_stats(unsigned long when) {
4611 struct helpserv_bot *hs;
4612 struct helpserv_user *hs_user;
4616 dict_iterator_t it, it2;
4618 last_stats_update = when;
4620 day = localtime(&feh)->tm_wday;
4621 for (it=dict_first(helpserv_bots_dict); it; it=iter_next(it)) {
4624 for (it2=dict_first(hs->users); it2; it2=iter_next(it2)) {
4625 hs_user = iter_data(it2);
4627 /* Skip the helper if it's not their week-start day. */
4628 if (hs_user->week_start != day)
4631 /* Adjust their credit if they are in-channel at rollover. */
4632 if (hs_user->join_time) {
4633 hs_user->time_per_week[0] += when - hs_user->join_time;
4634 hs_user->time_per_week[4] += when - hs_user->join_time;
4635 hs_user->join_time = when;
4638 /* Shift everything */
4639 for (i=3; i > 0; i--) {
4640 hs_user->time_per_week[i] = hs_user->time_per_week[i-1];
4641 hs_user->picked_up[i] = hs_user->picked_up[i-1];
4642 hs_user->closed[i] = hs_user->closed[i-1];
4643 hs_user->reassigned_from[i] = hs_user->reassigned_from[i-1];
4644 hs_user->reassigned_to[i] = hs_user->reassigned_to[i-1];
4647 /* Reset it for this week */
4648 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;
4653 static void helpserv_timed_run_stats(UNUSED_ARG(void *data)) {
4654 helpserv_run_stats(now);
4655 timeq_add(helpserv_next_stats(now), helpserv_timed_run_stats, data);
4659 helpserv_define_option(const char *name, helpserv_option_func_t *func) {
4660 dict_insert(helpserv_option_dict, name, func);
4663 static void helpserv_db_cleanup(void) {
4665 unreg_part_func(handle_part);
4666 unreg_del_user_func(handle_quit);
4667 close_helpfile(helpserv_helpfile);
4668 dict_delete(helpserv_func_dict);
4669 dict_delete(helpserv_option_dict);
4670 dict_delete(helpserv_usercmd_dict);
4671 dict_delete(helpserv_bots_dict);
4672 dict_delete(helpserv_bots_bychan_dict);
4673 dict_delete(helpserv_reqs_bynick_dict);
4674 dict_delete(helpserv_reqs_byhand_dict);
4675 dict_delete(helpserv_users_byhand_dict);
4681 int helpserv_init() {
4682 HS_LOG = log_register_type("HelpServ", "file:helpserv.log");
4683 conf_register_reload(helpserv_conf_read);
4685 helpserv_func_dict = dict_new();
4686 dict_set_free_data(helpserv_func_dict, free);
4687 helpserv_define_func("HELP", cmd_help, HlNone, CMD_NOT_OVERRIDE|CMD_IGNORE_EVENT);
4688 helpserv_define_func("LIST", cmd_list, HlTrial, CMD_NEED_BOT|CMD_IGNORE_EVENT);
4689 helpserv_define_func("NEXT", cmd_next, HlTrial, CMD_NEED_BOT|CMD_NEVER_FROM_OPSERV);
4690 helpserv_define_func("PICKUP", cmd_pickup, HlTrial, CMD_NEED_BOT|CMD_NEVER_FROM_OPSERV);
4691 helpserv_define_func("REASSIGN", cmd_reassign, HlManager, CMD_NEED_BOT|CMD_NEVER_FROM_OPSERV);
4692 helpserv_define_func("CLOSE", cmd_close, HlTrial, CMD_NEED_BOT|CMD_NEVER_FROM_OPSERV);
4693 helpserv_define_func("SHOW", cmd_show, HlTrial, CMD_NEED_BOT|CMD_IGNORE_EVENT);
4694 helpserv_define_func("ADDNOTE", cmd_addnote, HlTrial, CMD_NEED_BOT);
4695 helpserv_define_func("ADDOWNER", cmd_addowner, HlOper, CMD_NEED_BOT|CMD_FROM_OPSERV_ONLY);
4696 helpserv_define_func("DELOWNER", cmd_deluser, HlOper, CMD_NEED_BOT|CMD_FROM_OPSERV_ONLY);
4697 helpserv_define_func("ADDTRIAL", cmd_addtrial, HlManager, CMD_NEED_BOT);
4698 helpserv_define_func("ADDHELPER", cmd_addhelper, HlManager, CMD_NEED_BOT);
4699 helpserv_define_func("ADDMANAGER", cmd_addmanager, HlOwner, CMD_NEED_BOT);
4700 helpserv_define_func("GIVEOWNERSHIP", cmd_giveownership, HlOwner, CMD_NEED_BOT);
4701 helpserv_define_func("DELUSER", cmd_deluser, HlManager, CMD_NEED_BOT);
4702 helpserv_define_func("HELPERS", cmd_helpers, HlNone, CMD_NEED_BOT);
4703 helpserv_define_func("WLIST", cmd_wlist, HlNone, CMD_NEED_BOT);
4704 helpserv_define_func("MLIST", cmd_mlist, HlNone, CMD_NEED_BOT);
4705 helpserv_define_func("HLIST", cmd_hlist, HlNone, CMD_NEED_BOT);
4706 helpserv_define_func("TLIST", cmd_tlist, HlNone, CMD_NEED_BOT);
4707 helpserv_define_func("CLVL", cmd_clvl, HlManager, CMD_NEED_BOT);
4708 helpserv_define_func("PAGE", cmd_page, HlTrial, CMD_NEED_BOT);
4709 helpserv_define_func("SET", cmd_set, HlHelper, CMD_NEED_BOT);
4710 helpserv_define_func("STATS", cmd_stats, HlTrial, CMD_NEED_BOT);
4711 helpserv_define_func("STATSREPORT", cmd_statsreport, HlManager, CMD_NEED_BOT);
4712 helpserv_define_func("UNREGISTER", cmd_unregister, HlOwner, CMD_NEED_BOT);
4713 helpserv_define_func("READHELP", cmd_readhelp, HlOper, CMD_FROM_OPSERV_ONLY);
4714 helpserv_define_func("REGISTER", cmd_register, HlOper, CMD_FROM_OPSERV_ONLY);
4715 helpserv_define_func("MOVE", cmd_move, HlOper, CMD_FROM_OPSERV_ONLY|CMD_NEED_BOT);
4716 helpserv_define_func("BOTS", cmd_bots, HlOper, CMD_FROM_OPSERV_ONLY|CMD_IGNORE_EVENT);
4717 helpserv_define_func("EXPIRE", cmd_expire, HlOper, CMD_FROM_OPSERV_ONLY);
4718 helpserv_define_func("WEEKSTART", cmd_weekstart, HlTrial, CMD_NEED_BOT);
4719 helpserv_define_func("MODSTATS", cmd_modstats, HlOwner, CMD_NEED_BOT);
4721 helpserv_option_dict = dict_new();
4722 helpserv_define_option("PAGETARGET", opt_pagetarget_command);
4723 helpserv_define_option("ALERTPAGETARGET", opt_pagetarget_alert);
4724 helpserv_define_option("STATUSPAGETARGET", opt_pagetarget_status);
4725 helpserv_define_option("PAGE", opt_pagetype);
4726 helpserv_define_option("PAGETYPE", opt_pagetype);
4727 helpserv_define_option("ALERTPAGETYPE", opt_alert_page_type);
4728 helpserv_define_option("STATUSPAGETYPE", opt_status_page_type);
4729 helpserv_define_option("GREETING", opt_greeting);
4730 helpserv_define_option("REQOPENED", opt_req_opened);
4731 helpserv_define_option("REQASSIGNED", opt_req_assigned);
4732 helpserv_define_option("REQCLOSED", opt_req_closed);
4733 helpserv_define_option("IDLEDELAY", opt_idle_delay);
4734 helpserv_define_option("WHINEDELAY", opt_whine_delay);
4735 helpserv_define_option("WHINEINTERVAL", opt_whine_interval);
4736 helpserv_define_option("EMPTYINTERVAL", opt_empty_interval);
4737 helpserv_define_option("STALEDELAY", opt_stale_delay);
4738 helpserv_define_option("REQPERSIST", opt_request_persistence);
4739 helpserv_define_option("HELPERPERSIST", opt_helper_persistence);
4740 helpserv_define_option("NOTIFICATION", opt_notification);
4741 helpserv_define_option("REQMAXLEN", opt_req_maxlen);
4742 helpserv_define_option("IDWRAP", opt_id_wrap);
4743 helpserv_define_option("PRIVMSGONLY", opt_privmsg_only);
4744 helpserv_define_option("REQONJOIN", opt_req_on_join);
4745 helpserv_define_option("AUTOVOICE", opt_auto_voice);
4746 helpserv_define_option("AUTOJOIN", opt_auto_join);
4747 helpserv_define_option("AUTODEVOICE", opt_auto_devoice);
4748 helpserv_define_option("PUBLICCHAN", opt_publicchan);
4750 helpserv_usercmd_dict = dict_new();
4751 dict_insert(helpserv_usercmd_dict, "WAIT", usercmd_wait);
4753 helpserv_bots_dict = dict_new();
4754 dict_set_free_data(helpserv_bots_dict, helpserv_free_bot);
4756 helpserv_bots_bychan_dict = dict_new();
4757 dict_set_free_data(helpserv_bots_bychan_dict, helpserv_botlist_free);
4759 helpserv_reqs_bynick_dict = dict_new();
4760 dict_set_free_data(helpserv_reqs_bynick_dict, helpserv_reqlist_free);
4761 helpserv_reqs_byhand_dict = dict_new();
4762 dict_set_free_data(helpserv_reqs_byhand_dict, helpserv_reqlist_free);
4764 helpserv_users_byhand_dict = dict_new();
4765 dict_set_free_data(helpserv_users_byhand_dict, helpserv_userlist_free);
4767 saxdb_register("HelpServ", helpserv_saxdb_read, helpserv_saxdb_write);
4768 helpserv_helpfile_read();
4770 /* Make up for downtime... though this will only really affect the
4772 if (last_stats_update && (helpserv_next_stats(last_stats_update) < now)) {
4773 unsigned long statsrun = last_stats_update;
4774 while ((statsrun = helpserv_next_stats(statsrun)) < now)
4775 helpserv_run_stats(statsrun);
4777 timeq_add(helpserv_next_stats(now), helpserv_timed_run_stats, NULL);
4779 reg_join_func(handle_join);
4780 reg_part_func(handle_part); /* also deals with kick */
4781 reg_nick_change_func(handle_nickchange);
4782 reg_del_user_func(handle_quit);
4784 reg_auth_func(handle_nickserv_auth);
4785 reg_handle_rename_func(handle_nickserv_rename);
4786 reg_unreg_func(handle_nickserv_unreg);
4787 reg_allowauth_func(handle_nickserv_allowauth);
4788 reg_failpw_func(handle_nickserv_failpw);
4789 reg_handle_merge_func(handle_nickserv_merge);
4791 reg_exit_func(helpserv_db_cleanup);
4793 helpserv_module = module_register("helpserv", HS_LOG, HELPSERV_HELPFILE_NAME, helpserv_expand_variable);
4794 modcmd_register(helpserv_module, "helpserv", cmd_helpserv, 1, MODCMD_REQUIRE_AUTHED|MODCMD_NO_LOG|MODCMD_NO_DEFAULT_BIND, "level", "800", NULL);
4795 message_register_table(msgtab);
4800 helpserv_finalize(void) {