1 /* mod-helpserv.c - Support Helper assistant service
2 * Copyright 2002-2003 srvx Development Team
4 * This file is part of x3.
6 * x3 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 3 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 mod-helpserv.c
23 * - Get cmd_statsreport to sort by time and show requests closed
24 * - .. then make statsreport show weighted combination of time and requests closed :)
25 * - Allow manipulation of the persist types on a per-request basis... so
26 * if it's normally set to close requests after a user quits, but there
27 * is a long-term issue, then that single request can remain
38 #define HELPSERV_CONF_NAME "modules/helpserv"
39 #define HELPSERV_HELPFILE_NAME "mod-helpserv.help"
40 const char *helpserv_module_deps
[] = { NULL
};
43 #define KEY_BOTS "bots"
44 #define KEY_LAST_STATS_UPDATE "last_stats_update"
45 #define KEY_NICK "nick"
46 #define KEY_DB_BADCHANS "badchans"
47 #define KEY_HELP_CHANNEL "help_channel"
48 #define KEY_PAGE_DEST "page_dest"
49 #define KEY_CMDWORD "cmdword"
50 #define KEY_PERSIST_LENGTH "persist_length"
51 #define KEY_REGISTERED "registered"
52 #define KEY_REGISTRAR "registrar"
53 #define KEY_IDWRAP "id_wrap"
54 #define KEY_REQ_MAXLEN "req_maxlen"
55 #define KEY_LAST_REQUESTID "last_requestid"
56 #define KEY_HELPERS "users"
57 #define KEY_HELPER_LEVEL "level"
58 #define KEY_HELPER_HELPMODE "helpmode"
59 #define KEY_HELPER_WEEKSTART "weekstart"
60 #define KEY_HELPER_STATS "stats"
61 #define KEY_HELPER_STATS_TIME "time"
62 #define KEY_HELPER_STATS_PICKUP "picked_up"
63 #define KEY_HELPER_STATS_CLOSE "closed"
64 #define KEY_HELPER_STATS_REASSIGNFROM "reassign_from"
65 #define KEY_HELPER_STATS_REASSIGNTO "reassign_to"
66 #define KEY_REQUESTS "requests"
67 #define KEY_REQUEST_HELPER "helper"
68 #define KEY_REQUEST_ASSIGNED "assigned"
69 #define KEY_REQUEST_HANDLE "handle"
70 #define KEY_REQUEST_TEXT "text"
71 #define KEY_REQUEST_OPENED "opened"
72 #define KEY_REQUEST_NICK "nick"
73 #define KEY_REQUEST_USERHOST "userhost"
74 #define KEY_REQUEST_CLOSED "closed"
75 #define KEY_REQUEST_CLOSEREASON "closereason"
76 #define KEY_NOTIFICATION "notification"
77 #define KEY_PRIVMSG_ONLY "privmsg_only"
78 #define KEY_REQ_ON_JOIN "req_on_join"
79 #define KEY_AUTO_VOICE "auto_voice"
80 #define KEY_AUTO_DEVOICE "auto_devoice"
81 #define KEY_LAST_ACTIVE "last_active"
82 #define KEY_JOIN_TOTAL "join_total"
83 #define KEY_ALERT_NEW "alert_new"
84 #define KEY_SUSPENDED "suspended"
85 #define KEY_EXPIRY "expiry"
86 #define KEY_ISSUED "issued"
87 #define KEY_SUSPENDER "suspender"
88 #define KEY_REASON "reason"
91 #define HSFMT_TIME "%a, %d %b %Y %H:%M:%S %Z"
92 static const struct message_entry msgtab
[] = {
93 { "HSMSG_READHELP_SUCCESS", "Read HelpServ help database in "FMT_TIME_T
".%03ld seconds." },
94 { "HSMSG_INVALID_BOT", "This command requires a valid HelpServ bot name." },
95 { "HSMSG_ILLEGAL_CHANNEL", "$b%s$b is an illegal channel; cannot use it." },
96 { "HSMSG_INTERNAL_COMMAND", "$b%s$b appears to be an internal HelpServ command, sorry." },
97 { "HSMSG_NOT_IN_USERLIST", "%s lacks access to $b%s$b." },
98 { "HSMSG_LEVEL_TOO_LOW", "You lack access to this command." },
99 { "HSMSG_OPER_CMD", "This command can only be executed via $O." },
100 { "HSMSG_WTF_WHO_ARE_YOU", "$bUnable to find you on the %s userlist.$b This is a bug. Please report it." },
101 { "HSMSG_NO_USE_OPSERV", "This command cannot be used via $O. If you really need to use it, add yourself to the userlist." },
102 { "HSMSG_OPSERV_NEED_USER", "To use this command via $O, you must supply a user to target." },
103 { "HSMSG_PAGE_REQUEST", "Page from $b%s$b: $b%s$b" },
104 { "HSMSG_BAD_REQ_TYPE", "I don't know how to list $b%s$b requests." },
107 { "HSMSG_GREET_EXISTING_REQ", "Welcome back to %s. You have already opened request ID#%lu. Any messages you send to $S will be appended to that request." },
108 { "HSMSG_GREET_PRIVMSG_EXISTREQ", "Hello again. Your account has a previously opened request ID#%lu. This message and any further messages you send to $S will be appended to that request." },
109 { "HSMSG_REQUESTS_OPEN", "Hello. There are %d requests open. Use /msg %s LIST to view all requests, or use /msg %s NEXT to pickup the oldest request." },
111 /* User Management */
112 { "HSMSG_CANNOT_ADD", "You do not have permission to add users." },
113 { "HSMSG_CANNOT_DEL", "You do not have permission to delete users." },
114 { "HSMSG_CANNOT_CLVL", "You do not have permission to modify users' access." },
115 { "HSMSG_NO_SELF_CLVL", "You cannot change your own access." },
116 { "HSMSG_NO_BUMP_ACCESS", "You cannot give users the same or more access than yourself." },
117 { "HSMSG_NO_TRANSFER_SELF", "You cannot give ownership to your own account." },
118 { "HSMSG_ADDED_USER", "Added new $b%s$b %s to the user list." },
119 { "HSMSG_DELETED_USER", "Deleted $b%s$b %s from the user list." },
120 { "HSMSG_USER_EXISTS", "$b%s$b is already on the user list." },
121 { "HSMSG_INVALID_ACCESS", "$b%s$b is an invalid access level." },
122 { "HSMSG_CHANGED_ACCESS", "%s now has $b%s$b access." },
123 { "HSMSG_EXPIRATION_DONE", "%d eligible HelpServ bots have retired." },
124 { "HSMSG_BAD_WEEKDAY", "I do not know which day of the week $b%s$b is." },
125 { "HSMSG_WEEK_STARTS", "$b%s$b's weeks start on $b%s$b." },
128 { "HSMSG_ILLEGAL_NICK", "$b%s$b is an illegal nick; cannot use it." },
129 { "HSMSG_NICK_EXISTS", "The nick %s is in use by someone else." },
130 { "HSMSG_REG_SUCCESS", "%s now has ownership of bot %s." },
131 { "HSMSG_NEED_UNREG_CONFIRM", "To unregister this bot, you must /msg $S unregister CONFIRM" },
132 { "HSMSG_ERROR_ADDING_SERVICE", "Error creating new user $b%s$b." },
135 { "HSMSG_RENAMED", "%s has been renamed to $b%s$b." },
136 { "HSMSG_MOVE_SAME_CHANNEL", "You cannot move %s to the same channel it is on." },
137 { "HSMSG_INVALID_MOVE", "$b%s$b is not a valid nick or channel name." },
138 { "HSMSG_NEED_GIVEOWNERSHIP_CONFIRM", "To transfer ownership of this bot, you must /msg $S giveownership newowner CONFIRM" },
139 { "HSMSG_MULTIPLE_OWNERS", "There is more than one owner of %s; please use other commands to change ownership."},
140 { "HSMSG_NO_TRANSFER_SELF", "You cannot give ownership to your own account." },
141 { "HSMSG_OWNERSHIP_GIVEN", "Ownership of $b%s$b has been transferred to account $b%s$b." },
144 { "HSMSG_INVALID_OPTION", "$b%s$b is not a valid option." },
145 { "HSMSG_QUEUE_OPTIONS", "HelpServ Queue Options:" },
146 { "HSMSG_SET_COMMAND_TYPE", "$bPageType $b %s" },
147 { "HSMSG_SET_ALERT_TYPE", "$bAlertPageType $b %s" },
148 { "HSMSG_SET_STATUS_TYPE", "$bStatusPageType $b %s" },
149 { "HSMSG_SET_COMMAND_TARGET", "$bPageTarget $b %s" },
150 { "HSMSG_SET_ALERT_TARGET", "$bAlertPageTarget $b %s" },
151 { "HSMSG_SET_STATUS_TARGET", "$bStatusPageTarget$b %s" },
152 { "HSMSG_SET_GREETING", "$bGreeting $b %s" },
153 { "HSMSG_SET_REQOPENED", "$bReqOpened $b %s" },
154 { "HSMSG_SET_REQASSIGNED", "$bReqAssigned $b %s" },
155 { "HSMSG_SET_REQCLOSED", "$bReqClosed $b %s" },
156 { "HSMSG_SET_IDLEDELAY", "$bIdleDelay $b %s" },
157 { "HSMSG_SET_WHINEDELAY", "$bWhineDelay $b %s" },
158 { "HSMSG_SET_WHINEINTERVAL", "$bWhineInterval $b %s" },
159 { "HSMSG_SET_EMPTYINTERVAL", "$bEmptyInterval $b %s" },
160 { "HSMSG_SET_STALEDELAY", "$bStaleDelay $b %s" },
161 { "HSMSG_SET_REQPERSIST", "$bReqPersist $b %s" },
162 { "HSMSG_SET_HELPERPERSIST", "$bHelperPersist $b %s" },
163 { "HSMSG_SET_NOTIFICATION", "$bNotification $b %s" },
164 { "HSMSG_SET_IDWRAP", "$bIDWrap $b %d" },
165 { "HSMSG_SET_REQMAXLEN", "$bReqMaxLen $b %d" },
166 { "HSMSG_SET_PRIVMSGONLY", "$bPrivmsgOnly $b %s" },
167 { "HSMSG_SET_REQONJOIN", "$bReqOnJoin $b %s" },
168 { "HSMSG_SET_AUTOVOICE", "$bAutoVoice $b %s" },
169 { "HSMSG_SET_AUTODEVOICE", "$bAutoDevoice $b %s" },
170 { "HSMSG_SET_JOINTOTAL", "$bJoinTotal $b %s" },
171 { "HSMSG_SET_ALERTNEW", "$bAlertNew $b %s" },
172 { "HSMSG_PAGE_NOTICE", "notice" },
173 { "HSMSG_PAGE_PRIVMSG", "privmsg" },
174 { "HSMSG_PAGE_ONOTICE", "onotice" },
175 { "HSMSG_LENGTH_PART", "part" },
176 { "HSMSG_LENGTH_QUIT", "quit" },
177 { "HSMSG_LENGTH_CLOSE", "close" },
178 { "HSMSG_NOTIFY_DROP", "ReqDrop" },
179 { "HSMSG_NOTIFY_USERCHANGES", "UserChanges" },
180 { "HSMSG_NOTIFY_ACCOUNTCHANGES", "AccountChanges" },
181 { "HSMSG_INVALID_INTERVAL", "Sorry, %s must be at least %s." },
182 { "HSMSG_0_DISABLED", "0 (Disabled)" },
183 { "HSMSG_NEED_MANAGER", "Only managers or higher can do this." },
184 { "HSMSG_SET_NEED_OPER", "This option can only be set by an oper." },
187 { "HSMSG_REQ_INVALID", "$b%s$b is not a valid request ID, or there are no requests for that nick or account." },
188 { "HSMSG_REQ_NOT_YOURS_ASSIGNED_TO", "Request $b%lu$b is not assigned to you; it is currently assigned to %s." },
189 { "HSMSG_REQ_NOT_YOURS_UNASSIGNED", "Request $b%lu$b is not assigned to you; it is currently unassigned." },
190 { "HSMSG_REQ_FOUNDMANY", "The user you entered had multiple requests. The oldest one is being used." },
191 { "HSMSG_REQ_CLOSED", "Request $b%lu$b has been closed." },
192 { "HSMSG_REQ_NO_UNASSIGNED", "There are no unassigned requests." },
193 { "HSMSG_USERCMD_NO_REQUEST", "You must have an open request to use a user command." },
194 { "HSMSG_USERCMD_UNKNOWN", "I do not know the user command $b%s$b." },
195 { "HSMSG_REQ_YOU_NOT_IN_HELPCHAN_OPEN", "You cannot open this request as you are not in %s." },
196 { "HSMSG_REQ_YOU_NOT_IN_HELPCHAN", "You cannot be assigned this request as you are not in %s." },
197 { "HSMSG_REQ_HIM_NOT_IN_HELPCHAN", "%s cannot be assigned this request as they are not in %s." },
198 { "HSMSG_REQ_SELF_NOT_IN_OVERRIDE", "You are being assigned this request even though you are not in %s, because the restriction was overridden (you are a manager or owner). If you join and then part, this request will be marked unassigned." },
199 { "HSMSG_REQ_YOU_NOT_IN_OVERRIDE", "Note: You are being assigned this request even though you are not in %s, because the restriction was overridden by a manager or owner. If you join and then part, this request will be marked unassigned." },
200 { "HSMSG_REQ_HIM_NOT_IN_OVERRIDE", "Note: %s is being assigned this request even though they are not in %s, because the restriction was overridden (you are a manager or owner). If they join and then part, this request will be marked unassigned." },
201 { "HSMSG_REQ_ASSIGNED_YOU", "You have been assigned request ID#%lu:" },
202 { "HSMSG_REQ_REASSIGNED", "You have been assigned request ID#%lu (reassigned from %s):" },
203 { "HSMSG_REQ_INFO_1", "Request ID#%lu:" },
204 { "HSMSG_REQ_INFO_2a", " - Nick %s / Account %s" },
205 { "HSMSG_REQ_INFO_2b", " - Nick %s / Not authed" },
206 { "HSMSG_REQ_INFO_2c", " - Online somewhere / Account %s" },
207 { "HSMSG_REQ_INFO_2d", " - Not online / Account %s" },
208 { "HSMSG_REQ_INFO_2e", " - Not online / No account" },
209 { "HSMSG_REQ_INFO_3", " - Opened at %s (%s ago)" },
210 { "HSMSG_REQ_INFO_4", " - Message:" },
211 { "HSMSG_REQ_INFO_MESSAGE", " %s" },
212 { "HSMSG_REQ_ASSIGNED", "Your helper for request ID#%lu is %s (Current nick: %s)" },
213 { "HSMSG_REQ_ASSIGNED_AGAIN", "Your helper for request ID#%lu has been changed to %s (Current nick: %s)" },
214 { "HSMSG_REQ_UNASSIGNED", "Your helper for request ID#%lu has %s, so your request is no longer being handled by them. It has been placed near the front of the unhandled request queue, based on how long ago your request was opened." },
215 { "HSMSG_REQ_NEW", "Your message has been recorded and assigned request ID#%lu. A helper should contact you shortly." },
216 { "HSMSG_REQ_NEWONJOIN", "Welcome to %s. You have been assigned request ID#%lu. A helper should contact you shortly." },
217 { "HSMSG_REQ_ALERT", "A new request (ID#%lu) has been opened in %s. Use /msg %s PICKUP %lu to pickup this request, or use /msg %s NEXT to pickup the oldest request." },
218 { "HSMSG_REQ_UNHANDLED_TIME", "The oldest unhandled request has been waiting for %s." },
219 { "HSMSG_REQ_NO_UNHANDLED", "There are no other unhandled requests." },
220 { "HSMSG_REQ_PERSIST_QUIT", "Everything you tell me until you are helped (or you quit) will be recorded. If you disconnect, your request will be lost." },
221 { "HSMSG_REQ_PERSIST_PART", "Everything you tell me until you are helped (or you leave %s) will be recorded. If you part %s, your request will be lost." },
222 { "HSMSG_REQ_PERSIST_HANDLE", "Everything you tell me until you are helped will be recorded." },
223 { "HSMSG_REQ_MAXLEN", "Sorry, but your request has reached the maximum number of lines. Please wait to be assigned to a helper and continue explaining your request to them." },
224 { "HSMSQ_REQ_TEXT_ADDED", "Message from $b%s:$b %s" },
225 { "HSMSG_REQ_FOUND_ANOTHER", "Request ID#%lu has been closed. $S detected that you also have request ID#%lu open. If you send $S a message, it will be associated with that request." },
227 /* Messages that are inserted into request text */
228 { "HSMSG_REQMSG_NOTE_ADDED", "Your note for request ID#%lu has been recorded." },
230 /* Automatically generated page messages */
231 { "HSMSG_PAGE_NEW_REQUEST_AUTHED", "New request (ID#%lu) from $b%s$b (Account %s)" },
232 { "HSMSG_PAGE_NEW_REQUEST_UNAUTHED", "New request (ID#%lu) from $b%s$b (Not logged in)" },
233 { "HSMSG_PAGE_UPD_REQUEST_AUTHED", "Request ID#%lu has been updated by $b%s$b (Account %s). Request was initially opened at %s, and was last updated %s ago." },
234 { "HSMSG_PAGE_UPD_REQUEST_NOT_AUTHED", "Request ID#%lu has been updated by $b%s$b (not authed). Request was initially opened at %s, and was last updated %s ago." },
235 { "HSMSG_PAGE_CLOSE_REQUEST_1", "Request ID#%lu from $b%s$b (Account %s) has been closed by %s." },
236 { "HSMSG_PAGE_CLOSE_REQUEST_2", "Request ID#%lu from $b%s$b (Not authed) has been closed by %s." },
237 { "HSMSG_PAGE_CLOSE_REQUEST_3", "Request ID#%lu from an offline user (Account %s) has been closed by %s." },
238 { "HSMSG_PAGE_CLOSE_REQUEST_4", "Request ID#%lu from an offline user (no account) has been closed by %s." },
239 { "HSMSG_PAGE_ASSIGN_REQUEST_1", "Request ID#%lu from $b%s$b (Account %s) has been assigned to %s." },
240 { "HSMSG_PAGE_ASSIGN_REQUEST_2", "Request ID#%lu from $b%s$b (Not authed) has been assigned to %s." },
241 { "HSMSG_PAGE_ASSIGN_REQUEST_3", "Request ID#%lu from an offline user (Account %s) has been assigned to %s." },
242 { "HSMSG_PAGE_ASSIGN_REQUEST_4", "Request ID#%lu from an offline user (no account) has been assigned to %s." },
243 /* The last %s is still an I18N lose. Blame whoever decided to overload it so much. */
244 { "HSMSG_PAGE_HELPER_GONE_1", "Request ID#%lu from $b%s$b (Account %s) $bhas been unassigned$b, as its helper, %s has %s." },
245 { "HSMSG_PAGE_HELPER_GONE_2", "Request ID#%lu from $b%s$b (Not authed) $bhas been unassigned$b, as its helper, %s has %s." },
246 { "HSMSG_PAGE_HELPER_GONE_3", "Request ID#%lu from an offline user (Account %s) $bhas been unassigned$b, as its helper, %s has %s." },
247 { "HSMSG_PAGE_HELPER_GONE_4", "Request ID#%lu from an offline user (No account) $bhas been unassigned$b, as its helper, %s has %s." },
248 { "HSMSG_PAGE_WHINE_HEADER", "$b%u unhandled request(s)$b waiting at least $b%s$b (%u unhandled, %u total)" },
249 { "HSMSG_PAGE_IDLE_HEADER", "$b%u users$b in %s $bidle at least %s$b:" },
250 { "HSMSG_PAGE_EMPTYALERT", "$b%s has no helpers present (%u unhandled request(s))$b" },
251 { "HSMSG_PAGE_ONLYTRIALALERT", "$b%s has no full helpers present (%d trial(s) present; %u unhandled request(s))$b" },
252 { "HSMSG_PAGE_FIRSTEMPTYALERT", "$b%s has no helpers present because %s has left (%u unhandled request(s))$b" },
253 { "HSMSG_PAGE_FIRSTONLYTRIALALERT", "$b%s has no full helpers present because %s has left (%d trial(s) present; %u unhandled request(s))$b" },
254 { "HSMSG_PAGE_EMPTYNOMORE", "%s has joined %s; cancelling the \"no helpers present\" alert" },
256 /* Notification messages */
257 { "HSMSG_NOTIFY_USER_QUIT", "The user for request ID#%lu, $b%s$b, has disconnected." },
258 { "HSMSG_NOTIFY_USER_MOVE", "The account for request ID#%lu, $b%s$b has been unregistered. It has been associated with user $b%s$b." },
259 { "HSMSG_NOTIFY_USER_NICK", "The user for request ID#%lu has changed their nick from $b%s$b to $b%s$b." },
260 { "HSMSG_NOTIFY_USER_FOUND", "The user for request ID#%lu is now online using nick $b%s$b." },
261 { "HSMSG_NOTIFY_HAND_RENAME", "The account for request ID#%lu has been renamed from $b%s$b to $b%s$b." },
262 { "HSMSG_NOTIFY_HAND_MOVE", "The account for request ID#%lu has been changed to $b%s$b from $b%s$b." },
263 { "HSMSG_NOTIFY_HAND_STUCK", "The user for request ID#%lu, $b%s$b, has re-authenticated to account $b%s$b from $b%s$b, and the request remained associated with the old handle." },
264 { "HSMSG_NOTIFY_HAND_AUTH", "The user for request ID#%lu, $b%s$b, has authenticated to account $b%s$b." },
265 { "HSMSG_NOTIFY_HAND_UNREG", "The account for request ID#%lu, $b%s$b, has been unregistered by $b%s$b." },
266 { "HSMSG_NOTIFY_HAND_MERGE", "The account for request ID#%lu, $b%s$b, has been merged with $b%s$b by %s." },
267 { "HSMSG_NOTIFY_ALLOWAUTH", "The user for request ID#%lu, $b%s$b, has been permitted by %s to authenticate to account $b%s$b without hostmask checking." },
268 { "HSMSG_NOTIFY_UNALLOWAUTH", "The user for request ID#%lu, $b%s$b, has had their ability to authenticate without hostmask checking revoked by %s." },
269 { "HSMSG_NOTIFY_FAILPW", "The user for request ID#%lu, $b%s$b, has attempted to authenticate to account $b%s$b, but used an incorrect password." },
270 { "HSMSG_NOTIFY_REQ_DROP_PART", "Request ID#%lu has been $bdropped$b because %s left the help channel." },
271 { "HSMSG_NOTIFY_REQ_DROP_QUIT", "Request ID#%lu has been $bdropped$b because %s quit IRC." },
272 { "HSMSG_NOTIFY_REQ_DROP_UNREGGED", "Request ID#%lu (account %s) has been $bdropped$b because the account was unregistered." },
274 /* Presence and request-related messages */
275 { "HSMSG_REQ_DROPPED_PART", "You have left $b%s$b. Your help request (ID#%lu) has been deleted." },
276 { "HSMSG_REQ_WARN_UNREG", "The account you were authenticated to ($b%s$b) has been unregistered. Therefore unless you register a new handle, or authenticate to another one, if you disconnect, your HelpServ $S (%s) request ID#%lu will be lost." },
277 { "HSMSG_REQ_DROPPED_UNREG", "By unregistering the account $b%s$b, HelpServ $S (%s) request ID#%lu was dropped, as there was nobody online to associate the request with." },
278 { "HSMSG_REQ_ASSIGNED_UNREG", "As the account $b%s$b was unregistered, HelpServ $S (%s) request ID#%lu was associated with you, as you were authenticated to that account. If you disconnect, then this request will be lost forever." },
279 { "HSMSG_REQ_AUTH_STUCK", "By authenticating to account $b%s$b, HelpServ $S (%s) request ID#%lu remained with the previous account $b%s$b (because the request will remain until closed). This means that if you send $S a message, it $uwill not$u be associated with this request." },
280 { "HSMSG_REQ_AUTH_MOVED", "By authenticating to account $b%s$b, HelpServ $S (%s) request ID#%lu ceased to be associated with your previously authenticated account ($b%s$b), and was transferred to your new account (because the request will only remain until you %s). This means that if you send $S a message, it $uwill$u be associated with this request." },
283 { "HSMSG_BOTLIST_HEADER", "$bCurrent HelpServ bots:$b" },
284 { "HSMSG_USERLIST_HEADER", "$b%s users:$b" },
285 { "HSMSG_USERLIST_ZOOT_LVL", "%s $b%ss$b:" },
286 { "HSMSG_REQLIST_AUTH", "You are currently assigned these requests:" },
287 { "HSMSG_REQ_LIST_TOP_UNASSIGNED", "Listing $ball unassigned$b requests (%d in list)." },
288 { "HSMSG_REQ_LIST_TOP_ASSIGNED", "Listing $ball assigned$b requests (%d in list)." },
289 { "HSMSG_REQ_LIST_TOP_YOUR", "Listing $byour$b requests (%d in list)." },
290 { "HSMSG_REQ_LIST_TOP_ALL", "Listing $ball$b requests (%d in list)." },
291 { "HSMSG_REQ_LIST_NONE", "There are no matching requests." },
292 { "HSMSG_STATS_TOP", "Stats for %s user $b%s$b (week starts %s):" },
293 { "HSMSG_STATS_TIME", "$uTime spent helping in %s:$u" },
294 { "HSMSG_STATS_REQS", "$uRequest activity statistics:$u" },
296 /* Status report headers */
297 { "HSMSG_STATS_REPORT_0", "Stats report for current week" },
298 { "HSMSG_STATS_REPORT_1", "Stats report for one week ago" },
299 { "HSMSG_STATS_REPORT_2", "Stats report for two weeks ago" },
300 { "HSMSG_STATS_REPORT_3", "Stats report for three weeks ago" },
303 { "HSMSG_HELP_COMMAND_ALIAS", "$uAlias for:$u %s" },
304 { "HSMSG_HELP_COMMAND_HEADER", "Command help for: $b%s$b" },
305 { "HSMSG_HELP_COMMAND_UNKNOWN", "No help available for that command." },
306 { "HSMSG_HELP_TOPIC_HEADER", "Help topic: $b%s$b" },
307 { "HSMSG_HELP_DIVIDER", "=---------------------------------------=" },
308 { "HSMSG_HELP_FOOTER", "=------------- End of Help -------------=" },
309 { "HSMSG_COMMAND_BINDING", "%s is a binding of: %s" },
311 /* Channel [un]suspension */
312 { "HSMSG_ALREADY_SUSPENDED", "$b%s$b is already suspended." },
313 { "HSMSG_NOT_SUSPENDED", "$b%s$b is not suspended." },
314 { "HSMSG_SUSPENDED", "$b$S$b access to $b%s$b has been temporarily suspended." },
315 { "HSMSG_UNSUSPENDED", "$b$S$b access to $b%s$b has been restored." },
316 { "HSMSG_SUSPEND_NODELETE", "$b%s$b is protected from unregistration, and cannot be suspended." },
317 { "HSMSG_USER_SUSPENDED", "$b%s$b's access to $b%s$b has been suspended." },
318 { "HSMSG_USER_UNSUSPENDED", "$b%s$b's access to $b%s$b has been restored." },
319 { "HSMSG_BOT_NON_EXIST", "HelpServ bot %s does not exist." },
321 /* Responses to user commands */
322 { "HSMSG_YOU_BEING_HELPED", "You are already being helped." },
323 { "HSMSG_YOU_BEING_HELPED_BY", "You are already being helped by $b%s$b." },
324 { "HSMSG_WAIT_STATUS", "You are %d of %d in line; the first person has waited %s." },
325 { "HSMSG_NO_HELPER", "No one is currently helping you, please wait for a helper to pickup your request." },
326 { "HSMSG_HELPER", "You are currently being helped by %s." },
330 enum helpserv_level
{
340 static const char *helpserv_level_names
[] = {
358 static const struct {
363 { "none", "MSG_NONE", NULL
},
364 { "notice", "HSMSG_PAGE_NOTICE", irc_notice
},
365 { "privmsg", "HSMSG_PAGE_PRIVMSG", irc_privmsg
},
366 { "onotice", "HSMSG_PAGE_ONOTICE", irc_wallchops
},
377 static const struct {
382 { "command", "HSMSG_SET_COMMAND_TARGET", "HSMSG_SET_COMMAND_TYPE" },
383 { "alert", "HSMSG_SET_ALERT_TARGET", "HSMSG_SET_ALERT_TYPE" },
384 { "status", "HSMSG_SET_STATUS_TARGET", "HSMSG_SET_STATUS_TYPE" },
391 MSGTYPE_REQ_ASSIGNED
,
397 static const struct {
400 } message_types
[] = {
401 { "greeting", "HSMSG_SET_GREETING" },
402 { "reqopened", "HSMSG_SET_REQOPENED" },
403 { "reqassigned", "HSMSG_SET_REQASSIGNED" },
404 { "reqclosed", "HSMSG_SET_REQCLOSED" },
405 { "reqdropped", "HSMSG_SET_REQDROPPED" },
411 INTERVAL_WHINE_DELAY
,
412 INTERVAL_WHINE_INTERVAL
,
413 INTERVAL_EMPTY_INTERVAL
,
414 INTERVAL_STALE_DELAY
,
418 static const struct {
421 } interval_types
[] = {
422 { "idledelay", "HSMSG_SET_IDLEDELAY" },
423 { "whinedelay", "HSMSG_SET_WHINEDELAY" },
424 { "whineinterval", "HSMSG_SET_WHINEINTERVAL" },
425 { "emptyinterval", "HSMSG_SET_EMPTYINTERVAL" },
426 { "staledelay", "HSMSG_SET_STALEDELAY" },
430 enum persistence_type
{
436 static const struct {
439 } persistence_types
[] = {
440 { "reqpersist", "HSMSG_SET_REQPERSIST" },
441 { "helperpersist", "HSMSG_SET_HELPERPERSIST" },
445 enum persistence_length
{
452 static const struct {
455 } persistence_lengths
[] = {
456 { "part", "HSMSG_LENGTH_PART" },
457 { "quit", "HSMSG_LENGTH_QUIT" },
458 { "close", "HSMSG_LENGTH_CLOSE" },
462 enum notification_type
{
470 static const struct {
473 } notification_types
[] = {
474 { "none", "MSG_NONE" },
475 { "reqdrop", "HSMSG_NOTIFY_DROP" },
476 { "userchanges", "HSMSG_NOTIFY_USERCHANGES" },
477 { "accountchanges", "HSMSG_NOTIFY_ACCOUNTCHANGES" },
481 static const char *weekday_names
[] = {
492 static const char *statsreport_week
[] = {
493 "HSMSG_STATS_REPORT_0",
494 "HSMSG_STATS_REPORT_1",
495 "HSMSG_STATS_REPORT_2",
496 "HSMSG_STATS_REPORT_3"
500 const char *description
;
501 const char *reqlogfile
;
502 unsigned long db_backup_frequency
;
503 unsigned int expire_age
;
507 static time_t last_stats_update
;
508 static int shutting_down
;
509 static FILE *reqlog_f
;
510 static struct log_type
*HS_LOG
;
512 #define CMD_NEED_BOT 0x001
513 #define CMD_NOT_OVERRIDE 0x002
514 #define CMD_FROM_OPSERV_ONLY 0x004
515 #define CMD_IGNORE_EVENT 0x008
516 #define CMD_NEVER_FROM_OPSERV 0x010
518 struct helpserv_bot
{
519 struct userNode
*helpserv
;
521 struct chanNode
*helpchan
;
523 struct chanNode
*page_targets
[PGSRC_COUNT
];
524 enum page_type page_types
[PGSRC_COUNT
];
525 char *messages
[MSGTYPE_COUNT
];
526 unsigned long intervals
[INTERVAL_COUNT
];
527 enum notification_type notify
;
529 /* This is a default; it can be changed on a per-request basis */
530 enum persistence_length persist_types
[PERSIST_T_COUNT
];
532 dict_t users
; /* indexed by handle */
534 struct helpserv_request
*unhandled
; /* linked list of unhandled requests */
535 dict_t requests
; /* indexed by request id */
536 unsigned long last_requestid
;
537 unsigned long id_wrap
;
538 unsigned long req_maxlen
; /* Maxmimum request length in lines */
540 unsigned int privmsg_only
: 1;
541 unsigned int req_on_join
: 1;
542 unsigned int auto_voice
: 1;
543 unsigned int auto_devoice
: 1;
544 unsigned int join_total
: 1;
545 unsigned int alert_new
: 1;
547 unsigned int helpchan_empty
: 1;
549 unsigned int suspended
: 1;
550 time_t expiry
, issued
;
559 struct helpserv_user
{
560 struct handle_info
*handle
;
561 struct helpserv_bot
*hs
;
562 unsigned int help_mode
: 1;
563 unsigned int week_start
: 3;
564 enum helpserv_level level
;
566 time_t join_time
; /* when they joined, or 0 if not in channel */
567 /* [0] through [3] are n weeks ago, [4] is the total of everything before that */
568 unsigned int time_per_week
[5]; /* how long they've were in the channel the past 4 weeks */
569 unsigned int picked_up
[5]; /* how many requests they have picked up */
570 unsigned int closed
[5]; /* how many requests they have closed */
571 unsigned int reassigned_from
[5]; /* how many requests reassigned from them to others */
572 unsigned int reassigned_to
[5]; /* how many requests reassigned from others to them */
575 struct helpserv_request
{
576 struct helpserv_user
*helper
;
577 struct helpserv_bot
*hs
;
578 struct string_list
*text
;
579 struct helpserv_request
*next_unhandled
;
581 struct helpserv_reqlist
*parent_nick_list
;
582 struct helpserv_reqlist
*parent_hand_list
;
584 /* One, but not both, of "user" and "handle" may be NULL,
585 * depending on what we know about the user.
587 * If persist == PERSIST_CLOSE when the user quits, then it
588 * switches to handle instead of user... and stays that way (it's
589 * possible to have >1 nick per handle, so you can't really decide
590 * to reassign a userNode to it unless they send another message
593 struct userNode
*user
;
594 struct handle_info
*handle
;
602 #define DEFINE_LIST_ALLOC(STRUCTNAME) \
603 struct STRUCTNAME * STRUCTNAME##_alloc() {\
604 struct STRUCTNAME *newlist; \
605 newlist = malloc(sizeof(struct STRUCTNAME)); \
606 STRUCTNAME##_init(newlist); \
609 void STRUCTNAME##_free(void *data) {\
610 struct STRUCTNAME *list = data; /* void * to let dict_set_free_data use it */ \
611 STRUCTNAME##_clean(list); \
615 DECLARE_LIST(helpserv_botlist
, struct helpserv_bot
*);
616 DEFINE_LIST(helpserv_botlist
, struct helpserv_bot
*)
617 DEFINE_LIST_ALLOC(helpserv_botlist
)
619 DECLARE_LIST(helpserv_reqlist
, struct helpserv_request
*);
620 DEFINE_LIST(helpserv_reqlist
, struct helpserv_request
*)
621 DEFINE_LIST_ALLOC(helpserv_reqlist
)
623 DECLARE_LIST(helpserv_userlist
, struct helpserv_user
*);
624 DEFINE_LIST(helpserv_userlist
, struct helpserv_user
*)
625 DEFINE_LIST_ALLOC(helpserv_userlist
)
627 struct helpfile
*helpserv_helpfile
;
628 static struct module *helpserv_module
;
629 static dict_t helpserv_func_dict
;
630 static dict_t helpserv_usercmd_dict
; /* contains helpserv_usercmd_t */
631 static dict_t helpserv_option_dict
;
632 static dict_t helpserv_bots_dict
; /* indexed by nick */
633 static dict_t helpserv_bots_bychan_dict
; /* indexed by chan, holds a struct helpserv_botlist */
634 /* QUESTION: why are these outside of any helpserv_bot struct? */
635 static dict_t helpserv_reqs_bynick_dict
; /* indexed by nick, holds a struct helpserv_reqlist */
636 static dict_t helpserv_reqs_byhand_dict
; /* indexed by handle, holds a struct helpserv_reqlist */
637 static dict_t helpserv_users_byhand_dict
; /* indexed by handle, holds a struct helpserv_userlist */
639 /* This is so that overrides can "speak" from opserv */
640 extern struct userNode
*opserv
;
642 #define HELPSERV_SYNTAX() helpserv_help(hs, from_opserv, user, argv[0], 0)
643 #define HELPSERV_FUNC(NAME) int NAME(struct userNode *user, UNUSED_ARG(struct helpserv_bot *hs), int from_opserv, UNUSED_ARG(unsigned int argc), UNUSED_ARG(char *argv[]))
644 typedef HELPSERV_FUNC(helpserv_func_t
);
645 #define HELPSERV_USERCMD(NAME) int NAME(UNUSED_ARG(struct helpserv_request *req), UNUSED_ARG(struct userNode *likely_helper), UNUSED_ARG(const char *args), UNUSED_ARG(int argc), UNUSED_ARG(char *argv[]), UNUSED_ARG(struct userNode *user), UNUSED_ARG(struct helpserv_bot *hs))
646 typedef HELPSERV_USERCMD(helpserv_usercmd_t
);
647 #define HELPSERV_OPTION(NAME) HELPSERV_FUNC(NAME)
648 typedef HELPSERV_OPTION(helpserv_option_func_t
);
650 static HELPSERV_FUNC(cmd_help
);
652 #define REQUIRE_PARMS(N) if (argc < N) { \
653 helpserv_notice(user, "MSG_MISSING_PARAMS", argv[0]); \
657 /* For messages going to users being helped */
658 #if defined(GCC_VARMACROS)
659 # define helpserv_msguser(target, ARGS...) send_message_type((from_opserv ? 0 : hs->privmsg_only), (target), (fromopserv ? opserv : hs->helpserv), ARGS)
660 # define helpserv_user_reply(ARGS...) send_message_type(req->hs->privmsg_only, req->user, req->hs->helpserv, ARGS)
661 /* For messages going to helpers */
662 # define helpserv_notice(target, ARGS...) send_message((target), (from_opserv ? opserv : hs->helpserv), ARGS)
663 # define helpserv_notify(helper, ARGS...) do { struct userNode *_target; for (_target = (helper)->handle->users; _target; _target = _target->next_authed) { \
664 send_message(_target, (helper)->hs->helpserv, ARGS); \
666 # define helpserv_page(TYPE, ARGS...) do { \
667 int msg_type=0; struct chanNode *target=helpserv_get_page_type(hs, (TYPE), &msg_type); \
668 if (target) send_target_message(msg_type, target->name, hs->helpserv, ARGS); \
670 #elif defined(C99_VARMACROS)
671 # define helpserv_msguser(target, ...) send_message_type((from_opserv ? 0 : hs->privmsg_only), (target), (from_opserv ? opserv : hs->helpserv), __VA_ARGS__)
672 # define helpserv_user_reply(...) send_message_type(req->hs->privmsg_only, req->user, req->hs->helpserv, __VA_ARGS__)
673 /* For messages going to helpers */
674 # define helpserv_notice(target, ...) send_message((target), (from_opserv ? opserv : hs->helpserv), __VA_ARGS__)
675 # define helpserv_notify(helper, ...) do { struct userNode *_target; for (_target = (helper)->handle->users; _target; _target = _target->next_authed) { \
676 send_message(_target, (helper)->hs->helpserv, __VA_ARGS__); \
678 # define helpserv_page(TYPE, ...) do { \
679 int msg_type=0; struct chanNode *target=helpserv_get_page_type(hs, (TYPE), &msg_type); \
680 if (target) send_target_message(msg_type, target->name, hs->helpserv, __VA_ARGS__); \
683 #define helpserv_message(hs, target, id) do { if ((hs)->messages[id]) { \
685 send_message_type(4, (target), opserv, "%s", (hs)->messages[id]); \
687 send_message_type(4 | hs->privmsg_only, (target), hs->helpserv, "%s", (hs)->messages[id]); \
689 #define helpserv_get_handle_info(user, text) smart_get_handle_info((from_opserv ? opserv : hs->helpserv) , (user), (text))
691 struct helpserv_cmd
{
692 enum helpserv_level access
;
693 helpserv_func_t
*func
;
698 static void run_empty_interval(void *data
);
700 static void helpserv_interval(char *output
, time_t interval
) {
701 int num_hours
= interval
/ 3600;
702 int num_minutes
= (interval
% 3600) / 60;
703 sprintf(output
, "%u hour%s, %u minute%s", num_hours
, num_hours
== 1 ? "" : "s", num_minutes
, num_minutes
== 1 ? "" : "s");
706 static const char * helpserv_level2str(enum helpserv_level level
) {
707 if (level
<= HlOper
) {
708 return helpserv_level_names
[level
];
710 log_module(HS_LOG
, LOG_ERROR
, "helpserv_level2str receieved invalid level %d.", level
);
715 static enum helpserv_level
helpserv_str2level(const char *msg
) {
716 enum helpserv_level nn
;
717 for (nn
=HlNone
; nn
<=HlOper
; nn
++) {
718 if (!irccasecmp(msg
, helpserv_level_names
[nn
]))
721 log_module(HS_LOG
, LOG_ERROR
, "helpserv_str2level received invalid level %s.", msg
);
722 return HlNone
; /* Error */
725 static struct helpserv_user
*GetHSUser(struct helpserv_bot
*hs
, struct handle_info
*hi
) {
726 return dict_find(hs
->users
, hi
->handle
, NULL
);
729 static void helpserv_log_request(struct helpserv_request
*req
, const char *reason
) {
730 char key
[27+NICKLEN
];
731 char userhost
[USERLEN
+HOSTLEN
+2];
732 struct saxdb_context
*ctx
;
736 assert(reason
!= NULL
);
737 if (!reqlog_f
|| !(ctx
= saxdb_open_context(reqlog_f
)))
739 sprintf(key
, "%s-" FMT_TIME_T
"-%lu", req
->hs
->helpserv
->nick
, req
->opened
, req
->id
);
740 if ((res
= setjmp(*saxdb_jmp_buf(ctx
))) != 0) {
741 log_module(HS_LOG
, LOG_ERROR
, "Unable to log helpserv request: %s.", strerror(res
));
743 saxdb_start_record(ctx
, key
, 1);
745 saxdb_write_string(ctx
, KEY_REQUEST_HELPER
, req
->helper
->handle
->handle
);
746 saxdb_write_int(ctx
, KEY_REQUEST_ASSIGNED
, req
->assigned
);
749 saxdb_write_string(ctx
, KEY_REQUEST_HANDLE
, req
->handle
->handle
);
752 saxdb_write_string(ctx
, KEY_REQUEST_NICK
, req
->user
->nick
);
753 sprintf(userhost
, "%s@%s", req
->user
->ident
, req
->user
->hostname
);
754 saxdb_write_string(ctx
, KEY_REQUEST_USERHOST
, userhost
);
756 saxdb_write_int(ctx
, KEY_REQUEST_OPENED
, req
->opened
);
757 saxdb_write_int(ctx
, KEY_REQUEST_CLOSED
, now
);
758 saxdb_write_string(ctx
, KEY_REQUEST_CLOSEREASON
, reason
);
759 saxdb_write_string_list(ctx
, KEY_REQUEST_TEXT
, req
->text
);
760 saxdb_end_record(ctx
);
761 saxdb_close_context(ctx
, 0);
765 static struct chanNode
*helpserv_get_page_type(struct helpserv_bot
*hs
, enum page_source type
, int *msg_type
)
767 switch (hs
->page_types
[type
]) {
778 log_module(HS_LOG
, LOG_ERROR
, "helpserv_page() called but %s has an invalid page type %d.", hs
->helpserv
->nick
, type
);
779 /* and fall through */
783 return hs
->page_targets
[type
];
786 /* Searches for a request by number, nick, or account (num|nick|*account).
787 * As there can potentially be >1 match, it takes a reqlist. The return
788 * value is the "best" request found (explained in the comment block below).
790 * If num_results is not NULL, it is set to the number of potentially matching
792 * If hs_user is not NULL, requests assigned to this helper will be given
793 * preference (oldest assigned, falling back to oldest if there are none).
795 static struct helpserv_request
* smart_get_request(struct helpserv_bot
*hs
, struct helpserv_user
*hs_user
, const char *needle
, int *num_results
) {
796 struct helpserv_reqlist
*reqlist
, resultlist
;
797 struct helpserv_request
*req
, *oldest
=NULL
, *oldest_assigned
=NULL
;
798 struct userNode
*user
;
804 if (*needle
== '*') {
805 /* This test (handle) requires the least processing, so it's first */
806 if (!(reqlist
= dict_find(helpserv_reqs_byhand_dict
, needle
+1, NULL
)))
808 helpserv_reqlist_init(&resultlist
);
809 for (i
=0; i
< reqlist
->used
; i
++) {
810 req
= reqlist
->list
[i
];
812 helpserv_reqlist_append(&resultlist
, req
);
817 } else if (!needle
[strspn(needle
, "0123456789")]) {
818 /* The string is 100% numeric - a request id */
819 if (!(req
= dict_find(hs
->requests
, needle
, NULL
)))
824 } else if ((user
= GetUserH(needle
))) {
825 /* And finally, search by nick */
826 if (!(reqlist
= dict_find(helpserv_reqs_bynick_dict
, needle
, NULL
)))
828 helpserv_reqlist_init(&resultlist
);
830 for (i
=0; i
< reqlist
->used
; i
++) {
831 req
= reqlist
->list
[i
];
833 helpserv_reqlist_append(&resultlist
, req
);
838 /* If the nick didn't have anything, try their handle */
839 if (!resultlist
.used
&& user
->handle_info
) {
840 char star_handle
[NICKSERV_HANDLE_LEN
+2];
842 helpserv_reqlist_clean(&resultlist
);
843 sprintf(star_handle
, "*%s", user
->handle_info
->handle
);
845 return smart_get_request(hs
, hs_user
, star_handle
, num_results
);
851 if (resultlist
.used
== 0) {
852 helpserv_reqlist_clean(&resultlist
);
854 } else if (resultlist
.used
== 1) {
855 req
= resultlist
.list
[0];
856 helpserv_reqlist_clean(&resultlist
);
860 /* In case there is >1 request returned, use the oldest one assigned to
861 * the helper executing the command. Otherwise, use the oldest request.
862 * This may not be the intended result for cmd_pickup (first unhandled
863 * request may be better), or cmd_reassign (first handled request), but
864 * it's close enough, and there really aren't supposed to be multiple
865 * requests per person anyway; they're just side effects of authing. */
867 for (i
=0; i
< resultlist
.used
; i
++) {
868 req
= resultlist
.list
[i
];
869 if (!oldest
|| req
->opened
< oldest
->opened
)
871 if (hs_user
&& (!oldest_assigned
|| (req
->helper
== hs_user
&& req
->opened
< oldest_assigned
->opened
)))
872 oldest_assigned
= req
;
875 helpserv_reqlist_clean(&resultlist
);
877 return oldest_assigned
? oldest_assigned
: oldest
;
880 static struct helpserv_request
* create_request(struct userNode
*user
, struct helpserv_bot
*hs
, int from_join
) {
881 struct helpserv_request
*req
= calloc(1, sizeof(struct helpserv_request
));
882 char lbuf
[3][MAX_LINE_SIZE
], req_id
[INTERVALLEN
], abuf
[1][MAX_LINE_SIZE
];
883 struct helpserv_reqlist
*reqlist
, *hand_reqlist
;
884 struct helpserv_user
*hs_user
;
885 struct userNode
*target
, *next_un
= NULL
;
886 const unsigned int from_opserv
= 0;
887 const char *fmt
, *afmt
;
892 req
->id
= ++hs
->last_requestid
;
893 sprintf(req_id
, "%lu", req
->id
);
894 dict_insert(hs
->requests
, strdup(req_id
), req
);
899 if (hs
->last_requestid
< hs
->id_wrap
) {
900 for (i
=hs
->last_requestid
; i
< hs
->id_wrap
; i
++) {
901 sprintf(buf
, "%lu", i
);
902 if (!dict_find(hs
->requests
, buf
, NULL
)) {
903 hs
->last_requestid
= i
-1;
908 if (hs
->last_requestid
>= hs
->id_wrap
) {
909 for (i
=1; i
< hs
->id_wrap
; i
++) {
910 sprintf(buf
, "%lu", i
);
911 if (!dict_find(hs
->requests
, buf
, NULL
)) {
912 hs
->last_requestid
= i
-1;
916 if (i
>= hs
->id_wrap
) {
917 log_module(HS_LOG
, LOG_INFO
, "%s has more requests than its id_wrap.", hs
->helpserv
->nick
);
924 req
->text
= alloc_string_list(4);
926 req
->handle
= user
->handle_info
;
927 if (from_join
&& self
->burst
) {
928 extern time_t burst_begin
;
929 /* We need to keep all the requests during a burst join together,
930 * even if the burst takes more than 1 second. ircu seems to burst
931 * in reverse-join order. */
932 req
->opened
= burst_begin
;
938 if (!hs
->unhandled
) {
940 req
->next_unhandled
= NULL
;
941 } else if (self
->burst
&& hs
->unhandled
->opened
>= req
->opened
) {
942 req
->next_unhandled
= hs
->unhandled
;
944 } else if (self
->burst
) {
945 struct helpserv_request
*unh
;
946 /* Add the request to the beginning of the set of requests with
947 * req->opened having the same value. This makes reqonjoin create
948 * requests in the correct order while bursting. Note that this
949 * does not correct request ids, so they will be in reverse order
950 * though "/msg botname next" will work properly. */
951 for (unh
= hs
->unhandled
; unh
->next_unhandled
&& unh
->next_unhandled
->opened
< req
->opened
; unh
= unh
->next_unhandled
) ;
952 req
->next_unhandled
= unh
->next_unhandled
;
953 unh
->next_unhandled
= req
;
955 struct helpserv_request
*unh
;
957 for (unh
= hs
->unhandled
; unh
->next_unhandled
; unh
= unh
->next_unhandled
) ;
958 req
->next_unhandled
= NULL
;
959 unh
->next_unhandled
= req
;
962 if (!(reqlist
= dict_find(helpserv_reqs_bynick_dict
, user
->nick
, NULL
))) {
963 reqlist
= helpserv_reqlist_alloc();
964 dict_insert(helpserv_reqs_bynick_dict
, user
->nick
, reqlist
);
966 req
->parent_nick_list
= reqlist
;
967 helpserv_reqlist_append(reqlist
, req
);
969 if (user
->handle_info
) {
970 if (!(hand_reqlist
= dict_find(helpserv_reqs_byhand_dict
, user
->handle_info
->handle
, NULL
))) {
971 hand_reqlist
= helpserv_reqlist_alloc();
972 dict_insert(helpserv_reqs_byhand_dict
, user
->handle_info
->handle
, hand_reqlist
);
974 req
->parent_hand_list
= hand_reqlist
;
975 helpserv_reqlist_append(hand_reqlist
, req
);
977 req
->parent_hand_list
= NULL
;
981 fmt
= user_find_message(user
, "HSMSG_REQ_NEWONJOIN");
982 sprintf(lbuf
[0], fmt
, hs
->helpchan
->name
, req
->id
);
984 fmt
= user_find_message(user
, "HSMSG_REQ_NEW");
985 sprintf(lbuf
[0], fmt
, req
->id
);
988 if (req
!= hs
->unhandled
) {
989 intervalString(req_id
, now
- hs
->unhandled
->opened
, user
->handle_info
);
990 fmt
= user_find_message(user
, "HSMSG_REQ_UNHANDLED_TIME");
991 sprintf(lbuf
[1], fmt
, req_id
);
993 fmt
= user_find_message(user
, "HSMSG_REQ_NO_UNHANDLED");
994 sprintf(lbuf
[1], "%s", fmt
);
998 for (it
= dict_first(hs
->users
); it
; it
= iter_next(it
)) {
999 hs_user
= iter_data(it
);
1001 afmt
= user_find_message(user
, "HSMSG_REQ_ALERT");
1002 sprintf(abuf
[0], afmt
, req
->id
, hs
->helpchan
->name
, hs
->helpserv
->nick
, req
->id
, hs
->helpserv
->nick
);
1003 for (target
= hs_user
->handle
->users
; target
; target
= next_un
) {
1004 send_message_type(4, target
, hs
->helpserv
, "%s", abuf
[0]);
1005 next_un
= target
->next_authed
;
1010 switch (hs
->persist_types
[PERSIST_T_REQUEST
]) {
1012 fmt
= user_find_message(user
, "HSMSG_REQ_PERSIST_PART");
1013 sprintf(lbuf
[2], fmt
, hs
->helpchan
->name
, hs
->helpchan
->name
);
1016 fmt
= user_find_message(user
, "HSMSG_REQ_PERSIST_QUIT");
1017 sprintf(lbuf
[2], "%s", fmt
);
1020 log_module(HS_LOG
, LOG_ERROR
, "%s has an invalid req_persist.", hs
->helpserv
->nick
);
1022 if (user
->handle_info
) {
1023 fmt
= user_find_message(user
, "HSMSG_REQ_PERSIST_HANDLE");
1025 fmt
= user_find_message(user
, "HSMSG_REQ_PERSIST_QUIT");
1027 sprintf(lbuf
[2], "%s", fmt
);
1030 helpserv_message(hs
, user
, MSGTYPE_REQ_OPENED
);
1032 send_message_type(4, user
, opserv
, "%s %s %s", lbuf
[0], lbuf
[1], lbuf
[2]);
1034 send_message_type(4, user
, hs
->helpserv
, "%s %s %s", lbuf
[0], lbuf
[1], lbuf
[2]);
1036 if (hs
->req_on_join
&& req
== hs
->unhandled
&& hs
->helpchan_empty
&& !user
->uplink
->burst
) {
1037 timeq_del(0, run_empty_interval
, hs
, TIMEQ_IGNORE_WHEN
);
1038 run_empty_interval(hs
);
1044 /* Handle a message from a user to a HelpServ bot. */
1045 static void helpserv_usermsg(struct userNode
*user
, struct helpserv_bot
*hs
, const char *text
, char *argv
[], int argc
) {
1046 const int from_opserv
= 0; /* for helpserv_notice */
1047 struct helpserv_request
*req
=NULL
, *newest
=NULL
;
1048 struct helpserv_reqlist
*reqlist
, *hand_reqlist
;
1053 if ((reqlist
= dict_find(helpserv_reqs_bynick_dict
, user
->nick
, NULL
))) {
1054 for (i
=0; i
< reqlist
->used
; i
++) {
1055 req
= reqlist
->list
[i
];
1058 /* if (!newest || (newest->opened < req->opened)) ?? XXX: this is a noop.. commenting it out -rubin */
1059 /* newest is set to null 9 lines up, and isnt changed between.. */
1063 /* If nothing was found, this will set req to NULL */
1067 if (user
->handle_info
) {
1068 hand_reqlist
= dict_find(helpserv_reqs_byhand_dict
, user
->handle_info
->handle
, NULL
);
1069 if (!req
&& hand_reqlist
) {
1070 /* Most recent first again */
1071 for (i
=0; i
< hand_reqlist
->used
; i
++) {
1072 req
= hand_reqlist
->list
[i
];
1073 if ((req
->hs
!= hs
) || req
->user
)
1075 if (!newest
|| (newest
->opened
< req
->opened
))
1083 reqlist
= helpserv_reqlist_alloc();
1084 dict_insert(helpserv_reqs_bynick_dict
, user
->nick
, reqlist
);
1086 req
->parent_nick_list
= reqlist
;
1087 helpserv_reqlist_append(reqlist
, req
);
1089 if (req
->helper
&& (hs
->notify
>= NOTIFY_USER
))
1090 helpserv_notify(req
->helper
, "HSMSG_NOTIFY_USER_FOUND", req
->id
, user
->nick
);
1092 helpserv_msguser(user
, "HSMSG_GREET_PRIVMSG_EXISTREQ", req
->id
);
1098 if (argv
[1][0] == helpserv_conf
.user_escape
) {
1100 helpserv_usercmd_t
*usercmd
;
1101 struct userNode
*likely_helper
;
1103 /* Allow HELP user command but no other if no open request,
1104 this may change in the future if we make commands that
1105 dont need an open request */
1107 /* Find somebody likely to be the helper */
1108 likely_helper
= NULL
;
1110 cmdname
= argv
[1]+1;
1112 /* Call the user command function */
1113 usercmd
= dict_find(helpserv_usercmd_dict
, cmdname
, NULL
);
1114 if (usercmd
&& !strcasecmp(cmdname
, "HELP"))
1115 usercmd(req
, likely_helper
, NULL
, argc
, argv
, user
, hs
);
1117 helpserv_msguser(user
, "HSMSG_USERCMD_NO_REQUEST");
1121 if ((hs
->persist_types
[PERSIST_T_REQUEST
] == PERSIST_PART
) && !GetUserMode(hs
->helpchan
, user
)) {
1122 helpserv_msguser(user
, "HSMSG_REQ_YOU_NOT_IN_HELPCHAN_OPEN", hs
->helpchan
->name
);
1126 req
= create_request(user
, hs
, 0);
1127 if (user
->handle_info
)
1128 helpserv_page(PGSRC_STATUS
, "HSMSG_PAGE_NEW_REQUEST_AUTHED", req
->id
, user
->nick
, user
->handle_info
->handle
);
1130 helpserv_page(PGSRC_STATUS
, "HSMSG_PAGE_NEW_REQUEST_UNAUTHED", req
->id
, user
->nick
);
1131 } else if (argv
[1][0] == helpserv_conf
.user_escape
) {
1133 helpserv_usercmd_t
*usercmd
;
1134 struct userNode
*likely_helper
;
1136 /* Find somebody likely to be the helper */
1138 likely_helper
= NULL
;
1139 else if ((likely_helper
= req
->helper
->handle
->users
) && !likely_helper
->next_authed
) {
1140 /* only one user it could be :> */
1141 } else for (likely_helper
= req
->helper
->handle
->users
; likely_helper
; likely_helper
= likely_helper
->next_authed
)
1142 if (GetUserMode(hs
->helpchan
, likely_helper
))
1145 cmdname
= argv
[1]+1;
1147 /* Call the user command function */
1148 usercmd
= dict_find(helpserv_usercmd_dict
, cmdname
, NULL
);
1150 usercmd(req
, likely_helper
, text
, argc
, argv
, user
, hs
);
1152 helpserv_msguser(user
, "HSMSG_USERCMD_UNKNOWN", cmdname
);
1154 } else if (hs
->intervals
[INTERVAL_STALE_DELAY
]
1155 && (req
->updated
< (time_t)(now
- hs
->intervals
[INTERVAL_STALE_DELAY
]))
1156 && (!hs
->req_maxlen
|| req
->text
->used
< hs
->req_maxlen
)) {
1157 char buf
[MAX_LINE_SIZE
], updatestr
[INTERVALLEN
], timestr
[MAX_LINE_SIZE
];
1159 strftime(timestr
, MAX_LINE_SIZE
, HSFMT_TIME
, localtime(&req
->opened
));
1160 intervalString(updatestr
, now
- req
->updated
, user
->handle_info
);
1161 if (req
->helper
&& (hs
->notify
>= NOTIFY_USER
))
1162 if (user
->handle_info
)
1163 helpserv_notify(req
->helper
, "HSMSG_PAGE_UPD_REQUEST_AUTHED", req
->id
, user
->nick
, user
->handle_info
->handle
, timestr
, updatestr
);
1165 helpserv_notify(req
->helper
, "HSMSG_PAGE_UPD_REQUEST_NOT_AUTHED", req
->id
, user
->nick
, timestr
, updatestr
);
1167 if (user
->handle_info
)
1168 helpserv_page(PGSRC_STATUS
, "HSMSG_PAGE_UPD_REQUEST_AUTHED", req
->id
, user
->nick
, user
->handle_info
->handle
, timestr
, updatestr
);
1170 helpserv_page(PGSRC_STATUS
, "HSMSG_PAGE_UPD_REQUEST_NOT_AUTHED", req
->id
, user
->nick
, timestr
, updatestr
);
1171 strftime(timestr
, MAX_LINE_SIZE
, HSFMT_TIME
, localtime(&now
));
1172 snprintf(buf
, MAX_LINE_SIZE
, "[Stale request updated at %s]", timestr
);
1173 string_list_append(req
->text
, strdup(buf
));
1177 if (!hs
->req_maxlen
|| req
->text
->used
< hs
->req_maxlen
)
1179 string_list_append(req
->text
, strdup(text
));
1181 struct userNode
*likely_helper
;
1182 /* Find somebody likely to be the helper */
1184 likely_helper
= NULL
;
1185 else if ((likely_helper
= req
->helper
->handle
->users
) && !likely_helper
->next_authed
) {
1186 /* only one user it could be :> */
1187 } else for (likely_helper
= req
->helper
->handle
->users
; likely_helper
; likely_helper
= likely_helper
->next_authed
)
1188 if (GetUserMode(hs
->helpchan
, likely_helper
))
1192 send_target_message(1, likely_helper
->nick
, hs
->helpserv
, "HSMSQ_REQ_TEXT_ADDED", user
->nick
, text
);
1195 helpserv_msguser(user
, "HSMSG_REQ_MAXLEN");
1198 /* Handle messages direct to a HelpServ bot. */
1199 static void helpserv_botmsg(struct userNode
*user
, struct userNode
*target
, const char *text
, UNUSED_ARG(int server_qualified
)) {
1200 struct helpserv_bot
*hs
;
1201 struct helpserv_cmd
*cmd
;
1202 struct helpserv_user
*hs_user
= NULL
;
1203 char *argv
[MAXNUMPARAMS
];
1204 char tmpline
[MAXLEN
];
1205 int argc
, argv_shift
;
1206 const int from_opserv
= 0; /* for helpserv_notice */
1208 /* Ignore things consisting of empty lines or from ourselves */
1209 if (!*text
|| IsLocal(user
))
1212 hs
= dict_find(helpserv_bots_dict
, target
->nick
, NULL
);
1215 /* XXX: For some unknown reason we are shifting +1 on the array and ignoring argv[0]; to avoid someone rightly assuming argv[0]
1216 * was the first argument later on and addressing random memory, were going to make argv[0] null. This whole thing is pretty unacceptable and needs fixed, though.*/
1219 safestrncpy(tmpline
, text
, sizeof(tmpline
));
1220 argc
= split_line(tmpline
, false, ArrayLength(argv
)-argv_shift
, argv
+argv_shift
);
1224 /* XXX: why are we duplicating text here, and i don't see it being free'd anywhere.... */
1225 /* text = strdup(text); */
1227 if (user
->handle_info
)
1228 hs_user
= dict_find(hs
->users
, user
->handle_info
->handle
, NULL
);
1230 if (hs
->suspended
&& !IsOper(user
)) {
1231 helpserv_notice(user
, "HSMSG_SUSPENDED", hs
->helpserv
->nick
);
1235 /* See if we should listen to their message as a command (helper)
1236 * or a help request (user) */
1237 if (!user
->handle_info
|| !hs_user
) {
1238 helpserv_usermsg(user
, hs
, text
, argv
, argc
);
1242 cmd
= dict_find(helpserv_func_dict
, argv
[argv_shift
], NULL
);
1244 helpserv_notice(user
, "MSG_COMMAND_UNKNOWN", argv
[argv_shift
]);
1247 if (cmd
->flags
& CMD_FROM_OPSERV_ONLY
) {
1248 helpserv_notice(user
, "HSMSG_OPER_CMD");
1251 if (cmd
->access
> hs_user
->level
) {
1252 helpserv_notice(user
, "HSMSG_LEVEL_TOO_LOW");
1256 helpserv_notice(user
, "HSMSG_INTERNAL_COMMAND", argv
[argv_shift
]);
1257 } else if (cmd
->func(user
, hs
, 0, argc
, argv
+argv_shift
)) {
1258 unsplit_string(argv
+argv_shift
, argc
, tmpline
);
1259 log_audit(HS_LOG
, LOG_COMMAND
, user
, hs
->helpserv
, hs
->helpchan
->name
, 0, tmpline
);
1263 /* Handle a control command from an IRC operator */
1264 static MODCMD_FUNC(cmd_helpserv
) {
1265 struct helpserv_bot
*hs
= NULL
;
1266 struct helpserv_cmd
*subcmd
;
1267 const int from_opserv
= 1; /* for helpserv_notice */
1268 char botnick
[NICKLEN
+1]; /* in case command is unregister */
1272 send_help(user
, opserv
, helpserv_helpfile
, NULL
);
1276 if (!(subcmd
= dict_find(helpserv_func_dict
, argv
[1], NULL
))) {
1277 helpserv_notice(user
, "MSG_COMMAND_UNKNOWN", argv
[1]);
1281 if (!subcmd
->func
) {
1282 helpserv_notice(user
, "HSMSG_INTERNAL_COMMAND", argv
[1]);
1286 if ((subcmd
->flags
& CMD_NEED_BOT
) && ((argc
< 3) || !(hs
= dict_find(helpserv_bots_dict
, argv
[2], NULL
)))) {
1287 helpserv_notice(user
, "HSMSG_INVALID_BOT");
1291 if (subcmd
->flags
& CMD_NEVER_FROM_OPSERV
) {
1292 helpserv_notice(user
, "HSMSG_NO_USE_OPSERV");
1298 strcpy(botnick
, hs
->helpserv
->nick
);
1299 retval
= subcmd
->func(user
, hs
, 1, argc
-2, argv
+2);
1301 strcpy(botnick
, "No bot");
1302 retval
= subcmd
->func(user
, hs
, 1, argc
-1, argv
+1);
1308 static void helpserv_help(struct helpserv_bot
*hs
, int from_opserv
, struct userNode
*user
, const char *topic
, int from_user
) {
1309 char cmdname
[MAXLEN
];
1312 /* If there is no topic show the index */
1313 if (!topic
&& from_user
)
1314 topic
= "<userindex>";
1315 else if (!topic
&& !from_user
)
1318 /* make heading str (uppercase) */
1319 for (nn
=0; topic
[nn
]; nn
++)
1320 cmdname
[nn
] = toupper(topic
[nn
]);
1323 send_message(user
, (from_opserv
? opserv
: hs
->helpserv
), "HSMSG_HELP_TOPIC_HEADER", cmdname
);
1324 send_message(user
, (from_opserv
? opserv
: hs
->helpserv
), "HSMSG_HELP_DIVIDER");
1325 if (!send_help(user
, (from_opserv
? opserv
: hs
->helpserv
), helpserv_helpfile
, topic
))
1326 send_message(user
, (from_opserv
? opserv
: hs
->helpserv
), "MSG_TOPIC_UNKNOWN");
1327 send_message(user
, (from_opserv
? opserv
: hs
->helpserv
), "HSMSG_HELP_FOOTER");
1331 static int append_entry(const char *key
, UNUSED_ARG(void *data
), void *extra
) {
1332 struct helpfile_expansion
*exp
= extra
;
1335 row
= exp
->value
.table
.length
++;
1336 exp
->value
.table
.contents
[row
] = calloc(1, sizeof(char*));
1337 exp
->value
.table
.contents
[row
][0] = key
;
1341 static struct helpfile_expansion
helpserv_expand_variable(const char *variable
) {
1342 struct helpfile_expansion exp
;
1344 if (!irccasecmp(variable
, "index")) {
1345 exp
.type
= HF_TABLE
;
1346 exp
.value
.table
.length
= 1;
1347 exp
.value
.table
.width
= 1;
1348 exp
.value
.table
.flags
= TABLE_REPEAT_ROWS
;
1349 exp
.value
.table
.contents
= calloc(dict_size(helpserv_func_dict
)+1, sizeof(char**));
1350 exp
.value
.table
.contents
[0] = calloc(1, sizeof(char*));
1351 exp
.value
.table
.contents
[0][0] = "Commands:";
1352 dict_foreach(helpserv_func_dict
, append_entry
, &exp
);
1356 exp
.type
= HF_STRING
;
1357 exp
.value
.str
= NULL
;
1361 static void helpserv_helpfile_read(void) {
1362 helpserv_helpfile
= open_helpfile(HELPSERV_HELPFILE_NAME
, helpserv_expand_variable
);
1365 static HELPSERV_USERCMD(usercmd_wait
) {
1366 struct helpserv_request
*other
;
1368 char buf
[INTERVALLEN
];
1372 helpserv_user_reply("HSMSG_YOU_BEING_HELPED_BY", likely_helper
->nick
);
1374 helpserv_user_reply("HSMSG_YOU_BEING_HELPED");
1378 for (other
= req
->hs
->unhandled
, pos
= -1, count
= 0;
1380 other
= other
->next_unhandled
, ++count
) {
1385 intervalString(buf
, now
- req
->hs
->unhandled
->opened
, req
->user
->handle_info
);
1386 helpserv_user_reply("HSMSG_WAIT_STATUS", pos
+1, count
, buf
);
1391 static HELPSERV_USERCMD(usercmd_helper
) {
1394 helpserv_user_reply("HSMSG_HELPER", likely_helper
->nick
);
1396 helpserv_user_reply("HSMSG_NO_HELPER");
1401 static HELPSERV_USERCMD(usercmd_help
) {
1407 topic
= unsplit_string(argv
+2, argc
-1, NULL
);
1408 helpserv_help(hs
, 0, user
, topic
, 1);
1413 static HELPSERV_FUNC(cmd_help
) {
1419 topic
= unsplit_string(argv
+1, argc
-1, NULL
);
1420 helpserv_help(hs
, from_opserv
, user
, topic
, 0);
1425 static HELPSERV_FUNC(cmd_readhelp
) {
1426 struct timeval start
, stop
;
1427 struct helpfile
*old_helpfile
= helpserv_helpfile
;
1429 gettimeofday(&start
, NULL
);
1430 helpserv_helpfile_read();
1431 if (helpserv_helpfile
) {
1432 close_helpfile(old_helpfile
);
1434 helpserv_helpfile
= old_helpfile
;
1436 gettimeofday(&stop
, NULL
);
1437 stop
.tv_sec
-= start
.tv_sec
;
1438 stop
.tv_usec
-= start
.tv_usec
;
1439 if (stop
.tv_usec
< 0) {
1441 stop
.tv_usec
+= 1000000;
1443 helpserv_notice(user
, "HSMSG_READHELP_SUCCESS", stop
.tv_sec
, stop
.tv_usec
/1000);
1448 static struct helpserv_user
* helpserv_add_user(struct helpserv_bot
*hs
, struct handle_info
*handle
, enum helpserv_level level
) {
1449 struct helpserv_user
*hs_user
;
1450 struct helpserv_userlist
*userlist
;
1452 hs_user
= calloc(1, sizeof(struct helpserv_user
));
1453 hs_user
->handle
= handle
;
1455 hs_user
->help_mode
= 0;
1456 hs_user
->level
= level
;
1457 hs_user
->join_time
= find_handle_in_channel(hs
->helpchan
, handle
, NULL
) ? now
: 0;
1458 dict_insert(hs
->users
, handle
->handle
, hs_user
);
1460 if (!(userlist
= dict_find(helpserv_users_byhand_dict
, handle
->handle
, NULL
))) {
1461 userlist
= helpserv_userlist_alloc();
1462 dict_insert(helpserv_users_byhand_dict
, handle
->handle
, userlist
);
1464 helpserv_userlist_append(userlist
, hs_user
);
1469 static void helpserv_del_user(struct helpserv_bot
*hs
, struct helpserv_user
*hs_user
) {
1470 dict_remove(hs
->users
, hs_user
->handle
->handle
);
1473 static int cmd_add_user(struct helpserv_bot
*hs
, int from_opserv
, struct userNode
*user
, enum helpserv_level level
, int argc
, char *argv
[]) {
1474 struct helpserv_user
*actor
;
1475 struct handle_info
*handle
;
1480 actor
= GetHSUser(hs
, user
->handle_info
);
1481 if (actor
->level
< HlManager
) {
1482 helpserv_notice(user
, "HSMSG_CANNOT_ADD");
1489 if (!(handle
= helpserv_get_handle_info(user
, argv
[1])))
1492 if (GetHSUser(hs
, handle
)) {
1493 helpserv_notice(user
, "HSMSG_USER_EXISTS", handle
->handle
);
1497 if (!(from_opserv
) && actor
&& (actor
->level
<= level
)) {
1498 helpserv_notice(user
, "HSMSG_NO_BUMP_ACCESS");
1502 helpserv_add_user(hs
, handle
, level
);
1504 helpserv_notice(user
, "HSMSG_ADDED_USER", helpserv_level2str(level
), handle
->handle
);
1508 static HELPSERV_FUNC(cmd_deluser
) {
1509 struct helpserv_user
*actor
=NULL
, *victim
;
1510 struct handle_info
*handle
;
1511 enum helpserv_level level
;
1516 actor
= GetHSUser(hs
, user
->handle_info
);
1517 if (actor
->level
< HlManager
) {
1518 helpserv_notice(user
, "HSMSG_CANNOT_DEL");
1523 if (!(handle
= helpserv_get_handle_info(user
, argv
[1])))
1526 if (!(victim
= GetHSUser(hs
, handle
))) {
1527 helpserv_notice(user
, "HSMSG_NOT_IN_USERLIST", handle
->handle
, hs
->helpserv
->nick
);
1531 if (!from_opserv
&& actor
&& (actor
->level
<= victim
->level
)) {
1532 helpserv_notice(user
, "MSG_USER_OUTRANKED", victim
->handle
->handle
);
1536 level
= victim
->level
;
1537 helpserv_del_user(hs
, victim
);
1538 helpserv_notice(user
, "HSMSG_DELETED_USER", helpserv_level2str(level
), handle
->handle
);
1543 helpserv_user_comp(const void *arg_a
, const void *arg_b
)
1545 const struct helpserv_user
*a
= *(struct helpserv_user
**)arg_a
;
1546 const struct helpserv_user
*b
= *(struct helpserv_user
**)arg_b
;
1548 if (a
->level
!= b
->level
)
1549 res
= b
->level
- a
->level
;
1551 res
= irccasecmp(a
->handle
->handle
, b
->handle
->handle
);
1555 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
) {
1556 struct helpserv_userlist users
;
1557 struct helpfile_table tbl
;
1558 struct helpserv_user
*hs_user
;
1560 /* enum helpserv_level last_level; Zoot style */
1564 users
.size
= dict_size(hs
->users
);
1565 users
.list
= alloca(users
.size
*sizeof(hs
->users
[0]));
1566 helpserv_notice(user
, "HSMSG_USERLIST_HEADER", hs
->helpserv
->nick
);
1567 for (it
= dict_first(hs
->users
); it
; it
= iter_next(it
)) {
1568 hs_user
= iter_data(it
);
1569 if (hs_user
->level
< min_lvl
)
1571 if (hs_user
->level
> max_lvl
)
1573 users
.list
[users
.used
++] = hs_user
;
1576 helpserv_notice(user
, "MSG_NONE");
1579 qsort(users
.list
, users
.used
, sizeof(users
.list
[0]), helpserv_user_comp
);
1580 switch (user
->handle_info
->userlist_style
) {
1582 case HI_STYLE_NORMAL
:
1583 tbl
.length
= users
.used
+ 1;
1585 tbl
.flags
= TABLE_NO_FREE
;
1586 tbl
.contents
= alloca(tbl
.length
* sizeof(tbl
.contents
[0]));
1587 tbl
.contents
[0] = alloca(tbl
.width
* sizeof(tbl
.contents
[0][0]));
1588 tbl
.contents
[0][0] = "Level";
1589 tbl
.contents
[0][1] = "Handle";
1590 tbl
.contents
[0][2] = "WeekStart";
1591 for (ii
= 0; ii
< users
.used
; ) {
1592 hs_user
= users
.list
[ii
++];
1593 tbl
.contents
[ii
] = alloca(tbl
.width
* sizeof(tbl
.contents
[0][0]));
1594 tbl
.contents
[ii
][0] = helpserv_level_names
[hs_user
->level
];
1595 tbl
.contents
[ii
][1] = hs_user
->handle
->handle
;
1596 tbl
.contents
[ii
][2] = weekday_names
[hs_user
->week_start
];
1598 table_send((from_opserv
? opserv
: hs
->helpserv
), user
->nick
, 0, NULL
, tbl
);
1601 case HI_STYLE_ZOOT: default:
1602 last_level = HlNone;
1605 tbl.flags = TABLE_NO_FREE | TABLE_REPEAT_ROWS | TABLE_NO_HEADERS;
1606 tbl.contents = alloca(users.used * sizeof(tbl.contents[0]));
1607 for (ii = 0; ii < users.used; ) {
1608 hs_user = users.list[ii++];
1609 if (hs_user->level != last_level) {
1611 helpserv_notice(user, "HSMSG_USERLIST_ZOOT_LVL", hs->helpserv->nick, helpserv_level_names[last_level]);
1612 table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
1615 last_level = hs_user->level;
1617 tbl.contents[tbl.length] = alloca(tbl.width * sizeof(tbl.contents[0][0]));
1618 tbl.contents[tbl.length++][0] = hs_user->handle->handle;
1621 helpserv_notice(user, "HSMSG_USERLIST_ZOOT_LVL", hs->helpserv->nick, helpserv_level_names[last_level]);
1622 table_send((from_opserv ? opserv : hs->helpserv), user->nick, 0, NULL, tbl);
1629 static HELPSERV_FUNC(cmd_helpers
) {
1630 return show_helper_range(user
, hs
, from_opserv
, HlTrial
, HlOwner
);
1633 static HELPSERV_FUNC(cmd_wlist
) {
1634 return show_helper_range(user
, hs
, from_opserv
, HlOwner
, HlOwner
);
1637 static HELPSERV_FUNC(cmd_mlist
) {
1638 return show_helper_range(user
, hs
, from_opserv
, HlManager
, HlManager
);
1641 static HELPSERV_FUNC(cmd_hlist
) {
1642 return show_helper_range(user
, hs
, from_opserv
, HlHelper
, HlHelper
);
1645 static HELPSERV_FUNC(cmd_tlist
) {
1646 return show_helper_range(user
, hs
, from_opserv
, HlTrial
, HlTrial
);
1649 static HELPSERV_FUNC(cmd_addowner
) {
1650 return cmd_add_user(hs
, from_opserv
, user
, HlOwner
, argc
, argv
);
1653 static HELPSERV_FUNC(cmd_addmanager
) {
1654 return cmd_add_user(hs
, from_opserv
, user
, HlManager
, argc
, argv
);
1657 static HELPSERV_FUNC(cmd_addhelper
) {
1658 return cmd_add_user(hs
, from_opserv
, user
, HlHelper
, argc
, argv
);
1661 static HELPSERV_FUNC(cmd_addtrial
) {
1662 return cmd_add_user(hs
, from_opserv
, user
, HlTrial
, argc
, argv
);
1665 static HELPSERV_FUNC(cmd_clvl
) {
1666 struct helpserv_user
*actor
=NULL
, *victim
;
1667 struct handle_info
*handle
;
1668 enum helpserv_level level
;
1673 actor
= GetHSUser(hs
, user
->handle_info
);
1674 if (actor
->level
< HlManager
) {
1675 helpserv_notice(user
, "HSMSG_CANNOT_CLVL");
1680 if (!(handle
= helpserv_get_handle_info(user
, argv
[1])))
1683 if (!(victim
= GetHSUser(hs
, handle
))) {
1684 helpserv_notice(user
, "HSMSG_NOT_IN_USERLIST", handle
->handle
, hs
->helpserv
->nick
);
1688 if (((level
= helpserv_str2level(argv
[2])) == HlNone
) || level
== HlOper
) {
1689 helpserv_notice(user
, "HSMSG_INVALID_ACCESS", argv
[2]);
1693 if (!(from_opserv
) && actor
) {
1694 if (actor
== victim
) {
1695 helpserv_notice(user
, "HSMSG_NO_SELF_CLVL");
1699 if (actor
->level
<= victim
->level
) {
1700 helpserv_notice(user
, "MSG_USER_OUTRANKED", victim
->handle
->handle
);
1704 if (level
>= actor
->level
) {
1705 helpserv_notice(user
, "HSMSG_NO_BUMP_ACCESS");
1710 victim
->level
= level
;
1711 helpserv_notice(user
, "HSMSG_CHANGED_ACCESS", handle
->handle
, helpserv_level2str(level
));
1716 static void free_request(void *data
) {
1717 struct helpserv_request
*req
= data
;
1720 if (shutting_down
&& (req
->hs
->persist_types
[PERSIST_T_REQUEST
] != PERSIST_CLOSE
|| !req
->handle
)) {
1721 helpserv_log_request(req
, "X3 shutdown");
1724 /* Clean up from the unhandled queue */
1725 if (req
->hs
->unhandled
) {
1726 if (req
->hs
->unhandled
== req
) {
1727 req
->hs
->unhandled
= req
->next_unhandled
;
1729 struct helpserv_request
*uh
;
1730 for (uh
=req
->hs
->unhandled
; uh
->next_unhandled
&& (uh
->next_unhandled
!= req
); uh
= uh
->next_unhandled
);
1731 if (uh
->next_unhandled
) {
1732 uh
->next_unhandled
= req
->next_unhandled
;
1737 /* Clean up the lists */
1738 if (req
->parent_nick_list
) {
1739 if (req
->parent_nick_list
->used
== 1) {
1740 dict_remove(helpserv_reqs_bynick_dict
, req
->user
->nick
);
1742 helpserv_reqlist_remove(req
->parent_nick_list
, req
);
1745 if (req
->parent_hand_list
) {
1746 if (req
->parent_hand_list
->used
== 1) {
1747 dict_remove(helpserv_reqs_byhand_dict
, req
->handle
->handle
);
1749 helpserv_reqlist_remove(req
->parent_hand_list
, req
);
1753 free_string_list(req
->text
);
1757 static HELPSERV_FUNC(cmd_close
) {
1758 struct helpserv_request
*req
, *newest
=NULL
;
1759 struct helpserv_reqlist
*nick_list
, *hand_list
;
1760 struct helpserv_user
*hs_user
=GetHSUser(hs
, user
->handle_info
);
1761 struct userNode
*req_user
=NULL
;
1762 char close_reason
[MAXLEN
], reqnum
[12];
1763 unsigned long old_req
;
1771 if (!(req
= smart_get_request(hs
, hs_user
, argv
[1], &num_requests
))) {
1772 helpserv_notice(user
, "HSMSG_REQ_INVALID", argv
[1]);
1776 sprintf(reqnum
, "%lu", req
->id
);
1778 if (num_requests
> 1)
1779 helpserv_notice(user
, "HSMSG_REQ_FOUNDMANY");
1781 if (hs_user
->level
< HlManager
&& req
->helper
!= hs_user
) {
1783 helpserv_notice(user
, "HSMSG_REQ_NOT_YOURS_ASSIGNED_TO", req
->id
, req
->helper
->handle
->handle
);
1785 helpserv_notice(user
, "HSMSG_REQ_NOT_YOURS_UNASSIGNED", req
->id
);
1789 helpserv_notice(user
, "HSMSG_REQ_CLOSED", req
->id
);
1791 req_user
= req
->user
;
1792 helpserv_message(hs
, req
->user
, MSGTYPE_REQ_CLOSED
);
1794 helpserv_page(PGSRC_STATUS
, "HSMSG_PAGE_CLOSE_REQUEST_1", req
->id
, req
->user
->nick
, req
->handle
->handle
, user
->nick
);
1796 helpserv_page(PGSRC_STATUS
, "HSMSG_PAGE_CLOSE_REQUEST_2", req
->id
, req
->user
->nick
, user
->nick
);
1799 helpserv_page(PGSRC_STATUS
, "HSMSG_PAGE_CLOSE_REQUEST_3", req
->id
, req
->handle
->handle
, user
->nick
);
1801 helpserv_page(PGSRC_STATUS
, "HSMSG_PAGE_CLOSE_REQUEST_4", req
->id
, user
->nick
);
1804 hs_user
->closed
[0]++;
1805 hs_user
->closed
[4]++;
1807 /* Set these to keep track of the lists after the request is gone, but
1808 * not if free_request() will helpserv_reqlist_free() them. */
1809 nick_list
= req
->parent_nick_list
;
1810 if (nick_list
&& (nick_list
->used
== 1))
1812 hand_list
= req
->parent_hand_list
;
1813 if (hand_list
&& (hand_list
->used
== 1))
1818 snprintf(close_reason
, MAXLEN
, "Closed by %s: %s", user
->handle_info
->handle
, unsplit_string(argv
+2, argc
-2, NULL
));
1820 sprintf(close_reason
, "Closed by %s", user
->handle_info
->handle
);
1822 helpserv_log_request(req
, close_reason
);
1823 dict_remove(hs
->requests
, reqnum
);
1825 /* Look for other requests associated with them */
1827 for (i
=0; i
< nick_list
->used
; i
++) {
1828 req
= nick_list
->list
[i
];
1832 if (!newest
|| (newest
->opened
< req
->opened
))
1837 helpserv_msguser(newest
->user
, "HSMSG_REQ_FOUND_ANOTHER", old_req
, newest
->id
);
1840 if (req_user
&& hs
->auto_devoice
) {
1841 struct modeNode
*mn
= GetUserMode(hs
->helpchan
, req_user
);
1842 if ((!newest
|| !newest
->helper
) && mn
&& (mn
->modes
& MODE_VOICE
)) {
1843 struct mod_chanmode change
;
1844 mod_chanmode_init(&change
);
1846 change
.args
[0].mode
= MODE_REMOVE
| MODE_VOICE
;
1847 change
.args
[0].u
.member
= mn
;
1848 mod_chanmode_announce(hs
->helpserv
, hs
->helpchan
, &change
);
1855 static HELPSERV_USERCMD(usercmd_close
) {
1856 struct helpserv_request
*newest
=NULL
;
1857 struct helpserv_reqlist
*nick_list
, *hand_list
;
1858 char close_reason
[MAXLEN
], reqnum
[12];
1859 struct userNode
*req_user
=NULL
;
1860 unsigned long old_req
;
1862 int num_requests
=0, from_opserv
=0;
1866 sprintf(reqnum
, "%lu", req
->id
);
1868 if (num_requests
> 1) {
1869 helpserv_notice(user
, "HSMSG_REQ_FOUNDMANY");
1873 helpserv_notice(user
, "HSMSG_REQ_CLOSED", req
->id
);
1876 req_user
= req
->user
;
1877 helpserv_message(hs
, req
->user
, MSGTYPE_REQ_CLOSED
);
1879 helpserv_page(PGSRC_STATUS
, "HSMSG_PAGE_CLOSE_REQUEST_1", req
->id
, req
->user
->nick
, req
->handle
->handle
, user
->nick
);
1881 helpserv_page(PGSRC_STATUS
, "HSMSG_PAGE_CLOSE_REQUEST_2", req
->id
, req
->user
->nick
, user
->nick
);
1884 helpserv_page(PGSRC_STATUS
, "HSMSG_PAGE_CLOSE_REQUEST_3", req
->id
, req
->handle
->handle
, user
->nick
);
1886 helpserv_page(PGSRC_STATUS
, "HSMSG_PAGE_CLOSE_REQUEST_4", req
->id
, user
->nick
);
1889 /* Set these to keep track of the lists after the request is gone, but
1890 * not if free_request() will helpserv_reqlist_free() them. */
1891 nick_list
= req
->parent_nick_list
;
1892 if (nick_list
&& (nick_list
->used
== 1))
1894 hand_list
= req
->parent_hand_list
;
1895 if (hand_list
&& (hand_list
->used
== 1))
1900 if (user
->handle_info
)
1901 snprintf(close_reason
, MAXLEN
, "Closed by %s: %s", user
->handle_info
->handle
, unsplit_string(argv
+1, argc
-1, NULL
));
1903 snprintf(close_reason
, MAXLEN
, "Closed by %s: %s", user
->nick
, unsplit_string(argv
+1, argc
-1, NULL
));
1905 if (user
->handle_info
)
1906 sprintf(close_reason
, "Closed by %s", user
->nick
);
1908 sprintf(close_reason
, "Closed by %s", user
->handle_info
->handle
);
1910 helpserv_log_request(req
, close_reason
);
1911 dict_remove(hs
->requests
, reqnum
);
1913 /* Look for other requests associated with them */
1915 for (i
=0; i
< nick_list
->used
; i
++) {
1916 req
= nick_list
->list
[i
];
1920 if (!newest
|| (newest
->opened
< req
->opened
))
1925 helpserv_msguser(newest
->user
, "HSMSG_REQ_FOUND_ANOTHER", old_req
, newest
->id
);
1928 if (req_user
&& hs
->auto_devoice
) {
1929 struct modeNode
*mn
= GetUserMode(hs
->helpchan
, req_user
);
1930 if ((!newest
|| !newest
->helper
) && mn
&& (mn
->modes
& MODE_VOICE
)) {
1931 struct mod_chanmode change
;
1932 mod_chanmode_init(&change
);
1934 change
.args
[0].mode
= MODE_REMOVE
| MODE_VOICE
;
1935 change
.args
[0].u
.member
= mn
;
1936 mod_chanmode_announce(hs
->helpserv
, hs
->helpchan
, &change
);
1943 static HELPSERV_FUNC(cmd_list
) {
1946 struct helpfile_table tbl
;
1947 unsigned int line
, total
;
1948 struct helpserv_request
*req
;
1950 if ((argc
< 2) || !irccasecmp(argv
[1], "unassigned")) {
1951 for (req
= hs
->unhandled
, total
=0; req
; req
= req
->next_unhandled
, total
++) ;
1952 helpserv_notice(user
, "HSMSG_REQ_LIST_TOP_UNASSIGNED", total
);
1953 searchtype
= 1; /* Unassigned */
1954 } else if (!irccasecmp(argv
[1], "assigned")) {
1955 for (req
= hs
->unhandled
, total
=dict_size(hs
->requests
); req
; req
= req
->next_unhandled
, total
--) ;
1956 helpserv_notice(user
, "HSMSG_REQ_LIST_TOP_ASSIGNED", total
);
1957 searchtype
= 2; /* Assigned */
1958 } else if (!irccasecmp(argv
[1], "me")) {
1959 for (total
= 0, it
= dict_first(hs
->requests
); it
; it
= iter_next(it
)) {
1960 req
= iter_data(it
);
1961 if (req
->helper
&& (req
->helper
->handle
== user
->handle_info
))
1964 helpserv_notice(user
, "HSMSG_REQ_LIST_TOP_YOUR", total
);
1966 } else if (!irccasecmp(argv
[1], "all")) {
1967 total
= dict_size(hs
->requests
);
1968 helpserv_notice(user
, "HSMSG_REQ_LIST_TOP_ALL", total
);
1969 searchtype
= 3; /* All */
1971 helpserv_notice(user
, "HSMSG_BAD_REQ_TYPE", argv
[1]);
1976 helpserv_notice(user
, "HSMSG_REQ_LIST_NONE");
1980 tbl
.length
= total
+1;
1982 tbl
.flags
= TABLE_NO_FREE
;
1983 tbl
.contents
= alloca(tbl
.length
* sizeof(*tbl
.contents
));
1984 tbl
.contents
[0] = alloca(tbl
.width
* sizeof(**tbl
.contents
));
1985 tbl
.contents
[0][0] = "ID#";
1986 tbl
.contents
[0][1] = "User";
1987 tbl
.contents
[0][2] = "Helper";
1988 tbl
.contents
[0][3] = "Time open";
1989 tbl
.contents
[0][4] = "User status";
1991 for (it
=dict_first(hs
->requests
), line
=0; it
; it
=iter_next(it
)) {
1992 char opentime
[INTERVALLEN
], reqid
[12], username
[NICKLEN
+2];
1994 req
= iter_data(it
);
1996 switch (searchtype
) {
2009 if (!req
->helper
|| (req
->helper
->handle
!= user
->handle_info
))
2016 tbl
.contents
[line
] = alloca(tbl
.width
* sizeof(**tbl
.contents
));
2017 sprintf(reqid
, "%lu", req
->id
);
2018 tbl
.contents
[line
][0] = strdup(reqid
);
2020 strcpy(username
, req
->user
->nick
);
2023 strcpy(username
+1, req
->handle
->handle
);
2025 tbl
.contents
[line
][1] = strdup(username
);
2026 tbl
.contents
[line
][2] = req
->helper
? req
->helper
->handle
->handle
: "(Unassigned)";
2027 intervalString(opentime
, now
- req
->opened
, user
->handle_info
);
2028 tbl
.contents
[line
][3] = strdup(opentime
);
2029 tbl
.contents
[line
][4] = ((req
->user
|| req
->handle
->users
) ? "Online" : "Offline");
2032 table_send((from_opserv
? opserv
: hs
->helpserv
), user
->nick
, 0, NULL
, tbl
);
2034 for (; line
> 0; line
--) {
2035 free((char *)tbl
.contents
[line
][0]);
2036 free((char *)tbl
.contents
[line
][1]);
2037 free((char *)tbl
.contents
[line
][3]);
2043 static void helpserv_show(int from_opserv
, struct helpserv_bot
*hs
, struct userNode
*user
, struct helpserv_request
*req
) {
2045 char buf
[MAX_LINE_SIZE
];
2046 char buf2
[INTERVALLEN
];
2050 helpserv_notice(user
, "HSMSG_REQ_INFO_2a", req
->user
->nick
, req
->handle
->handle
);
2052 helpserv_notice(user
, "HSMSG_REQ_INFO_2b", req
->user
->nick
);
2053 else if (req
->handle
)
2054 if (req
->handle
->users
)
2055 helpserv_notice(user
, "HSMSG_REQ_INFO_2c", req
->handle
->handle
);
2057 helpserv_notice(user
, "HSMSG_REQ_INFO_2d", req
->handle
->handle
);
2059 helpserv_notice(user
, "HSMSG_REQ_INFO_2e");
2060 strftime(buf
, MAX_LINE_SIZE
, HSFMT_TIME
, localtime(&req
->opened
));
2061 intervalString(buf2
, now
- req
->opened
, user
->handle_info
);
2062 helpserv_notice(user
, "HSMSG_REQ_INFO_3", buf
, buf2
);
2063 helpserv_notice(user
, "HSMSG_REQ_INFO_4");
2064 for (nn
=0; nn
< req
->text
->used
; nn
++)
2065 helpserv_notice(user
, "HSMSG_REQ_INFO_MESSAGE", req
->text
->list
[nn
]);
2068 /* actor is the one who executed the command... it should == user except from
2070 static int helpserv_assign(int from_opserv
, struct helpserv_bot
*hs
, struct userNode
*user
, struct userNode
*actor
, struct helpserv_request
*req
) {
2071 struct helpserv_request
*req2
;
2072 struct helpserv_user
*old_helper
;
2074 if (!user
->handle_info
)
2076 if ((hs
->persist_types
[PERSIST_T_HELPER
] == PERSIST_PART
) && !GetUserMode(hs
->helpchan
, user
)) {
2077 struct helpserv_user
*hsuser_actor
= GetHSUser(hs
, actor
->handle_info
);
2078 if (hsuser_actor
->level
< HlManager
) {
2079 helpserv_notice(user
, "HSMSG_REQ_YOU_NOT_IN_HELPCHAN", hs
->helpchan
->name
);
2081 } else if (user
!= actor
) {
2082 helpserv_notice(user
, "HSMSG_REQ_YOU_NOT_IN_OVERRIDE", hs
->helpchan
->name
);
2083 helpserv_notice(actor
, "HSMSG_REQ_HIM_NOT_IN_OVERRIDE", user
->nick
, hs
->helpchan
->name
);
2085 helpserv_notice(user
, "HSMSG_REQ_SELF_NOT_IN_OVERRIDE", hs
->helpchan
->name
);
2088 hs
->last_active
= now
;
2089 if ((old_helper
= req
->helper
)) {
2090 /* don't need to remove from the unhandled queue */
2091 } else if (hs
->unhandled
== req
) {
2092 hs
->unhandled
= req
->next_unhandled
;
2093 } else for (req2
= hs
->unhandled
; req2
; req2
= req2
->next_unhandled
) {
2094 if (req2
->next_unhandled
== req
) {
2095 req2
->next_unhandled
= req
->next_unhandled
;
2099 req
->next_unhandled
= NULL
;
2100 req
->helper
= GetHSUser(hs
, user
->handle_info
);
2101 assert(req
->helper
);
2102 req
->assigned
= now
;
2105 helpserv_notice(user
, "HSMSG_REQ_REASSIGNED", req
->id
, old_helper
->handle
->handle
);
2106 req
->helper
->reassigned_to
[0]++;
2107 req
->helper
->reassigned_to
[4]++;
2108 old_helper
->reassigned_from
[0]++;
2109 old_helper
->reassigned_from
[4]++;
2111 helpserv_notice(user
, "HSMSG_REQ_ASSIGNED_YOU", req
->id
);
2112 req
->helper
->picked_up
[0]++;
2113 req
->helper
->picked_up
[4]++;
2115 helpserv_show(from_opserv
, hs
, user
, req
);
2117 helpserv_message(hs
, req
->user
, MSGTYPE_REQ_ASSIGNED
);
2119 helpserv_msguser(req
->user
, "HSMSG_REQ_ASSIGNED_AGAIN", req
->id
, user
->handle_info
->handle
, user
->nick
);
2121 helpserv_msguser(req
->user
, "HSMSG_REQ_ASSIGNED", req
->id
, user
->handle_info
->handle
, user
->nick
);
2124 helpserv_page(PGSRC_STATUS
, "HSMSG_PAGE_ASSIGN_REQUEST_1", req
->id
, req
->user
->nick
, req
->handle
->handle
, user
->nick
);
2126 helpserv_page(PGSRC_STATUS
, "HSMSG_PAGE_ASSIGN_REQUEST_2", req
->id
, req
->user
->nick
, user
->nick
);
2129 helpserv_page(PGSRC_STATUS
, "HSMSG_PAGE_ASSIGN_REQUEST_3", req
->id
, req
->handle
->handle
, user
->nick
);
2131 helpserv_page(PGSRC_STATUS
, "HSMSG_PAGE_ASSIGN_REQUEST_4", req
->id
, user
->nick
);
2134 if (req
->user
&& hs
->auto_voice
) {
2135 struct mod_chanmode change
;
2136 mod_chanmode_init(&change
);
2138 change
.args
[0].mode
= MODE_VOICE
;
2139 if ((change
.args
[0].u
.member
= GetUserMode(hs
->helpchan
, req
->user
)))
2140 mod_chanmode_announce(hs
->helpserv
, hs
->helpchan
, &change
);
2146 static HELPSERV_FUNC(cmd_next
) {
2147 struct helpserv_request
*req
;
2149 if (!(req
= hs
->unhandled
)) {
2150 helpserv_notice(user
, "HSMSG_REQ_NO_UNASSIGNED");
2153 return helpserv_assign(from_opserv
, hs
, user
, user
, req
);
2156 static HELPSERV_FUNC(cmd_show
) {
2157 struct helpserv_request
*req
;
2158 struct helpserv_user
*hs_user
=GetHSUser(hs
, user
->handle_info
);
2163 if (!(req
= smart_get_request(hs
, hs_user
, argv
[1], &num_requests
))) {
2164 helpserv_notice(user
, "HSMSG_REQ_INVALID", argv
[1]);
2168 if (num_requests
> 1)
2169 helpserv_notice(user
, "HSMSG_REQ_FOUNDMANY");
2171 helpserv_notice(user
, "HSMSG_REQ_INFO_1", req
->id
);
2172 helpserv_show(from_opserv
, hs
, user
, req
);
2176 static HELPSERV_USERCMD(usercmd_show
) {
2180 if (num_requests
> 1)
2181 helpserv_notice(user
, "HSMSG_REQ_FOUNDMANY");
2183 helpserv_notice(user
, "HSMSG_REQ_INFO_1", req
->id
);
2184 helpserv_show(from_opserv
, hs
, user
, req
);
2188 static HELPSERV_FUNC(cmd_pickup
) {
2189 struct helpserv_request
*req
;
2190 struct helpserv_user
*hs_user
=GetHSUser(hs
, user
->handle_info
);
2197 if (!(req
= smart_get_request(hs
, hs_user
, argv
[1], &num_requests
))) {
2198 helpserv_notice(user
, "HSMSG_REQ_INVALID", argv
[1]);
2202 if (num_requests
> 1)
2203 helpserv_notice(user
, "HSMSG_REQ_FOUNDMANY");
2205 return helpserv_assign(from_opserv
, hs
, user
, user
, req
);
2208 static HELPSERV_FUNC(cmd_reassign
) {
2209 struct helpserv_request
*req
;
2210 struct userNode
*targetuser
;
2211 struct helpserv_user
*target
;
2212 struct helpserv_user
*hs_user
=GetHSUser(hs
, user
->handle_info
);
2219 if (!(req
= smart_get_request(hs
, hs_user
, argv
[1], &num_requests
))) {
2220 helpserv_notice(user
, "HSMSG_REQ_INVALID", argv
[1]);
2224 if (num_requests
> 1)
2225 helpserv_notice(user
, "HSMSG_REQ_FOUNDMANY");
2227 if (!(targetuser
= GetUserH(argv
[2]))) {
2228 helpserv_notice(user
, "MSG_NICK_UNKNOWN", argv
[2]);
2232 if (!targetuser
->handle_info
) {
2233 helpserv_notice(user
, "MSG_USER_AUTHENTICATE", targetuser
->nick
);
2237 if (!(target
= GetHSUser(hs
, targetuser
->handle_info
))) {
2238 helpserv_notice(user
, "HSMSG_NOT_IN_USERLIST", targetuser
->nick
, hs
->helpserv
->nick
);
2242 if ((hs
->persist_types
[PERSIST_T_HELPER
] == PERSIST_PART
) && !GetUserMode(hs
->helpchan
, user
) && (hs_user
->level
< HlManager
)) {
2243 helpserv_notice(user
, "HSMSG_REQ_HIM_NOT_IN_HELPCHAN", targetuser
->nick
, hs
->helpchan
->name
);
2247 helpserv_assign(from_opserv
, hs
, targetuser
, user
, req
);
2251 static HELPSERV_FUNC(cmd_addnote
) {
2252 char text
[MAX_LINE_SIZE
], timestr
[MAX_LINE_SIZE
], *note
;
2253 struct helpserv_request
*req
;
2254 struct helpserv_user
*hs_user
=GetHSUser(hs
, user
->handle_info
);
2259 if (!(req
= smart_get_request(hs
, hs_user
, argv
[1], &num_requests
))) {
2260 helpserv_notice(user
, "HSMSG_REQ_INVALID", argv
[1]);
2264 if (num_requests
> 1)
2265 helpserv_notice(user
, "HSMSG_REQ_FOUNDMANY");
2267 note
= unsplit_string(argv
+2, argc
-2, NULL
);
2269 strftime(timestr
, MAX_LINE_SIZE
, HSFMT_TIME
, localtime(&now
));
2270 snprintf(text
, MAX_LINE_SIZE
, "[Helper note at %s]:", timestr
);
2271 string_list_append(req
->text
, strdup(text
));
2272 snprintf(text
, MAX_LINE_SIZE
, " <%s> %s", user
->handle_info
->handle
, note
);
2273 string_list_append(req
->text
, strdup(text
));
2275 helpserv_notice(user
, "HSMSG_REQMSG_NOTE_ADDED", req
->id
);
2280 static HELPSERV_FUNC(cmd_page
) {
2283 helpserv_page(PGSRC_COMMAND
, "HSMSG_PAGE_REQUEST", user
->nick
, unsplit_string(argv
+1, argc
-1, NULL
));
2288 static HELPSERV_FUNC(cmd_stats
) {
2289 struct helpserv_user
*target
, *hs_user
;
2290 struct handle_info
*target_handle
;
2291 struct helpfile_table tbl
;
2293 char intervalstr
[INTERVALLEN
], buf
[16];
2295 hs_user
= from_opserv
? NULL
: GetHSUser(hs
, user
->handle_info
);
2298 if (!from_opserv
&& (hs_user
->level
< HlManager
)) {
2299 helpserv_notice(user
, "HSMSG_NEED_MANAGER");
2303 if (!(target_handle
= helpserv_get_handle_info(user
, argv
[1]))) {
2307 if (!(target
= GetHSUser(hs
, target_handle
))) {
2308 helpserv_notice(user
, "HSMSG_NOT_IN_USERLIST", target_handle
->handle
, hs
->helpserv
->nick
);
2313 helpserv_notice(user
, "HSMSG_OPSERV_NEED_USER");
2319 helpserv_notice(user
, "HSMSG_STATS_TOP", hs
->helpserv
->nick
, target
->handle
->handle
, weekday_names
[target
->week_start
]);
2323 tbl
.flags
= TABLE_NO_FREE
;
2324 tbl
.contents
= alloca(tbl
.length
* sizeof(*tbl
.contents
));
2325 tbl
.contents
[0] = alloca(tbl
.width
* sizeof(**tbl
.contents
));
2326 tbl
.contents
[0][0] = "";
2327 tbl
.contents
[0][1] = "Recorded time";
2328 for (i
=0; i
< 5; i
++) {
2329 unsigned int week_time
= target
->time_per_week
[i
];
2330 tbl
.contents
[i
+1] = alloca(tbl
.width
* sizeof(**tbl
.contents
));
2331 if ((i
== 0 || i
== 4) && target
->join_time
)
2332 week_time
+= now
- target
->join_time
;
2333 helpserv_interval(intervalstr
, week_time
);
2334 tbl
.contents
[i
+1][1] = strdup(intervalstr
);
2336 tbl
.contents
[1][0] = "This week";
2337 tbl
.contents
[2][0] = "Last week";
2338 tbl
.contents
[3][0] = "2 weeks ago";
2339 tbl
.contents
[4][0] = "3 weeks ago";
2340 tbl
.contents
[5][0] = "Total";
2342 helpserv_notice(user
, "HSMSG_STATS_TIME", hs
->helpchan
->name
);
2343 table_send((from_opserv
? opserv
: hs
->helpserv
), user
->nick
, 0, NULL
, tbl
);
2345 for (i
=1; i
<= 5; i
++)
2346 free((char *)tbl
.contents
[i
][1]);
2350 tbl
.flags
= TABLE_NO_FREE
;
2351 tbl
.contents
= alloca(tbl
.length
* sizeof(*tbl
.contents
));
2352 tbl
.contents
[0] = alloca(tbl
.width
* sizeof(**tbl
.contents
));
2353 tbl
.contents
[1] = alloca(tbl
.width
* sizeof(**tbl
.contents
));
2354 tbl
.contents
[2] = alloca(tbl
.width
* sizeof(**tbl
.contents
));
2355 tbl
.contents
[3] = alloca(tbl
.width
* sizeof(**tbl
.contents
));
2356 tbl
.contents
[4] = alloca(tbl
.width
* sizeof(**tbl
.contents
));
2357 tbl
.contents
[0][0] = "Category";
2358 tbl
.contents
[0][1] = "This week";
2359 tbl
.contents
[0][2] = "Last week";
2360 tbl
.contents
[0][3] = "Total";
2362 tbl
.contents
[1][0] = "Requests picked up";
2363 for (i
=0; i
< 3; i
++) {
2364 sprintf(buf
, "%u", target
->picked_up
[(i
== 2 ? 4 : i
)]);
2365 tbl
.contents
[1][i
+1] = strdup(buf
);
2367 tbl
.contents
[2][0] = "Requests closed";
2368 for (i
=0; i
< 3; i
++) {
2369 sprintf(buf
, "%u", target
->closed
[(i
== 2 ? 4 : i
)]);
2370 tbl
.contents
[2][i
+1] = strdup(buf
);
2372 tbl
.contents
[3][0] = "Reassigned from";
2373 for (i
=0; i
< 3; i
++) {
2374 sprintf(buf
, "%u", target
->reassigned_from
[(i
== 2 ? 4 : i
)]);
2375 tbl
.contents
[3][i
+1] = strdup(buf
);
2377 tbl
.contents
[4][0] = "Reassigned to";
2378 for (i
=0; i
< 3; i
++) {
2379 sprintf(buf
, "%u", target
->reassigned_to
[(i
== 2 ? 4 : i
)]);
2380 tbl
.contents
[4][i
+1] = strdup(buf
);
2383 helpserv_notice(user
, "HSMSG_STATS_REQS");
2384 table_send((from_opserv
? opserv
: hs
->helpserv
), user
->nick
, 0, NULL
, tbl
);
2386 for (i
=1; i
< 5; i
++) {
2387 free((char *)tbl
.contents
[i
][1]);
2388 free((char *)tbl
.contents
[i
][2]);
2389 free((char *)tbl
.contents
[i
][3]);
2395 static HELPSERV_FUNC(cmd_statsreport
) {
2397 struct helpfile_table tbl
;
2399 unsigned int line
, i
;
2400 struct userNode
*srcbot
= from_opserv
? opserv
: hs
->helpserv
;
2402 if ((argc
> 1) && !irccasecmp(argv
[1], "NOTICE"))
2405 tbl
.length
= dict_size(hs
->users
)+1;
2407 tbl
.flags
= TABLE_NO_FREE
;
2408 tbl
.contents
= alloca(tbl
.length
* sizeof(*tbl
.contents
));
2409 tbl
.contents
[0] = alloca(tbl
.width
* sizeof(**tbl
.contents
));
2410 tbl
.contents
[0][0] = "Account";
2411 tbl
.contents
[0][1] = "Requests";
2412 tbl
.contents
[0][2] = "Time helping";
2414 for (it
=dict_first(hs
->users
), line
=0; it
; it
=iter_next(it
)) {
2415 struct helpserv_user
*hs_user
=iter_data(it
);
2417 tbl
.contents
[++line
] = alloca(tbl
.width
* sizeof(**tbl
.contents
));
2418 tbl
.contents
[line
][0] = hs_user
->handle
->handle
;
2419 tbl
.contents
[line
][1] = malloc(12);
2420 tbl
.contents
[line
][2] = malloc(32); /* A bit more than needed */
2423 /* 4 to 1 instead of 3 to 0 because it's unsigned */
2424 for (i
=4; i
> 0; i
--) {
2425 for (it
=dict_first(hs
->users
), line
=0; it
; it
=iter_next(it
)) {
2426 struct helpserv_user
*hs_user
= iter_data(it
);
2428 unsigned int week_time
= hs_user
->time_per_week
[i
-1];
2429 if ((i
==1) && hs_user
->join_time
)
2430 week_time
+= now
- hs_user
->join_time
;
2431 helpserv_interval((char *)tbl
.contents
[++line
][2], week_time
);
2434 sprintf((char *)tbl
.contents
[line
][1], "%u", hs_user
->picked_up
[i
-1]+hs_user
->reassigned_to
[i
-1]);
2436 send_target_message(use_privmsg
, user
->nick
, srcbot
, statsreport_week
[i
-1]);
2437 table_send(srcbot
, user
->nick
, 0, (use_privmsg
? irc_privmsg
: irc_notice
), tbl
);
2440 for (line
=1; line
<= dict_size(hs
->users
); line
++) {
2441 free((char *)tbl
.contents
[line
][1]);
2442 free((char *)tbl
.contents
[line
][2]);
2449 helpserv_in_channel(struct helpserv_bot
*hs
, struct chanNode
*channel
) {
2450 enum page_source pgsrc
;
2451 if (channel
== hs
->helpchan
)
2453 for (pgsrc
=0; pgsrc
<PGSRC_COUNT
; pgsrc
++)
2454 if (channel
== hs
->page_targets
[pgsrc
])
2459 static HELPSERV_FUNC(cmd_move
) {
2461 helpserv_notice(user
, "HSMSG_INVALID_BOT");
2467 if (is_valid_nick(argv
[1])) {
2468 char *newnick
= argv
[1], oldnick
[NICKLEN
];
2470 strcpy(oldnick
, hs
->helpserv
->nick
);
2472 if (GetUserH(newnick
)) {
2473 helpserv_notice(user
, "HSMSG_NICK_EXISTS", newnick
);
2477 dict_remove2(helpserv_bots_dict
, hs
->helpserv
->nick
, 1);
2478 NickChange(hs
->helpserv
, newnick
, 0);
2479 dict_insert(helpserv_bots_dict
, hs
->helpserv
->nick
, hs
);
2481 helpserv_notice(user
, "HSMSG_RENAMED", oldnick
, newnick
);
2483 global_message_args(MESSAGE_RECIPIENT_OPERS
, "HSMSG_BOT_RENAMED", oldnick
,
2484 hs
->helpchan
->name
, newnick
, user
->nick
);
2487 } else if (IsChannelName(argv
[1])) {
2488 struct chanNode
*old_helpchan
= hs
->helpchan
;
2489 char *newchan
= argv
[1], oldchan
[CHANNELLEN
], reason
[MAXLEN
];
2490 struct helpserv_botlist
*botlist
;
2492 strcpy(oldchan
, hs
->helpchan
->name
);
2494 if (!irccasecmp(oldchan
, newchan
)) {
2495 helpserv_notice(user
, "HSMSG_MOVE_SAME_CHANNEL", hs
->helpserv
->nick
);
2499 if (opserv_bad_channel(newchan
)) {
2500 helpserv_notice(user
, "HSMSG_ILLEGAL_CHANNEL", newchan
);
2504 botlist
= dict_find(helpserv_bots_bychan_dict
, hs
->helpchan
->name
, NULL
);
2505 helpserv_botlist_remove(botlist
, hs
);
2506 if (botlist
->used
== 0) {
2507 dict_remove(helpserv_bots_bychan_dict
, hs
->helpchan
->name
);
2510 hs
->helpchan
= NULL
;
2511 if (!helpserv_in_channel(hs
, old_helpchan
)) {
2512 snprintf(reason
, MAXLEN
, "Moved to %s by %s.", newchan
, user
->nick
);
2513 DelChannelUser(hs
->helpserv
, old_helpchan
, reason
, 0);
2516 if (!(hs
->helpchan
= GetChannel(newchan
))) {
2517 hs
->helpchan
= AddChannel(newchan
, now
, NULL
, NULL
, NULL
);
2518 AddChannelUser(hs
->helpserv
, hs
->helpchan
)->modes
|= MODE_CHANOP
;
2519 } else if (!helpserv_in_channel(hs
, old_helpchan
)) {
2520 struct mod_chanmode change
;
2521 mod_chanmode_init(&change
);
2523 change
.args
[0].mode
= MODE_CHANOP
;
2524 change
.args
[0].u
.member
= AddChannelUser(hs
->helpserv
, hs
->helpchan
);
2525 mod_chanmode_announce(hs
->helpserv
, hs
->helpchan
, &change
);
2528 if (!(botlist
= dict_find(helpserv_bots_bychan_dict
, hs
->helpchan
->name
, NULL
))) {
2529 botlist
= helpserv_botlist_alloc();
2530 dict_insert(helpserv_bots_bychan_dict
, hs
->helpchan
->name
, botlist
);
2532 helpserv_botlist_append(botlist
, hs
);
2534 global_message_args(MESSAGE_RECIPIENT_OPERS
, "HSMSG_BOT_MOVED", hs
->helpserv
->nick
,
2535 oldchan
, newchan
, user
->nick
);
2539 helpserv_notice(user
, "HSMSG_INVALID_MOVE", argv
[1]);
2544 static HELPSERV_FUNC(cmd_bots
) {
2546 struct helpfile_table tbl
;
2549 helpserv_notice(user
, "HSMSG_BOTLIST_HEADER");
2551 tbl
.length
= dict_size(helpserv_bots_dict
)+1;
2553 tbl
.flags
= TABLE_NO_FREE
;
2554 tbl
.contents
= alloca(tbl
.length
* sizeof(*tbl
.contents
));
2555 tbl
.contents
[0] = alloca(tbl
.width
* sizeof(**tbl
.contents
));
2556 tbl
.contents
[0][0] = "Bot";
2557 tbl
.contents
[0][1] = "Channel";
2558 tbl
.contents
[0][2] = "Owner";
2559 tbl
.contents
[0][3] = "Inactivity";
2561 for (it
=dict_first(helpserv_bots_dict
), i
=1; it
; it
=iter_next(it
), i
++) {
2562 dict_iterator_t it2
;
2563 struct helpserv_bot
*bot
;
2564 struct helpserv_user
*owner
=NULL
;
2566 bot
= iter_data(it
);
2568 for (it2
=dict_first(bot
->users
); it2
; it2
=iter_next(it2
)) {
2569 if (((struct helpserv_user
*)iter_data(it2
))->level
== HlOwner
) {
2570 owner
= iter_data(it2
);
2575 tbl
.contents
[i
] = alloca(tbl
.width
* sizeof(**tbl
.contents
));
2576 tbl
.contents
[i
][0] = iter_key(it
);
2577 tbl
.contents
[i
][1] = bot
->helpchan
->name
;
2578 tbl
.contents
[i
][2] = owner
? owner
->handle
->handle
: "None";
2579 tbl
.contents
[i
][3] = alloca(INTERVALLEN
);
2580 intervalString((char*)tbl
.contents
[i
][3], now
- bot
->last_active
, user
->handle_info
);
2583 table_send((from_opserv
? opserv
: hs
->helpserv
), user
->nick
, 0, NULL
, tbl
);
2588 static void helpserv_page_helper_gone(struct helpserv_bot
*hs
, struct helpserv_request
*req
, const char *reason
) {
2589 const int from_opserv
= 0;
2594 /* Let the user know that their request is now unhandled */
2596 struct modeNode
*mn
= GetUserMode(hs
->helpchan
, req
->user
);
2597 helpserv_msguser(req
->user
, "HSMSG_REQ_UNASSIGNED", req
->id
, reason
);
2598 if (hs
->auto_devoice
&& mn
&& (mn
->modes
& MODE_VOICE
)) {
2599 struct mod_chanmode change
;
2600 mod_chanmode_init(&change
);
2602 change
.args
[0].mode
= MODE_REMOVE
| MODE_VOICE
;
2603 change
.args
[0].u
.member
= mn
;
2604 mod_chanmode_announce(hs
->helpserv
, hs
->helpchan
, &change
);
2607 helpserv_page(PGSRC_STATUS
, "HSMSG_PAGE_HELPER_GONE_1", req
->id
, req
->user
->nick
, req
->handle
->handle
, req
->helper
->handle
->handle
, reason
);
2609 helpserv_page(PGSRC_STATUS
, "HSMSG_PAGE_HELPER_GONE_2", req
->id
, req
->user
->nick
, req
->helper
->handle
->handle
, reason
);
2612 helpserv_page(PGSRC_STATUS
, "HSMSG_PAGE_HELPER_GONE_3", req
->id
, req
->handle
->handle
, req
->helper
->handle
->handle
, reason
);
2614 helpserv_page(PGSRC_STATUS
, "HSMSG_PAGE_HELPER_GONE_2", req
->id
, req
->helper
->handle
->handle
, reason
);
2617 /* Now put it back in the queue */
2618 if (hs
->unhandled
== NULL
) {
2619 /* Nothing there, put it at the front */
2620 hs
->unhandled
= req
;
2621 req
->next_unhandled
= NULL
;
2623 /* Should it be at the front? */
2624 if (hs
->unhandled
->opened
>= req
->opened
) {
2625 req
->next_unhandled
= hs
->unhandled
;
2626 hs
->unhandled
= req
;
2628 struct helpserv_request
*unhandled
;
2629 /* Find the request that this should be inserted AFTER */
2630 for (unhandled
=hs
->unhandled
; unhandled
->next_unhandled
&& (unhandled
->next_unhandled
->opened
< req
->opened
); unhandled
= unhandled
->next_unhandled
);
2631 req
->next_unhandled
= unhandled
->next_unhandled
;
2632 unhandled
->next_unhandled
= req
;
2639 /* This takes care of WHINE_DELAY and IDLE_DELAY */
2640 static void run_whine_interval(void *data
) {
2641 struct helpserv_bot
*hs
=data
;
2642 struct helpfile_table tbl
;
2645 /* First, run the WHINE_DELAY */
2646 if (hs
->intervals
[INTERVAL_WHINE_DELAY
]
2647 && (hs
->page_types
[PGSRC_ALERT
] != PAGE_NONE
)
2648 && (hs
->page_targets
[PGSRC_ALERT
] != NULL
)
2649 && (!hs
->intervals
[INTERVAL_EMPTY_INTERVAL
] || !hs
->helpchan_empty
)) {
2650 struct helpserv_request
*unh
;
2651 struct helpserv_reqlist reqlist
;
2652 unsigned int queuesize
=0;
2654 helpserv_reqlist_init(&reqlist
);
2656 for (unh
= hs
->unhandled
; unh
; unh
= unh
->next_unhandled
) {
2658 if ((now
- unh
->opened
) >= (time_t)hs
->intervals
[INTERVAL_WHINE_DELAY
]) {
2659 helpserv_reqlist_append(&reqlist
, unh
);
2664 char strwhinedelay
[INTERVALLEN
];
2666 intervalString(strwhinedelay
, (time_t)hs
->intervals
[INTERVAL_WHINE_DELAY
], NULL
);
2667 #if ANNOYING_ALERT_PAGES
2668 tbl
.length
= reqlist
.used
+ 1;
2670 tbl
.flags
= TABLE_NO_FREE
;
2671 tbl
.contents
= alloca(tbl
.length
* sizeof(*tbl
.contents
));
2672 tbl
.contents
[0] = alloca(tbl
.width
* sizeof(**tbl
.contents
));
2673 tbl
.contents
[0][0] = "ID#";
2674 tbl
.contents
[0][1] = "Nick";
2675 tbl
.contents
[0][2] = "Account";
2676 tbl
.contents
[0][3] = "Waiting time";
2678 for (i
=1; i
<= reqlist
.used
; i
++) {
2679 char reqid
[12], unh_time
[INTERVALLEN
];
2680 unh
= reqlist
.list
[i
-1];
2682 tbl
.contents
[i
] = alloca(tbl
.width
* sizeof(**tbl
.contents
));
2683 sprintf(reqid
, "%lu", unh
->id
);
2684 tbl
.contents
[i
][0] = strdup(reqid
);
2685 tbl
.contents
[i
][1] = unh
->user
? unh
->user
->nick
: "Not online";
2686 tbl
.contents
[i
][2] = unh
->handle
? unh
->handle
->handle
: "Not authed";
2687 intervalString(unh_time
, now
- unh
->opened
, NULL
);
2688 tbl
.contents
[i
][3] = strdup(unh_time
);
2691 helpserv_page(PGSRC_ALERT
, "HSMSG_PAGE_WHINE_HEADER", reqlist
.used
, strwhinedelay
, queuesize
, dict_size(hs
->requests
));
2692 table_send(hs
->helpserv
, hs
->page_targets
[PGSRC_ALERT
]->name
, 0, page_type_funcs
[hs
->page_types
[PGSRC_ALERT
]], tbl
);
2694 for (i
=1; i
<= reqlist
.used
; i
++) {
2695 free((char *)tbl
.contents
[i
][0]);
2696 free((char *)tbl
.contents
[i
][3]);
2699 helpserv_page(PGSRC_ALERT
, "HSMSG_PAGE_WHINE_HEADER", reqlist
.used
, strwhinedelay
, queuesize
, dict_size(hs
->requests
));
2703 helpserv_reqlist_clean(&reqlist
);
2706 /* Next run IDLE_DELAY */
2707 if (hs
->intervals
[INTERVAL_IDLE_DELAY
]
2708 && (hs
->page_types
[PGSRC_STATUS
] != PAGE_NONE
)
2709 && (hs
->page_targets
[PGSRC_STATUS
] != NULL
)) {
2710 struct modeList mode_list
;
2712 modeList_init(&mode_list
);
2714 for (i
=0; i
< hs
->helpchan
->members
.used
; i
++) {
2715 struct modeNode
*mn
= hs
->helpchan
->members
.list
[i
];
2716 /* Ignore ops. Perhaps this should be a set option? */
2717 if (mn
->modes
& MODE_CHANOP
)
2719 /* Check if they've been idle long enough */
2720 if ((unsigned)(now
- mn
->idle_since
) < hs
->intervals
[INTERVAL_IDLE_DELAY
])
2722 /* Add them to the list of idle people.. */
2723 modeList_append(&mode_list
, mn
);
2726 if (mode_list
.used
) {
2727 char stridledelay
[INTERVALLEN
];
2729 tbl
.length
= mode_list
.used
+ 1;
2731 tbl
.flags
= TABLE_NO_FREE
;
2732 tbl
.contents
= alloca(tbl
.length
* sizeof(*tbl
.contents
));
2733 tbl
.contents
[0] = alloca(tbl
.width
* sizeof(**tbl
.contents
));
2734 tbl
.contents
[0][0] = "Nick";
2735 tbl
.contents
[0][1] = "Account";
2736 tbl
.contents
[0][2] = "ID#";
2737 tbl
.contents
[0][3] = "Idle time";
2739 for (i
=1; i
<= mode_list
.used
; i
++) {
2740 char reqid
[12], idle_time
[INTERVALLEN
];
2741 struct helpserv_reqlist
*reqlist
;
2742 struct modeNode
*mn
= mode_list
.list
[i
-1];
2744 tbl
.contents
[i
] = alloca(tbl
.width
* sizeof(**tbl
.contents
));
2745 tbl
.contents
[i
][0] = mn
->user
->nick
;
2746 tbl
.contents
[i
][1] = mn
->user
->handle_info
? mn
->user
->handle_info
->handle
: "Not authed";
2748 if ((reqlist
= dict_find(helpserv_reqs_bynick_dict
, mn
->user
->nick
, NULL
))) {
2751 for (j
= reqlist
->used
-1; j
>= 0; j
--) {
2752 struct helpserv_request
*req
= reqlist
->list
[j
];
2754 if (req
->hs
== hs
) {
2755 sprintf(reqid
, "%lu", req
->id
);
2761 strcpy(reqid
, "None");
2763 strcpy(reqid
, "None");
2765 tbl
.contents
[i
][2] = strdup(reqid
);
2767 intervalString(idle_time
, now
- mn
->idle_since
, NULL
);
2768 tbl
.contents
[i
][3] = strdup(idle_time
);
2771 intervalString(stridledelay
, (time_t)hs
->intervals
[INTERVAL_IDLE_DELAY
], NULL
);
2772 helpserv_page(PGSRC_STATUS
, "HSMSG_PAGE_IDLE_HEADER", mode_list
.used
, hs
->helpchan
->name
, stridledelay
);
2773 table_send(hs
->helpserv
, hs
->page_targets
[PGSRC_STATUS
]->name
, 0, page_types
[hs
->page_types
[PGSRC_STATUS
]].func
, tbl
);
2775 for (i
=1; i
<= mode_list
.used
; i
++) {
2776 free((char *)tbl
.contents
[i
][2]);
2777 free((char *)tbl
.contents
[i
][3]);
2781 modeList_clean(&mode_list
);
2784 if (hs
->intervals
[INTERVAL_WHINE_INTERVAL
]) {
2785 timeq_add(now
+ hs
->intervals
[INTERVAL_WHINE_INTERVAL
], run_whine_interval
, hs
);
2789 /* Returns -1 if there's any helpers,
2790 * 0 if there are no helpers
2791 * >1 if there are trials (number of trials)
2793 static int find_helpchan_helpers(struct helpserv_bot
*hs
) {
2797 for (it
=dict_first(hs
->users
); it
; it
=iter_next(it
)) {
2798 struct helpserv_user
*hs_user
=iter_data(it
);
2800 if (find_handle_in_channel(hs
->helpchan
, hs_user
->handle
, NULL
)) {
2801 if (hs_user
->level
>= HlHelper
) {
2802 hs
->helpchan_empty
= 0;
2809 hs
->helpchan_empty
= 1;
2814 static void run_empty_interval(void *data
) {
2815 struct helpserv_bot
*hs
=data
;
2816 int num_trials
=find_helpchan_helpers(hs
);
2817 unsigned int num_unh
;
2818 struct helpserv_request
*unh
;
2820 if (num_trials
== -1)
2822 if (hs
->req_on_join
&& !hs
->unhandled
)
2825 for (num_unh
=0, unh
=hs
->unhandled
; unh
; num_unh
++)
2826 unh
= unh
->next_unhandled
;
2829 helpserv_page(PGSRC_ALERT
, "HSMSG_PAGE_ONLYTRIALALERT", hs
->helpchan
->name
, num_trials
, num_unh
);
2831 helpserv_page(PGSRC_ALERT
, "HSMSG_PAGE_EMPTYALERT", hs
->helpchan
->name
, num_unh
);
2833 if (hs
->intervals
[INTERVAL_EMPTY_INTERVAL
])
2834 timeq_add(now
+ hs
->intervals
[INTERVAL_EMPTY_INTERVAL
], run_empty_interval
, hs
);
2837 static void free_user(void *data
) {
2838 struct helpserv_user
*hs_user
= data
;
2839 struct helpserv_bot
*hs
= hs_user
->hs
;
2840 struct helpserv_userlist
*userlist
;
2844 for (it
=dict_first(hs
->requests
); it
; it
=iter_next(it
)) {
2845 struct helpserv_request
*req
= iter_data(it
);
2847 if (req
->helper
== hs_user
)
2848 helpserv_page_helper_gone(hs
, req
, "been deleted");
2852 userlist
= dict_find(helpserv_users_byhand_dict
, hs_user
->handle
->handle
, NULL
);
2853 if (userlist
->used
== 1) {
2854 dict_remove(helpserv_users_byhand_dict
, hs_user
->handle
->handle
);
2856 helpserv_userlist_remove(userlist
, hs_user
);
2862 static struct helpserv_bot
*register_helpserv(const char *nick
, const char *help_channel
, const char *registrar
) {
2863 struct helpserv_bot
*hs
;
2864 struct helpserv_botlist
*botlist
;
2866 /* Laziness dictates calloc, since there's a lot to set to NULL or 0, and
2867 * it's a harmless default */
2868 hs
= calloc(1, sizeof(struct helpserv_bot
));
2870 if (!(hs
->helpserv
= AddLocalUser(nick
, nick
, NULL
, helpserv_conf
.description
, NULL
))) {
2875 reg_privmsg_func(hs
->helpserv
, helpserv_botmsg
);
2877 if (!(hs
->helpchan
= GetChannel(help_channel
))) {
2878 hs
->helpchan
= AddChannel(help_channel
, now
, NULL
, NULL
, NULL
);
2879 AddChannelUser(hs
->helpserv
, hs
->helpchan
)->modes
|= MODE_CHANOP
;
2880 } else if (!hs
->suspended
) {
2881 struct mod_chanmode change
;
2882 mod_chanmode_init(&change
);
2884 change
.args
[0].mode
= MODE_CHANOP
;
2885 change
.args
[0].u
.member
= AddChannelUser(hs
->helpserv
, hs
->helpchan
);
2886 mod_chanmode_announce(hs
->helpserv
, hs
->helpchan
, &change
);
2890 hs
->registrar
= strdup(registrar
);
2892 hs
->users
= dict_new();
2893 /* Don't free keys - they use the handle_info's handle field */
2894 dict_set_free_data(hs
->users
, free_user
);
2895 hs
->requests
= dict_new();
2896 dict_set_free_keys(hs
->requests
, free
);
2897 dict_set_free_data(hs
->requests
, free_request
);
2899 dict_insert(helpserv_bots_dict
, hs
->helpserv
->nick
, hs
);
2901 if (!(botlist
= dict_find(helpserv_bots_bychan_dict
, hs
->helpchan
->name
, NULL
))) {
2902 botlist
= helpserv_botlist_alloc();
2903 dict_insert(helpserv_bots_bychan_dict
, hs
->helpchan
->name
, botlist
);
2905 helpserv_botlist_append(botlist
, hs
);
2910 static HELPSERV_FUNC(cmd_register
) {
2911 char *nick
, *helpchan
;
2912 struct handle_info
*handle
;
2916 if (!is_valid_nick(nick
)) {
2917 helpserv_notice(user
, "HSMSG_ILLEGAL_NICK", nick
);
2920 if (GetUserH(nick
)) {
2921 helpserv_notice(user
, "HSMSG_NICK_EXISTS", nick
);
2925 if (!IsChannelName(helpchan
)) {
2926 helpserv_notice(user
, "HSMSG_ILLEGAL_CHANNEL", helpchan
);
2930 if (opserv_bad_channel(helpchan
)) {
2931 helpserv_notice(user
, "HSMSG_ILLEGAL_CHANNEL", helpchan
);
2934 if (!(handle
= helpserv_get_handle_info(user
, argv
[3])))
2937 if (!(hs
= register_helpserv(nick
, helpchan
, user
->handle_info
->handle
))) {
2938 helpserv_notice(user
, "HSMSG_ERROR_ADDING_SERVICE", nick
);
2942 hs
->registered
= now
;
2943 helpserv_add_user(hs
, handle
, HlOwner
);
2945 helpserv_notice(user
, "HSMSG_REG_SUCCESS", handle
->handle
, nick
);
2947 /* Not sent to helpers, since they can't register HelpServ */
2948 global_message_args(MESSAGE_RECIPIENT_OPERS
, "HSMSG_BOT_REGISTERED", nick
,
2949 hs
->helpchan
->name
, handle
->handle
, user
->nick
);
2953 static void unregister_helpserv(struct helpserv_bot
*hs
) {
2954 enum message_type msgtype
;
2956 timeq_del(0, NULL
, hs
, TIMEQ_IGNORE_WHEN
|TIMEQ_IGNORE_FUNC
);
2958 /* Requests before users so that it doesn't spam mentioning now-unhandled
2959 * requests because the users were deleted */
2960 dict_delete(hs
->requests
);
2961 hs
->requests
= NULL
; /* so we don't try to look up requests in free_user() */
2962 dict_delete(hs
->users
);
2963 free(hs
->registrar
);
2965 for (msgtype
=0; msgtype
<MSGTYPE_COUNT
; msgtype
++)
2966 free(hs
->messages
[msgtype
]);
2969 static void helpserv_free_bot(void *data
) {
2970 unregister_helpserv(data
);
2974 static void helpserv_expire_suspension(void *data
) {
2975 struct helpserv_bot
*hs
= data
;
2976 struct chanNode
*channel
;
2977 struct mod_chanmode
*change
;
2979 channel
= hs
->helpchan
;
2984 hs
->suspender
= NULL
;
2986 change
= mod_chanmode_alloc(1);
2988 change
->args
[0].mode
= MODE_CHANOP
;
2989 change
->args
[0].u
.member
= AddChannelUser(hs
->helpserv
, channel
);
2991 mod_chanmode_announce(hs
->helpserv
, channel
, change
);
2992 mod_chanmode_free(change
);
2995 static void helpserv_unregister(struct helpserv_bot
*bot
, const char *quit_fmt
, const char *global_fmt
, const char *actor
) {
2996 char reason
[MAXLEN
], channame
[CHANNELLEN
], botname
[NICKLEN
];
2997 struct helpserv_botlist
*botlist
;
3000 if (bot
->suspended
&& bot
->expiry
)
3001 timeq_del(bot
->expiry
, helpserv_expire_suspension
, bot
, 0);
3003 botlist
= dict_find(helpserv_bots_bychan_dict
, bot
->helpchan
->name
, NULL
);
3004 helpserv_botlist_remove(botlist
, bot
);
3006 dict_remove(helpserv_bots_bychan_dict
, bot
->helpchan
->name
);
3007 len
= strlen(bot
->helpserv
->nick
) + 1;
3008 safestrncpy(botname
, bot
->helpserv
->nick
, len
);
3009 len
= strlen(bot
->helpchan
->name
) + 1;
3010 safestrncpy(channame
, bot
->helpchan
->name
, len
);
3011 snprintf(reason
, sizeof(reason
), quit_fmt
, actor
);
3012 DelUser(bot
->helpserv
, NULL
, 1, reason
);
3013 dict_remove(helpserv_bots_dict
, botname
);
3014 global_message_args(MESSAGE_RECIPIENT_OPERS
, global_fmt
, botname
, channame
, actor
);
3017 static HELPSERV_FUNC(cmd_unregister
) {
3019 if (argc
< 2 || strcmp(argv
[1], "CONFIRM")) {
3020 helpserv_notice(user
, "HSMSG_NEED_UNREG_CONFIRM");
3023 log_audit(HS_LOG
, LOG_COMMAND
, user
, hs
->helpserv
, hs
->helpchan
->name
, 0, "unregister CONFIRM");
3026 helpserv_unregister(hs
, "Unregistered by %s", "HSMSG_BOT_UNREGISTERED", user
->nick
);
3030 static HELPSERV_FUNC(cmd_suspend
) {
3031 char reason
[MAXLEN
];
3032 struct helpserv_bot
*hsb
;
3033 time_t expiry
, duration
;
3037 if(!strcmp(argv
[2], "0"))
3039 else if((duration
= ParseInterval(argv
[2])))
3040 expiry
= now
+ duration
;
3043 helpserv_notice(user
, "MSG_INVALID_DURATION", argv
[1]);
3047 hsb
= dict_find(helpserv_bots_dict
, argv
[1], NULL
);
3049 helpserv_notice(user
, "HSMSG_BOT_NON_EXIST", argv
[1]);
3053 unsplit_string(argv
+ 3, argc
- 3, reason
);
3057 hsb
->expiry
= expiry
;
3058 hsb
->reason
= strdup(reason
);
3059 hsb
->suspender
= strdup(user
->handle_info
->handle
);
3062 timeq_add(hsb
->expiry
, helpserv_expire_suspension
, hsb
);
3064 DelChannelUser(hsb
->helpserv
, hsb
->helpchan
, hsb
->reason
, 0);
3065 helpserv_notice(user
, "HSMSG_SUSPENDED", hsb
->helpchan
->name
);
3066 global_message_args(MESSAGE_RECIPIENT_OPERS
| MESSAGE_RECIPIENT_HELPERS
, "HSMSG_SUSPENDED_BY",
3067 hsb
->helpchan
->name
, hsb
->suspender
);
3072 static HELPSERV_FUNC(cmd_unsuspend
) {
3073 struct helpserv_bot
*hsb
;
3075 hsb
= dict_find(helpserv_bots_dict
, argv
[1], NULL
);
3077 helpserv_notice(user
, "HSMSG_BOT_NON_EXIST", argv
[1]);
3083 helpserv_notice(user
, "HSMSG_NOT_SUSPENDED", hsb
->helpchan
->name
);
3087 /* Expire the suspension and join ChanServ to the channel. */
3088 timeq_del(hsb
->expiry
, helpserv_expire_suspension
, hsb
, 0);
3089 helpserv_expire_suspension(hsb
);
3090 helpserv_notice(user
, "HSMSG_UNSUSPENDED", hsb
->helpchan
->name
);
3091 global_message_args(MESSAGE_RECIPIENT_OPERS
|MESSAGE_RECIPIENT_HELPERS
, "HSMSG_UNSUSPENDED_BY",
3092 hsb
->helpchan
->name
, user
->handle_info
->handle
);
3096 static HELPSERV_FUNC(cmd_expire
) {
3097 struct helpserv_botlist victims
;
3098 struct helpserv_bot
*bot
;
3099 dict_iterator_t it
, next
;
3100 unsigned int count
= 0;
3102 memset(&victims
, 0, sizeof(victims
));
3103 for (it
= dict_first(helpserv_bots_dict
); it
; it
= next
) {
3104 bot
= iter_data(it
);
3105 next
= iter_next(it
);
3106 if ((unsigned int)(now
- bot
->last_active
) < helpserv_conf
.expire_age
)
3108 helpserv_unregister(bot
, "Registration expired due to inactivity", "HSMSG_BOT_EXPIRED", user
->nick
);
3111 helpserv_notice(user
, "HSMSG_EXPIRATION_DONE", count
);
3115 static HELPSERV_FUNC(cmd_giveownership
) {
3116 struct handle_info
*hi
;
3117 struct helpserv_user
*new_owner
, *old_owner
, *hs_user
;
3119 char reason
[MAXLEN
];
3121 if (!from_opserv
&& ((argc
< 3) || strcmp(argv
[2], "CONFIRM"))) {
3122 helpserv_notice(user
, "HSMSG_NEED_GIVEOWNERSHIP_CONFIRM");
3125 hi
= helpserv_get_handle_info(user
, argv
[1]);
3128 new_owner
= GetHSUser(hs
, hi
);
3130 helpserv_notice(user
, "HSMSG_NOT_IN_USERLIST", hi
->handle
, hs
->helpserv
->nick
);
3134 old_owner
= GetHSUser(hs
, user
->handle_info
);
3135 else for (it
= dict_first(hs
->users
), old_owner
= NULL
; it
; it
= iter_next(it
)) {
3136 hs_user
= iter_data(it
);
3137 if (hs_user
->level
!= HlOwner
)
3140 helpserv_notice(user
, "HSMSG_MULTIPLE_OWNERS", hs
->helpserv
->nick
);
3143 old_owner
= hs_user
;
3145 if (!from_opserv
&& (new_owner
->handle
== user
->handle_info
)) {
3146 helpserv_notice(user
, "HSMSG_NO_TRANSFER_SELF");
3150 old_owner
->level
= HlManager
;
3151 new_owner
->level
= HlOwner
;
3152 helpserv_notice(user
, "HSMSG_OWNERSHIP_GIVEN", hs
->helpserv
->nick
, new_owner
->handle
->handle
);
3153 sprintf(reason
, "%s (%s) ownership transferred to %s by %s.", hs
->helpserv
->nick
, hs
->helpchan
->name
, new_owner
->handle
->handle
, user
->handle_info
->handle
);
3157 static HELPSERV_FUNC(cmd_weekstart
) {
3158 struct handle_info
*hi
;
3159 struct helpserv_user
*actor
, *victim
;
3163 actor
= from_opserv
? NULL
: GetHSUser(hs
, user
->handle_info
);
3164 if (!(hi
= helpserv_get_handle_info(user
, argv
[1])))
3166 if (!(victim
= GetHSUser(hs
, hi
))) {
3167 helpserv_notice(user
, "HSMSG_NOT_IN_USERLIST", hi
->handle
, hs
->helpserv
->nick
);
3170 if (actor
&& (actor
->level
<= victim
->level
) && (actor
!= victim
)) {
3171 helpserv_notice(user
, "MSG_USER_OUTRANKED", victim
->handle
->handle
);
3174 if (argc
> 2 && (!actor
|| actor
->level
>= HlManager
)) {
3176 switch (argv
[2][0]) {
3178 if ((argv
[2][1] == 'u') || (argv
[2][1] == 'U'))
3180 else if ((argv
[2][1] == 'a') || (argv
[2][1] == 'A'))
3183 case 'm': case 'M': new_day
= 1; break;
3185 if ((argv
[2][1] == 'u') || (argv
[2][1] == 'U'))
3187 else if ((argv
[2][1] == 'h') || (argv
[2][1] == 'H'))
3190 case 'w': case 'W': new_day
= 3; break;
3191 case 'f': case 'F': new_day
= 5; break;
3194 helpserv_notice(user
, "HSMSG_BAD_WEEKDAY", argv
[2]);
3197 victim
->week_start
= new_day
;
3200 helpserv_notice(user
, "HSMSG_WEEK_STARTS", victim
->handle
->handle
, weekday_names
[victim
->week_start
]);
3204 static void set_page_target(struct helpserv_bot
*hs
, enum page_source idx
, const char *target
) {
3205 struct chanNode
*new_target
, *old_target
;
3208 if (!IsChannelName(target
)) {
3209 log_module(HS_LOG
, LOG_ERROR
, "%s has an invalid page target.", hs
->helpserv
->nick
);
3212 new_target
= GetChannel(target
);
3214 new_target
= AddChannel(target
, now
, NULL
, NULL
, NULL
);
3215 AddChannelUser(hs
->helpserv
, new_target
);
3220 if (new_target
== hs
->page_targets
[idx
])
3222 old_target
= hs
->page_targets
[idx
];
3223 hs
->page_targets
[idx
] = NULL
;
3224 if (old_target
&& !helpserv_in_channel(hs
, old_target
))
3225 DelChannelUser(hs
->helpserv
, old_target
, "Changing page target.", 0);
3226 if (new_target
&& !helpserv_in_channel(hs
, new_target
)) {
3227 struct mod_chanmode change
;
3228 mod_chanmode_init(&change
);
3230 change
.args
[0].mode
= MODE_CHANOP
;
3231 change
.args
[0].u
.member
= AddChannelUser(hs
->helpserv
, new_target
);
3232 mod_chanmode_announce(hs
->helpserv
, new_target
, &change
);
3234 hs
->page_targets
[idx
] = new_target
;
3237 static int opt_page_target(struct userNode
*user
, struct helpserv_bot
*hs
, int from_opserv
, int argc
, char *argv
[], enum page_source idx
) {
3241 if (!IsOper(user
)) {
3242 helpserv_notice(user
, "HSMSG_SET_NEED_OPER");
3245 if (!strcmp(argv
[0], "*")) {
3246 set_page_target(hs
, idx
, NULL
);
3248 } else if (!IsChannelName(argv
[0])) {
3249 helpserv_notice(user
, "MSG_NOT_CHANNEL_NAME");
3252 set_page_target(hs
, idx
, argv
[0]);
3256 if (hs
->page_targets
[idx
])
3257 helpserv_notice(user
, page_sources
[idx
].print_target
, hs
->page_targets
[idx
]->name
);
3259 helpserv_notice(user
, page_sources
[idx
].print_target
, user_find_message(user
, "MSG_NONE"));
3263 static HELPSERV_OPTION(opt_pagetarget_command
) {
3264 return opt_page_target(user
, hs
, from_opserv
, argc
, argv
, PGSRC_COMMAND
);
3267 static HELPSERV_OPTION(opt_pagetarget_alert
) {
3268 return opt_page_target(user
, hs
, from_opserv
, argc
, argv
, PGSRC_ALERT
);
3271 static HELPSERV_OPTION(opt_pagetarget_status
) {
3272 return opt_page_target(user
, hs
, from_opserv
, argc
, argv
, PGSRC_STATUS
);
3275 static enum page_type
page_type_from_name(const char *name
) {
3276 enum page_type type
;
3277 for (type
=0; type
<PAGE_COUNT
; type
++)
3278 if (!irccasecmp(page_types
[type
].db_name
, name
))
3283 static int opt_page_type(struct userNode
*user
, struct helpserv_bot
*hs
, int from_opserv
, int argc
, char *argv
[], enum page_source idx
) {
3284 enum page_type new_type
;
3288 new_type
= page_type_from_name(argv
[0]);
3289 if (new_type
== PAGE_COUNT
) {
3290 helpserv_notice(user
, "HSMSG_INVALID_OPTION", argv
[0]);
3293 hs
->page_types
[idx
] = new_type
;
3296 helpserv_notice(user
, page_sources
[idx
].print_type
,
3297 user_find_message(user
, page_types
[hs
->page_types
[idx
]].print_name
));
3301 static HELPSERV_OPTION(opt_pagetype
) {
3302 return opt_page_type(user
, hs
, from_opserv
, argc
, argv
, PGSRC_COMMAND
);
3305 static HELPSERV_OPTION(opt_alert_page_type
) {
3306 return opt_page_type(user
, hs
, from_opserv
, argc
, argv
, PGSRC_ALERT
);
3309 static HELPSERV_OPTION(opt_status_page_type
) {
3310 return opt_page_type(user
, hs
, from_opserv
, argc
, argv
, PGSRC_STATUS
);
3313 static int opt_message(struct userNode
*user
, struct helpserv_bot
*hs
, int from_opserv
, int argc
, char *argv
[], enum message_type idx
) {
3317 char *msg
= unsplit_string(argv
, argc
, NULL
);
3318 free(hs
->messages
[idx
]);
3319 hs
->messages
[idx
] = strcmp(msg
, "*") ? strdup(msg
) : NULL
;
3322 if (hs
->messages
[idx
])
3323 helpserv_notice(user
, message_types
[idx
].print_name
, hs
->messages
[idx
]);
3325 helpserv_notice(user
, message_types
[idx
].print_name
, user_find_message(user
, "MSG_NONE"));
3329 static HELPSERV_OPTION(opt_greeting
) {
3330 return opt_message(user
, hs
, from_opserv
, argc
, argv
, MSGTYPE_GREETING
);
3333 static HELPSERV_OPTION(opt_req_opened
) {
3334 return opt_message(user
, hs
, from_opserv
, argc
, argv
, MSGTYPE_REQ_OPENED
);
3337 static HELPSERV_OPTION(opt_req_assigned
) {
3338 return opt_message(user
, hs
, from_opserv
, argc
, argv
, MSGTYPE_REQ_ASSIGNED
);
3341 static HELPSERV_OPTION(opt_req_closed
) {
3342 return opt_message(user
, hs
, from_opserv
, argc
, argv
, MSGTYPE_REQ_CLOSED
);
3345 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
) {
3346 char buf
[INTERVALLEN
];
3350 unsigned long new_int
= ParseInterval(argv
[0]);
3351 if (!new_int
&& strcmp(argv
[0], "0")) {
3352 helpserv_notice(user
, "MSG_INVALID_DURATION", argv
[0]);
3355 if (new_int
&& new_int
< min
) {
3356 intervalString(buf
, min
, user
->handle_info
);
3357 helpserv_notice(user
, "HSMSG_INVALID_INTERVAL", user_find_message(user
, interval_types
[idx
].print_name
), buf
);
3360 hs
->intervals
[idx
] = new_int
;
3363 if (hs
->intervals
[idx
]) {
3364 intervalString(buf
, hs
->intervals
[idx
], user
->handle_info
);
3365 helpserv_notice(user
, interval_types
[idx
].print_name
, buf
);
3367 helpserv_notice(user
, interval_types
[idx
].print_name
, user_find_message(user
, "HSMSG_0_DISABLED"));
3371 static HELPSERV_OPTION(opt_idle_delay
) {
3372 return opt_interval(user
, hs
, from_opserv
, argc
, argv
, INTERVAL_IDLE_DELAY
, 60);
3375 static HELPSERV_OPTION(opt_whine_delay
) {
3376 return opt_interval(user
, hs
, from_opserv
, argc
, argv
, INTERVAL_WHINE_DELAY
, 60);
3379 static HELPSERV_OPTION(opt_whine_interval
) {
3380 unsigned int old_val
= hs
->intervals
[INTERVAL_WHINE_INTERVAL
];
3383 retval
= opt_interval(user
, hs
, from_opserv
, argc
, argv
, INTERVAL_WHINE_INTERVAL
, 60);
3385 if (!old_val
&& hs
->intervals
[INTERVAL_WHINE_INTERVAL
]) {
3386 timeq_add(now
+ hs
->intervals
[INTERVAL_WHINE_INTERVAL
], run_whine_interval
, hs
);
3387 } else if (old_val
&& !hs
->intervals
[INTERVAL_WHINE_INTERVAL
]) {
3388 timeq_del(0, run_whine_interval
, hs
, TIMEQ_IGNORE_WHEN
);
3394 static HELPSERV_OPTION(opt_empty_interval
) {
3395 unsigned int old_val
= hs
->intervals
[INTERVAL_EMPTY_INTERVAL
];
3398 retval
= opt_interval(user
, hs
, from_opserv
, argc
, argv
, INTERVAL_EMPTY_INTERVAL
, 60);
3400 if (!old_val
&& hs
->intervals
[INTERVAL_EMPTY_INTERVAL
]) {
3401 timeq_add(now
+ hs
->intervals
[INTERVAL_EMPTY_INTERVAL
], run_empty_interval
, hs
);
3402 } else if (old_val
&& !hs
->intervals
[INTERVAL_EMPTY_INTERVAL
]) {
3403 timeq_del(0, run_empty_interval
, hs
, TIMEQ_IGNORE_WHEN
);
3409 static HELPSERV_OPTION(opt_stale_delay
) {
3410 return opt_interval(user
, hs
, from_opserv
, argc
, argv
, INTERVAL_STALE_DELAY
, 60);
3413 static enum persistence_length
persistence_from_name(const char *name
) {
3414 enum persistence_length pers
;
3415 for (pers
=0; pers
<PERSIST_COUNT
; pers
++)
3416 if (!irccasecmp(name
, persistence_lengths
[pers
].db_name
))
3418 return PERSIST_COUNT
;
3421 static int opt_persist(struct userNode
*user
, struct helpserv_bot
*hs
, int from_opserv
, int argc
, char *argv
[], enum persistence_type idx
) {
3425 enum persistence_length new_pers
= persistence_from_name(argv
[0]);
3426 if (new_pers
== PERSIST_COUNT
) {
3427 helpserv_notice(user
, "HSMSG_INVALID_OPTION", argv
[0]);
3430 hs
->persist_types
[idx
] = new_pers
;
3433 helpserv_notice(user
, persistence_types
[idx
].print_name
,
3434 user_find_message(user
, persistence_lengths
[hs
->persist_types
[idx
]].print_name
));
3438 static HELPSERV_OPTION(opt_request_persistence
) {
3439 return opt_persist(user
, hs
, from_opserv
, argc
, argv
, PERSIST_T_REQUEST
);
3442 static HELPSERV_OPTION(opt_helper_persistence
) {
3443 return opt_persist(user
, hs
, from_opserv
, argc
, argv
, PERSIST_T_HELPER
);
3446 static enum notification_type
notification_from_name(const char *name
) {
3447 enum notification_type notify
;
3448 for (notify
=0; notify
<NOTIFY_COUNT
; notify
++)
3449 if (!irccasecmp(name
, notification_types
[notify
].db_name
))
3451 return NOTIFY_COUNT
;
3454 static HELPSERV_OPTION(opt_notification
) {
3458 enum notification_type new_notify
= notification_from_name(argv
[0]);
3459 if (new_notify
== NOTIFY_COUNT
) {
3460 helpserv_notice(user
, "HSMSG_INVALID_OPTION", argv
[0]);
3463 if (!from_opserv
&& (new_notify
== NOTIFY_HANDLE
)) {
3464 helpserv_notice(user
, "HSMSG_SET_NEED_OPER");
3467 hs
->notify
= new_notify
;
3470 helpserv_notice(user
, "HSMSG_SET_NOTIFICATION", user_find_message(user
, notification_types
[hs
->notify
].print_name
));
3474 #define OPTION_UINT(var, name) do { \
3477 (var) = strtoul(argv[0], NULL, 0); \
3480 helpserv_notice(user, name, (var)); \
3484 static HELPSERV_OPTION(opt_id_wrap
) {
3485 OPTION_UINT(hs
->id_wrap
, "HSMSG_SET_IDWRAP");
3488 static HELPSERV_OPTION(opt_req_maxlen
) {
3489 OPTION_UINT(hs
->req_maxlen
, "HSMSG_SET_REQMAXLEN");
3492 #define OPTION_BINARY(var, name) do { \
3495 if (enabled_string(argv[0])) { \
3498 } else if (disabled_string(argv[0])) { \
3502 helpserv_notice(user, "MSG_INVALID_BINARY", argv[0]); \
3506 helpserv_notice(user, name, user_find_message(user, (var) ? "MSG_ON" : "MSG_OFF")); \
3510 static HELPSERV_OPTION(opt_privmsg_only
) {
3511 OPTION_BINARY(hs
->privmsg_only
, "HSMSG_SET_PRIVMSGONLY");
3514 static HELPSERV_OPTION(opt_req_on_join
) {
3515 OPTION_BINARY(hs
->req_on_join
, "HSMSG_SET_REQONJOIN");
3518 static HELPSERV_OPTION(opt_auto_voice
) {
3519 OPTION_BINARY(hs
->auto_voice
, "HSMSG_SET_AUTOVOICE");
3522 static HELPSERV_OPTION(opt_auto_devoice
) {
3523 OPTION_BINARY(hs
->auto_devoice
, "HSMSG_SET_AUTODEVOICE");
3526 static HELPSERV_OPTION(opt_join_total
) {
3527 OPTION_BINARY(hs
->join_total
, "HSMSG_SET_JOINTOTAL");
3530 static HELPSERV_OPTION(opt_alert_new
) {
3531 OPTION_BINARY(hs
->alert_new
, "HSMSG_SET_ALERTNEW");
3534 static HELPSERV_FUNC(cmd_set
) {
3535 helpserv_option_func_t
*opt
;
3539 helpserv_option_func_t
*display
[] = {
3540 opt_pagetarget_command
, opt_pagetarget_alert
, opt_pagetarget_status
,
3541 opt_pagetype
, opt_alert_page_type
, opt_status_page_type
,
3542 opt_greeting
, opt_req_opened
, opt_req_assigned
, opt_req_closed
,
3543 opt_idle_delay
, opt_whine_delay
, opt_whine_interval
,
3544 opt_empty_interval
, opt_stale_delay
, opt_request_persistence
,
3545 opt_helper_persistence
, opt_notification
, opt_id_wrap
,
3546 opt_req_maxlen
, opt_privmsg_only
, opt_req_on_join
, opt_auto_voice
,
3547 opt_auto_devoice
, opt_join_total
, opt_alert_new
3550 helpserv_notice(user
, "HSMSG_QUEUE_OPTIONS");
3551 for (i
=0; i
<ArrayLength(display
); i
++)
3552 display
[i
](user
, hs
, from_opserv
, 0, argv
);
3556 if (!(opt
= dict_find(helpserv_option_dict
, argv
[1], NULL
))) {
3557 helpserv_notice(user
, "HSMSG_INVALID_OPTION", argv
[1]);
3561 if ((argc
> 2) && !from_opserv
) {
3562 struct helpserv_user
*hs_user
;
3564 if (!(hs_user
= dict_find(hs
->users
, user
->handle_info
->handle
, NULL
))) {
3565 helpserv_notice(user
, "HSMSG_WTF_WHO_ARE_YOU", hs
->helpserv
->nick
);
3569 if (hs_user
->level
< HlManager
) {
3570 helpserv_notice(user
, "HSMSG_NEED_MANAGER");
3574 return opt(user
, hs
, from_opserv
, argc
-2, argv
+2);
3578 get_helpserv_id(const char *nick
, struct userNode
*user
) {
3579 char id
[MAX_LINE_SIZE
];
3581 struct helpserv_bot
*hs
;
3582 struct userNode
*target
;
3584 struct helpserv_request
*req
=NULL
, *newest
=NULL
;
3585 struct helpserv_reqlist
*reqlist
;
3588 if (!IsChannelName(nick
))
3589 target
= GetUserN(nick
);
3591 sprintf(id
, "%s", "$i");
3595 for (it
=dict_first(helpserv_bots_dict
); it
; it
=iter_next(it
)) {
3597 if (strcasecmp(user
->nick
, hs
->helpserv
->nick
))
3600 if ((reqlist
= dict_find(helpserv_reqs_bynick_dict
, target
->nick
, NULL
))) {
3601 for (i
=0; i
< reqlist
->used
; i
++) {
3602 req
= reqlist
->list
[i
];
3605 if (!newest
|| (newest
->opened
< req
->opened
))
3609 /* If nothing was found, this will set req to NULL */
3620 sprintf(id
, "ID#%lu", req
->id
);
3622 sprintf(id
, "%s", "$i");
3627 static int user_write_helper(const char *key
, void *data
, void *extra
) {
3628 struct helpserv_user
*hs_user
= data
;
3629 struct saxdb_context
*ctx
= extra
;
3630 struct string_list strlist
;
3631 char str
[5][16], *strs
[5];
3634 saxdb_start_record(ctx
, key
, 0);
3635 /* Helper identification. */
3636 saxdb_write_string(ctx
, KEY_HELPER_LEVEL
, helpserv_level2str(hs_user
->level
));
3637 saxdb_write_string(ctx
, KEY_HELPER_HELPMODE
, (hs_user
->help_mode
? "1" : "0"));
3638 saxdb_write_int(ctx
, KEY_HELPER_WEEKSTART
, hs_user
->week_start
);
3640 saxdb_start_record(ctx
, KEY_HELPER_STATS
, 0);
3641 for (i
=0; i
< ArrayLength(strs
); ++i
)
3643 strlist
.list
= strs
;
3645 /* Time in help channel */
3646 for (i
=0; i
< strlist
.used
; i
++) {
3647 unsigned int week_time
= hs_user
->time_per_week
[i
];
3648 if ((i
==0 || i
==4) && hs_user
->join_time
)
3649 week_time
+= now
- hs_user
->join_time
;
3650 sprintf(str
[i
], "%u", week_time
);
3652 saxdb_write_string_list(ctx
, KEY_HELPER_STATS_TIME
, &strlist
);
3653 /* Requests picked up */
3654 for (i
=0; i
< strlist
.used
; i
++)
3655 sprintf(str
[i
], "%u", hs_user
->picked_up
[i
]);
3656 saxdb_write_string_list(ctx
, KEY_HELPER_STATS_PICKUP
, &strlist
);
3657 /* Requests closed */
3658 for (i
=0; i
< strlist
.used
; i
++)
3659 sprintf(str
[i
], "%u", hs_user
->closed
[i
]);
3660 saxdb_write_string_list(ctx
, KEY_HELPER_STATS_CLOSE
, &strlist
);
3661 /* Requests reassigned from user */
3662 for (i
=0; i
< strlist
.used
; i
++)
3663 sprintf(str
[i
], "%u", hs_user
->reassigned_from
[i
]);
3664 saxdb_write_string_list(ctx
, KEY_HELPER_STATS_REASSIGNFROM
, &strlist
);
3665 /* Requests reassigned to user */
3666 for (i
=0; i
< strlist
.used
; i
++)
3667 sprintf(str
[i
], "%u", hs_user
->reassigned_to
[i
]);
3668 saxdb_write_string_list(ctx
, KEY_HELPER_STATS_REASSIGNTO
, &strlist
);
3669 /* End of stats and whole record. */
3670 saxdb_end_record(ctx
);
3671 saxdb_end_record(ctx
);
3675 static int user_read_helper(const char *key
, void *data
, void *extra
) {
3676 struct record_data
*rd
= data
;
3677 struct helpserv_bot
*hs
= extra
;
3678 struct helpserv_user
*hs_user
;
3679 struct handle_info
*handle
;
3681 enum helpserv_level level
;
3683 struct string_list
*strlist
;
3686 if (rd
->type
!= RECDB_OBJECT
|| !dict_size(rd
->d
.object
)) {
3687 log_module(HS_LOG
, LOG_ERROR
, "Invalid user %s for %s.", key
, hs
->helpserv
->nick
);
3691 if (!(handle
= get_handle_info(key
))) {
3692 log_module(HS_LOG
, LOG_ERROR
, "Nonexistant account %s for %s.", key
, hs
->helpserv
->nick
);
3695 str
= database_get_data(rd
->d
.object
, KEY_HELPER_LEVEL
, RECDB_QSTRING
);
3697 level
= helpserv_str2level(str
);
3698 if (level
== HlNone
) {
3699 log_module(HS_LOG
, LOG_ERROR
, "Account %s has invalid level %s.", key
, str
);
3703 log_module(HS_LOG
, LOG_ERROR
, "Account %s has no level field for %s.", key
, hs
->helpserv
->nick
);
3707 hs_user
= helpserv_add_user(hs
, handle
, level
);
3709 str
= database_get_data(rd
->d
.object
, KEY_HELPER_HELPMODE
, RECDB_QSTRING
);
3710 hs_user
->help_mode
= (str
&& strtol(str
, NULL
, 0)) ? 1 : 0;
3711 str
= database_get_data(rd
->d
.object
, KEY_HELPER_WEEKSTART
, RECDB_QSTRING
);
3712 hs_user
->week_start
= str
? strtol(str
, NULL
, 0) : 0;
3715 stats
= database_get_data(GET_RECORD_OBJECT(rd
), KEY_HELPER_STATS
, RECDB_OBJECT
);
3718 /* The tests for strlist->used are for converting the old format to the new one */
3719 strlist
= database_get_data(stats
, KEY_HELPER_STATS_TIME
, RECDB_STRING_LIST
);
3721 for (i
=0; i
< 5 && i
< strlist
->used
; i
++)
3722 hs_user
->time_per_week
[i
] = strtoul(strlist
->list
[i
], NULL
, 0);
3723 if (strlist
->used
== 4)
3724 hs_user
->time_per_week
[4] = hs_user
->time_per_week
[0]+hs_user
->time_per_week
[1]+hs_user
->time_per_week
[2]+hs_user
->time_per_week
[3];
3726 strlist
= database_get_data(stats
, KEY_HELPER_STATS_PICKUP
, RECDB_STRING_LIST
);
3728 for (i
=0; i
< 5 && i
< strlist
->used
; i
++)
3729 hs_user
->picked_up
[i
] = strtoul(strlist
->list
[i
], NULL
, 0);
3730 if (strlist
->used
== 2)
3731 hs_user
->picked_up
[4] = hs_user
->picked_up
[0]+hs_user
->picked_up
[1];
3733 strlist
= database_get_data(stats
, KEY_HELPER_STATS_CLOSE
, RECDB_STRING_LIST
);
3735 for (i
=0; i
< 5 && i
< strlist
->used
; i
++)
3736 hs_user
->closed
[i
] = strtoul(strlist
->list
[i
], NULL
, 0);
3737 if (strlist
->used
== 2)
3738 hs_user
->closed
[4] = hs_user
->closed
[0]+hs_user
->closed
[1];
3740 strlist
= database_get_data(stats
, KEY_HELPER_STATS_REASSIGNFROM
, RECDB_STRING_LIST
);
3742 for (i
=0; i
< 5 && i
< strlist
->used
; i
++)
3743 hs_user
->reassigned_from
[i
] = strtoul(strlist
->list
[i
], NULL
, 0);
3744 if (strlist
->used
== 2)
3745 hs_user
->reassigned_from
[4] = hs_user
->reassigned_from
[0]+hs_user
->reassigned_from
[1];
3747 strlist
= database_get_data(stats
, KEY_HELPER_STATS_REASSIGNTO
, RECDB_STRING_LIST
);
3749 for (i
=0; i
< 5 && i
< strlist
->used
; i
++)
3750 hs_user
->reassigned_to
[i
] = strtoul(strlist
->list
[i
], NULL
, 0);
3751 if (strlist
->used
== 2)
3752 hs_user
->reassigned_to
[4] = hs_user
->reassigned_to
[0]+hs_user
->reassigned_to
[1];
3759 static int request_write_helper(const char *key
, void *data
, void *extra
) {
3760 struct helpserv_request
*request
= data
;
3761 struct saxdb_context
*ctx
= extra
;
3763 if (!request
->handle
)
3766 saxdb_start_record(ctx
, key
, 0);
3767 if (request
->helper
) {
3768 saxdb_write_string(ctx
, KEY_REQUEST_HELPER
, request
->helper
->handle
->handle
);
3769 saxdb_write_int(ctx
, KEY_REQUEST_ASSIGNED
, request
->assigned
);
3771 saxdb_write_string(ctx
, KEY_REQUEST_HANDLE
, request
->handle
->handle
);
3772 saxdb_write_int(ctx
, KEY_REQUEST_OPENED
, request
->opened
);
3773 saxdb_write_string_list(ctx
, KEY_REQUEST_TEXT
, request
->text
);
3774 saxdb_end_record(ctx
);
3778 static int request_read_helper(const char *key
, void *data
, void *extra
) {
3779 struct record_data
*rd
= data
;
3780 struct helpserv_bot
*hs
= extra
;
3781 struct helpserv_request
*request
;
3782 struct string_list
*strlist
;
3785 if (rd
->type
!= RECDB_OBJECT
|| !dict_size(rd
->d
.object
)) {
3786 log_module(HS_LOG
, LOG_ERROR
, "Invalid request %s:%s.", hs
->helpserv
->nick
, key
);
3790 request
= calloc(1, sizeof(struct helpserv_request
));
3792 request
->id
= strtoul(key
, NULL
, 0);
3794 request
->user
= NULL
;
3795 request
->parent_nick_list
= request
->parent_hand_list
= NULL
;
3797 str
= database_get_data(rd
->d
.object
, KEY_REQUEST_HANDLE
, RECDB_QSTRING
);
3798 if (!str
|| !(request
->handle
= get_handle_info(str
))) {
3799 log_module(HS_LOG
, LOG_ERROR
, "Request %s:%s has an invalid or nonexistant account.", hs
->helpserv
->nick
, key
);
3803 if (!(request
->parent_hand_list
= dict_find(helpserv_reqs_byhand_dict
, request
->handle
->handle
, NULL
))) {
3804 request
->parent_hand_list
= helpserv_reqlist_alloc();
3805 dict_insert(helpserv_reqs_byhand_dict
, request
->handle
->handle
, request
->parent_hand_list
);
3807 helpserv_reqlist_append(request
->parent_hand_list
, request
);
3809 str
= database_get_data(rd
->d
.object
, KEY_REQUEST_OPENED
, RECDB_QSTRING
);
3811 log_module(HS_LOG
, LOG_ERROR
, "Request %s:%s has a nonexistant opening time. Using time(NULL).", hs
->helpserv
->nick
, key
);
3812 request
->opened
= time(NULL
);
3814 request
->opened
= (time_t)strtoul(str
, NULL
, 0);
3817 str
= database_get_data(rd
->d
.object
, KEY_REQUEST_ASSIGNED
, RECDB_QSTRING
);
3819 request
->assigned
= (time_t)strtoul(str
, NULL
, 0);
3821 str
= database_get_data(rd
->d
.object
, KEY_REQUEST_HELPER
, RECDB_QSTRING
);
3823 if (!(request
->helper
= dict_find(hs
->users
, str
, NULL
))) {
3824 log_module(HS_LOG
, LOG_ERROR
, "Request %s:%s has an invalid or nonexistant helper.", hs
->helpserv
->nick
, key
);
3829 if (!hs
->unhandled
) {
3830 request
->next_unhandled
= NULL
;
3831 hs
->unhandled
= request
;
3832 } else if (hs
->unhandled
->opened
> request
->opened
) {
3833 request
->next_unhandled
= hs
->unhandled
;
3834 hs
->unhandled
= request
;
3836 struct helpserv_request
*unh
;
3837 for (unh
= hs
->unhandled
; unh
->next_unhandled
&& (unh
->next_unhandled
->opened
< request
->opened
); unh
= unh
->next_unhandled
);
3838 request
->next_unhandled
= unh
->next_unhandled
;
3839 unh
->next_unhandled
= request
;
3843 strlist
= database_get_data(rd
->d
.object
, KEY_REQUEST_TEXT
, RECDB_STRING_LIST
);
3845 log_module(HS_LOG
, LOG_ERROR
, "Request %s:%s has no text.", hs
->helpserv
->nick
, key
);
3849 request
->text
= string_list_copy(strlist
);
3851 dict_insert(hs
->requests
, strdup(key
), request
);
3857 helpserv_bot_write(const char *key
, void *data
, void *extra
) {
3858 const struct helpserv_bot
*hs
= data
;
3859 struct saxdb_context
*ctx
= extra
;
3860 enum page_source pagesrc
;
3861 enum message_type msgtype
;
3862 enum interval_type inttype
;
3863 enum persistence_type persisttype
;
3864 struct string_list
*slist
;
3867 saxdb_start_record(ctx
, key
, 1);
3870 saxdb_start_record(ctx
, KEY_HELPERS
, 1);
3871 dict_foreach(hs
->users
, user_write_helper
, ctx
);
3872 saxdb_end_record(ctx
);
3875 if (hs
->persist_types
[PERSIST_T_REQUEST
] == PERSIST_CLOSE
) {
3876 saxdb_start_record(ctx
, KEY_REQUESTS
, 0);
3877 dict_foreach(hs
->requests
, request_write_helper
, ctx
);
3878 saxdb_end_record(ctx
);
3881 /* Other settings and state */
3882 saxdb_write_string(ctx
, KEY_HELP_CHANNEL
, hs
->helpchan
->name
);
3883 slist
= alloc_string_list(PGSRC_COUNT
);
3884 for (pagesrc
=0; pagesrc
<PGSRC_COUNT
; pagesrc
++) {
3885 struct chanNode
*target
= hs
->page_targets
[pagesrc
];
3886 string_list_append(slist
, strdup(target
? target
->name
: "*"));
3888 saxdb_write_string_list(ctx
, KEY_PAGE_DEST
, slist
);
3889 free_string_list(slist
);
3890 for (pagesrc
=0; pagesrc
<PGSRC_COUNT
; pagesrc
++) {
3891 const char *src
= page_types
[hs
->page_types
[pagesrc
]].db_name
;
3892 saxdb_write_string(ctx
, page_sources
[pagesrc
].db_name
, src
);
3894 for (msgtype
=0; msgtype
<MSGTYPE_COUNT
; msgtype
++) {
3895 const char *msg
= hs
->messages
[msgtype
];
3897 saxdb_write_string(ctx
, message_types
[msgtype
].db_name
, msg
);
3899 for (inttype
=0; inttype
<INTERVAL_COUNT
; inttype
++) {
3900 if (!hs
->intervals
[inttype
])
3902 saxdb_write_int(ctx
, interval_types
[inttype
].db_name
, hs
->intervals
[inttype
]);
3904 for (persisttype
=0; persisttype
<PERSIST_T_COUNT
; persisttype
++) {
3905 const char *persist
= persistence_lengths
[hs
->persist_types
[persisttype
]].db_name
;
3906 saxdb_write_string(ctx
, persistence_types
[persisttype
].db_name
, persist
);
3908 saxdb_write_string(ctx
, KEY_NOTIFICATION
, notification_types
[hs
->notify
].db_name
);
3909 saxdb_write_int(ctx
, KEY_REGISTERED
, hs
->registered
);
3910 saxdb_write_int(ctx
, KEY_IDWRAP
, hs
->id_wrap
);
3911 saxdb_write_int(ctx
, KEY_REQ_MAXLEN
, hs
->req_maxlen
);
3912 saxdb_write_int(ctx
, KEY_LAST_REQUESTID
, hs
->last_requestid
);
3914 saxdb_write_string(ctx
, KEY_REGISTRAR
, hs
->registrar
);
3915 saxdb_write_int(ctx
, KEY_PRIVMSG_ONLY
, hs
->privmsg_only
);
3916 saxdb_write_int(ctx
, KEY_REQ_ON_JOIN
, hs
->req_on_join
);
3917 saxdb_write_int(ctx
, KEY_AUTO_VOICE
, hs
->auto_voice
);
3918 saxdb_write_int(ctx
, KEY_AUTO_DEVOICE
, hs
->auto_devoice
);
3919 saxdb_write_int(ctx
, KEY_JOIN_TOTAL
, hs
->join_total
);
3920 saxdb_write_int(ctx
, KEY_ALERT_NEW
, hs
->alert_new
);
3921 saxdb_write_int(ctx
, KEY_LAST_ACTIVE
, hs
->last_active
);
3923 if (hs
->suspended
) {
3924 saxdb_write_int(ctx
, KEY_SUSPENDED
, hs
->suspended
);
3925 saxdb_write_int(ctx
, KEY_EXPIRY
, hs
->expiry
);
3926 saxdb_write_int(ctx
, KEY_ISSUED
, hs
->issued
);
3927 saxdb_write_string(ctx
, KEY_SUSPENDER
, hs
->suspender
);
3928 saxdb_write_string(ctx
, KEY_REASON
, hs
->reason
);
3931 /* End bot record */
3932 saxdb_end_record(ctx
);
3937 helpserv_saxdb_write(struct saxdb_context
*ctx
) {
3938 saxdb_start_record(ctx
, KEY_BOTS
, 1);
3939 dict_foreach(helpserv_bots_dict
, helpserv_bot_write
, ctx
);
3940 saxdb_end_record(ctx
);
3941 saxdb_write_int(ctx
, KEY_LAST_STATS_UPDATE
, last_stats_update
);
3945 static int helpserv_bot_read(const char *key
, void *data
, UNUSED_ARG(void *extra
)) {
3946 struct record_data
*br
= data
, *raw_record
;
3947 struct helpserv_bot
*hs
;
3948 char *registrar
, *helpchannel_name
, *str
;
3949 dict_t users
, requests
;
3950 enum page_source pagesrc
;
3951 enum message_type msgtype
;
3952 enum interval_type inttype
;
3953 enum persistence_type persisttype
;
3955 users
= database_get_data(GET_RECORD_OBJECT(br
), KEY_HELPERS
, RECDB_OBJECT
);
3957 log_module(HS_LOG
, LOG_ERROR
, "%s has no users.", key
);
3960 helpchannel_name
= database_get_data(GET_RECORD_OBJECT(br
), KEY_HELP_CHANNEL
, RECDB_QSTRING
);
3961 if (!helpchannel_name
|| !IsChannelName(helpchannel_name
)) {
3962 log_module(HS_LOG
, LOG_ERROR
, "%s has an invalid channel name.", key
);
3965 registrar
= database_get_data(GET_RECORD_OBJECT(br
), KEY_REGISTRAR
, RECDB_QSTRING
);
3967 hs
= register_helpserv(key
, helpchannel_name
, registrar
);
3969 raw_record
= dict_find(GET_RECORD_OBJECT(br
), KEY_PAGE_DEST
, NULL
);
3970 switch (raw_record
? raw_record
->type
: RECDB_INVALID
) {
3972 set_page_target(hs
, PGSRC_COMMAND
, GET_RECORD_QSTRING(raw_record
));
3973 pagesrc
= PGSRC_COMMAND
+ 1;
3975 case RECDB_STRING_LIST
: {
3976 struct string_list
*slist
= GET_RECORD_STRING_LIST(raw_record
);
3977 for (pagesrc
=0; (pagesrc
<slist
->used
) && (pagesrc
<PGSRC_COUNT
); pagesrc
++) {
3978 const char *dest
= slist
->list
[pagesrc
];
3979 set_page_target(hs
, pagesrc
, strcmp(dest
, "*") ? dest
: NULL
);
3984 set_page_target(hs
, PGSRC_COMMAND
, NULL
);
3985 pagesrc
= PGSRC_COMMAND
+ 1;
3988 while (pagesrc
< PGSRC_COUNT
) {
3989 set_page_target(hs
, pagesrc
++, hs
->page_targets
[PGSRC_COMMAND
] ? hs
->page_targets
[PGSRC_COMMAND
]->name
: NULL
);
3992 for (pagesrc
=0; pagesrc
<PGSRC_COUNT
; pagesrc
++) {
3993 str
= database_get_data(GET_RECORD_OBJECT(br
), page_sources
[pagesrc
].db_name
, RECDB_QSTRING
);
3994 hs
->page_types
[pagesrc
] = str
? page_type_from_name(str
) : PAGE_NONE
;
3997 for (msgtype
=0; msgtype
<MSGTYPE_COUNT
; msgtype
++) {
3998 str
= database_get_data(GET_RECORD_OBJECT(br
), message_types
[msgtype
].db_name
, RECDB_QSTRING
);
3999 hs
->messages
[msgtype
] = str
? strdup(str
) : NULL
;
4002 for (inttype
=0; inttype
<INTERVAL_COUNT
; inttype
++) {
4003 str
= database_get_data(GET_RECORD_OBJECT(br
), interval_types
[inttype
].db_name
, RECDB_QSTRING
);
4004 hs
->intervals
[inttype
] = str
? ParseInterval(str
) : 0;
4006 if (hs
->intervals
[INTERVAL_WHINE_INTERVAL
])
4007 timeq_add(now
+ hs
->intervals
[INTERVAL_WHINE_INTERVAL
], run_whine_interval
, hs
);
4008 if (hs
->intervals
[INTERVAL_EMPTY_INTERVAL
])
4009 timeq_add(now
+ hs
->intervals
[INTERVAL_EMPTY_INTERVAL
], run_empty_interval
, hs
);
4011 for (persisttype
=0; persisttype
<PERSIST_T_COUNT
; persisttype
++) {
4012 str
= database_get_data(GET_RECORD_OBJECT(br
), persistence_types
[persisttype
].db_name
, RECDB_QSTRING
);
4013 hs
->persist_types
[persisttype
] = str
? persistence_from_name(str
) : PERSIST_QUIT
;
4015 str
= database_get_data(GET_RECORD_OBJECT(br
), KEY_NOTIFICATION
, RECDB_QSTRING
);
4016 hs
->notify
= str
? notification_from_name(str
) : NOTIFY_NONE
;
4017 str
= database_get_data(GET_RECORD_OBJECT(br
), KEY_REGISTERED
, RECDB_QSTRING
);
4019 hs
->registered
= (time_t)strtol(str
, NULL
, 0);
4020 str
= database_get_data(GET_RECORD_OBJECT(br
), KEY_IDWRAP
, RECDB_QSTRING
);
4022 hs
->id_wrap
= strtoul(str
, NULL
, 0);
4023 str
= database_get_data(GET_RECORD_OBJECT(br
), KEY_REQ_MAXLEN
, RECDB_QSTRING
);
4025 hs
->req_maxlen
= strtoul(str
, NULL
, 0);
4026 str
= database_get_data(GET_RECORD_OBJECT(br
), KEY_LAST_REQUESTID
, RECDB_QSTRING
);
4028 hs
->last_requestid
= strtoul(str
, NULL
, 0);
4029 str
= database_get_data(GET_RECORD_OBJECT(br
), KEY_PRIVMSG_ONLY
, RECDB_QSTRING
);
4030 hs
->privmsg_only
= str
? enabled_string(str
) : 0;
4031 str
= database_get_data(GET_RECORD_OBJECT(br
), KEY_REQ_ON_JOIN
, RECDB_QSTRING
);
4032 hs
->req_on_join
= str
? enabled_string(str
) : 0;
4033 str
= database_get_data(GET_RECORD_OBJECT(br
), KEY_AUTO_VOICE
, RECDB_QSTRING
);
4034 hs
->auto_voice
= str
? enabled_string(str
) : 0;
4035 str
= database_get_data(GET_RECORD_OBJECT(br
), KEY_AUTO_DEVOICE
, RECDB_QSTRING
);
4036 hs
->auto_devoice
= str
? enabled_string(str
) : 0;
4037 str
= database_get_data(GET_RECORD_OBJECT(br
), KEY_JOIN_TOTAL
, RECDB_QSTRING
);
4038 hs
->join_total
= str
? enabled_string(str
) : 0;
4039 str
= database_get_data(GET_RECORD_OBJECT(br
), KEY_ALERT_NEW
, RECDB_QSTRING
);
4040 hs
->alert_new
= str
? enabled_string(str
) : 0;
4041 str
= database_get_data(GET_RECORD_OBJECT(br
), KEY_LAST_ACTIVE
, RECDB_QSTRING
);
4042 hs
->last_active
= str
? atoi(str
) : now
;
4044 str
= database_get_data(GET_RECORD_OBJECT(br
), KEY_SUSPENDED
, RECDB_QSTRING
);
4045 hs
->suspended
= str
? atoi(str
) : 0;
4046 if (hs
->suspended
) {
4047 str
= database_get_data(GET_RECORD_OBJECT(br
), KEY_EXPIRY
, RECDB_QSTRING
);
4048 hs
->expiry
= str
? atoi(str
) : 0;
4049 str
= database_get_data(GET_RECORD_OBJECT(br
), KEY_ISSUED
, RECDB_QSTRING
);
4050 hs
->issued
= str
? atoi(str
) : 0;
4051 str
= database_get_data(GET_RECORD_OBJECT(br
), KEY_SUSPENDER
, RECDB_QSTRING
);
4052 hs
->suspender
= str
? str
: 0;
4053 str
= database_get_data(GET_RECORD_OBJECT(br
), KEY_REASON
, RECDB_QSTRING
);
4054 hs
->reason
= str
? str
: 0;
4057 dict_foreach(users
, user_read_helper
, hs
);
4059 requests
= database_get_data(GET_RECORD_OBJECT(br
), KEY_REQUESTS
, RECDB_OBJECT
);
4061 dict_foreach(requests
, request_read_helper
, hs
);
4063 if(hs
->suspended
&& hs
->expiry
)
4064 timeq_add(hs
->expiry
, helpserv_expire_suspension
, hs
);
4070 helpserv_saxdb_read(struct dict
*conf_db
) {
4074 if ((object
= database_get_data(conf_db
, KEY_BOTS
, RECDB_OBJECT
))) {
4075 dict_foreach(object
, helpserv_bot_read
, NULL
);
4078 str
= database_get_data(conf_db
, KEY_LAST_STATS_UPDATE
, RECDB_QSTRING
);
4079 last_stats_update
= str
? (time_t)strtol(str
, NULL
, 0) : now
;
4083 static void helpserv_conf_read(void) {
4087 if (!(conf_node
= conf_get_data(HELPSERV_CONF_NAME
, RECDB_OBJECT
))) {
4088 log_module(HS_LOG
, LOG_ERROR
, "config node `%s' is missing or has wrong type", HELPSERV_CONF_NAME
);
4092 str
= database_get_data(conf_node
, "db_backup_freq", RECDB_QSTRING
);
4093 helpserv_conf
.db_backup_frequency
= str
? ParseInterval(str
) : 7200;
4095 str
= database_get_data(conf_node
, "description", RECDB_QSTRING
);
4096 helpserv_conf
.description
= str
? str
: "Help Queue Manager";
4098 str
= database_get_data(conf_node
, "reqlogfile", RECDB_QSTRING
);
4099 if (str
&& strlen(str
))
4100 helpserv_conf
.reqlogfile
= str
;
4102 helpserv_conf
.reqlogfile
= NULL
;
4104 str
= database_get_data(conf_node
, "expiration", RECDB_QSTRING
);
4105 helpserv_conf
.expire_age
= ParseInterval(str
? str
: "60d");
4106 str
= database_get_data(conf_node
, "user_escape", RECDB_QSTRING
);
4107 helpserv_conf
.user_escape
= str
? str
[0] : '@';
4113 if (helpserv_conf
.reqlogfile
4114 && !(reqlog_f
= fopen(helpserv_conf
.reqlogfile
, "a"))) {
4115 log_module(HS_LOG
, LOG_ERROR
, "Unable to open request logfile (%s): %s", helpserv_conf
.reqlogfile
, strerror(errno
));
4119 static struct helpserv_cmd
*
4120 helpserv_define_func(const char *name
, helpserv_func_t
*func
, enum helpserv_level level
, long flags
) {
4121 struct helpserv_cmd
*cmd
= calloc(1, sizeof(struct helpserv_cmd
));
4123 cmd
->access
= level
;
4127 dict_insert(helpserv_func_dict
, name
, cmd
);
4132 /* Drop requests that persist until part when a user leaves the chan */
4133 static void handle_part(struct modeNode
*mn
, UNUSED_ARG(const char *reason
), UNUSED_ARG(void *extra
)) {
4134 struct helpserv_botlist
*botlist
;
4135 struct helpserv_userlist
*userlist
;
4136 const int from_opserv
= 0; /* for helpserv_notice */
4139 if ((botlist
= dict_find(helpserv_bots_bychan_dict
, mn
->channel
->name
, NULL
))) {
4140 for (i
=0; i
< botlist
->used
; i
++) {
4141 struct helpserv_bot
*hs
;
4144 hs
= botlist
->list
[i
];
4147 if (hs
->persist_types
[PERSIST_T_REQUEST
] != PERSIST_PART
)
4150 for (it
=dict_first(hs
->requests
); it
; it
=iter_next(it
)) {
4151 struct helpserv_request
*req
= iter_data(it
);
4153 if (mn
->user
!= req
->user
)
4155 if (req
->text
->used
) {
4156 helpserv_message(hs
, mn
->user
, MSGTYPE_REQ_DROPPED
);
4157 helpserv_msguser(mn
->user
, "HSMSG_REQ_DROPPED_PART", mn
->channel
->name
, req
->id
);
4158 if (req
->helper
&& (hs
->notify
>= NOTIFY_DROP
))
4159 helpserv_notify(req
->helper
, "HSMSG_NOTIFY_REQ_DROP_PART", req
->id
, mn
->user
->nick
);
4161 helpserv_log_request(req
, "Dropped");
4162 dict_remove(hs
->requests
, iter_key(it
));
4168 if (mn
->user
->handle_info
&& (userlist
= dict_find(helpserv_users_byhand_dict
, mn
->user
->handle_info
->handle
, NULL
))) {
4169 for (i
=0; i
< userlist
->used
; i
++) {
4170 struct helpserv_user
*hs_user
= userlist
->list
[i
];
4171 struct helpserv_bot
*hs
= hs_user
->hs
;
4174 if ((hs
->helpserv
== NULL
) || (hs
->helpchan
!= mn
->channel
) || find_handle_in_channel(hs
->helpchan
, mn
->user
->handle_info
, mn
->user
))
4177 /* In case of the clock being set back for whatever reason,
4178 * minimize penalty. Don't duplicate this in handle_quit because
4179 * when users quit, handle_part is called for every channel first.
4181 if (hs_user
->join_time
&& (hs_user
->join_time
< now
)) {
4182 hs_user
->time_per_week
[0] += (unsigned int)(now
- hs_user
->join_time
);
4183 hs_user
->time_per_week
[4] += (unsigned int)(now
- hs_user
->join_time
);
4185 hs_user
->join_time
= 0;
4187 for (it
=dict_first(hs
->requests
); it
; it
=iter_next(it
)) {
4188 struct helpserv_request
*req
=iter_data(it
);
4190 if ((hs
->persist_types
[PERSIST_T_HELPER
] == PERSIST_PART
)
4191 && (req
->helper
== hs_user
)) {
4192 char our_reason
[CHANNELLEN
+ 8];
4193 sprintf(our_reason
, "parted %s", mn
->channel
->name
);
4194 helpserv_page_helper_gone(hs
, req
, our_reason
);
4198 if (hs
->intervals
[INTERVAL_EMPTY_INTERVAL
] && hs_user
->level
>= HlHelper
) {
4201 if ((num_trials
= find_helpchan_helpers(hs
)) >= 0) {
4202 unsigned int num_unh
;
4203 struct helpserv_request
*unh
;
4205 for (num_unh
=0, unh
=hs
->unhandled
; unh
; num_unh
++)
4206 unh
= unh
->next_unhandled
;
4209 helpserv_page(PGSRC_ALERT
, "HSMSG_PAGE_FIRSTONLYTRIALALERT", hs
->helpchan
->name
, mn
->user
->nick
, num_trials
, num_unh
);
4211 helpserv_page(PGSRC_ALERT
, "HSMSG_PAGE_FIRSTEMPTYALERT", hs
->helpchan
->name
, mn
->user
->nick
, num_unh
);
4213 if (num_unh
|| !hs
->req_on_join
) {
4214 timeq_del(0, run_empty_interval
, hs
, TIMEQ_IGNORE_WHEN
);
4215 timeq_add(now
+ hs
->intervals
[INTERVAL_EMPTY_INTERVAL
], run_empty_interval
, hs
);
4223 /* Drop requests that persist until part or quit when a user quits. Otherwise
4224 * set req->user to null (it's no longer valid) if they have a handle,
4225 * and drop it if they don't (nowhere to store the request).
4227 * Unassign requests where req->helper persists until the helper parts or
4229 static void handle_quit(struct userNode
*user
, UNUSED_ARG(struct userNode
*killer
), UNUSED_ARG(const char *why
), UNUSED_ARG(void *extra
)) {
4230 struct helpserv_reqlist
*reqlist
;
4231 struct helpserv_userlist
*userlist
;
4234 if (IsLocal(user
)) {
4235 struct helpserv_bot
*hs
;
4236 if ((hs
= dict_find(helpserv_bots_dict
, user
->nick
, NULL
))) {
4237 hs
->helpserv
= NULL
;
4242 if ((reqlist
= dict_find(helpserv_reqs_bynick_dict
, user
->nick
, NULL
))) {
4244 for (i
=0; i
< n
; i
++) {
4245 struct helpserv_request
*req
= reqlist
->list
[0];
4247 if ((req
->hs
->persist_types
[PERSIST_T_REQUEST
] == PERSIST_QUIT
) || !req
->handle
) {
4249 sprintf(buf
, "%lu", req
->id
);
4251 if (req
->helper
&& (req
->hs
->notify
>= NOTIFY_DROP
))
4252 helpserv_notify(req
->helper
, "HSMSG_NOTIFY_REQ_DROP_QUIT", req
->id
, req
->user
->nick
);
4254 helpserv_log_request(req
, "Dropped");
4255 dict_remove(req
->hs
->requests
, buf
);
4258 req
->parent_nick_list
= NULL
;
4259 helpserv_reqlist_remove(reqlist
, req
);
4261 if (req
->helper
&& (req
->hs
->notify
>= NOTIFY_USER
))
4262 helpserv_notify(req
->helper
, "HSMSG_NOTIFY_USER_QUIT", req
->id
, user
->nick
);
4266 dict_remove(helpserv_reqs_bynick_dict
, user
->nick
);
4269 if (user
->handle_info
&& (userlist
= dict_find(helpserv_users_byhand_dict
, user
->handle_info
->handle
, NULL
))) {
4270 for (i
=0; i
< userlist
->used
; i
++) {
4271 struct helpserv_user
*hs_user
= userlist
->list
[i
];
4272 struct helpserv_bot
*hs
= hs_user
->hs
;
4275 if ((hs
->helpserv
== NULL
) || user
->next_authed
|| (user
->handle_info
->users
!= user
))
4278 for (it
=dict_first(hs
->requests
); it
; it
=iter_next(it
)) {
4279 struct helpserv_request
*req
=iter_data(it
);
4281 if ((hs
->persist_types
[PERSIST_T_HELPER
] == PERSIST_QUIT
) && (req
->helper
== hs_user
)) {
4282 helpserv_page_helper_gone(hs
, req
, "disconnected");
4289 static void associate_requests_bybot(struct helpserv_bot
*hs
, struct userNode
*user
, int force_greet
) {
4290 struct helpserv_reqlist
*reqlist
, *hand_reqlist
=NULL
;
4291 struct helpserv_request
*newest
=NULL
, *nicknewest
=NULL
;
4293 const int from_opserv
= 0; /* For helpserv_notice */
4295 if (!(user
->handle_info
&& (hand_reqlist
= dict_find(helpserv_reqs_byhand_dict
, user
->handle_info
->handle
, NULL
))) && !force_greet
) {
4299 reqlist
= dict_find(helpserv_reqs_bynick_dict
, user
->nick
, NULL
);
4302 for (i
=0; i
< hand_reqlist
->used
; i
++) {
4303 struct helpserv_request
*req
=hand_reqlist
->list
[i
];
4305 if (req
->user
|| (req
->hs
!= hs
))
4310 reqlist
= helpserv_reqlist_alloc();
4311 dict_insert(helpserv_reqs_bynick_dict
, user
->nick
, reqlist
);
4313 req
->parent_nick_list
= reqlist
;
4314 helpserv_reqlist_append(reqlist
, req
);
4316 if (req
->helper
&& (hs
->notify
>= NOTIFY_USER
))
4317 helpserv_notify(req
->helper
, "HSMSG_NOTIFY_USER_FOUND", req
->id
, user
->nick
);
4319 if (!newest
|| (newest
->opened
< req
->opened
))
4324 /* If it's supposed to force a greeting, only bail out if there are no
4325 * requests at all. If it's not supposed to force a greeting, bail out if
4326 * nothing was changed. */
4327 if (!(newest
|| (force_greet
&& reqlist
)))
4330 /* Possible conditions here:
4331 * 1. newest == NULL, force_greeting == 1, reqlist != NULL
4332 * 2. newest != NULL, force_greeting doesn't matter, reqlist != NULL */
4334 /* Figure out which request will get their next message */
4335 for (i
=0; i
< reqlist
->used
; i
++) {
4336 struct helpserv_request
*req
=reqlist
->list
[i
];
4341 if (!nicknewest
|| (nicknewest
->opened
< req
->opened
))
4344 if (hs
->auto_voice
&& req
->helper
)
4346 struct mod_chanmode change
;
4347 mod_chanmode_init(&change
);
4349 change
.args
[0].mode
= MODE_VOICE
;
4350 if ((change
.args
[0].u
.member
= GetUserMode(hs
->helpchan
, user
)))
4351 mod_chanmode_announce(hs
->helpserv
, hs
->helpchan
, &change
);
4355 if ((force_greet
&& nicknewest
) || (newest
&& (nicknewest
== newest
))) {
4356 /* Let the user know. Either the user is forced to be greeted, or the
4357 * above has changed which request will get their next message. */
4358 helpserv_msguser(user
, "HSMSG_GREET_EXISTING_REQ", hs
->helpchan
->name
, nicknewest
->id
);
4362 static void associate_requests_bychan(struct chanNode
*chan
, struct userNode
*user
, int force_greet
) {
4363 struct helpserv_botlist
*botlist
;
4366 if (!(botlist
= dict_find(helpserv_bots_bychan_dict
, chan
->name
, NULL
)))
4369 for (i
=0; i
< botlist
->used
; i
++)
4370 associate_requests_bybot(botlist
->list
[i
], user
, force_greet
);
4374 /* Greet users upon joining a helpserv channel (if greeting is set) and set
4375 * req->user to the user joining for all requests owned by the user's handle
4376 * (if any) with a req->user == NULL */
4377 static int handle_join(struct modeNode
*mNode
, UNUSED_ARG(void *extra
)) {
4378 struct userNode
*user
= mNode
->user
;
4379 struct chanNode
*chan
= mNode
->channel
;
4380 struct helpserv_botlist
*botlist
;
4382 const int from_opserv
= 0; /* for helpserv_notice */
4387 if (!(botlist
= dict_find(helpserv_bots_bychan_dict
, chan
->name
, NULL
)))
4390 for (i
=0; i
< botlist
->used
; i
++) {
4391 struct helpserv_bot
*hs
=botlist
->list
[i
];
4393 if (user
->handle_info
) {
4394 struct helpserv_user
*hs_user
;
4396 if ((hs_user
= dict_find(hs
->users
, user
->handle_info
->handle
, NULL
))) {
4397 if (!hs_user
->join_time
)
4398 hs_user
->join_time
= now
;
4400 if (hs
->join_total
) {
4401 if (hs_user
->level
>= HlHelper
) {
4403 struct helpserv_request
*req
;
4405 for (req
= hs
->unhandled
, total
=0; req
; req
= req
->next_unhandled
, total
++) ;
4408 helpserv_notice(user
, "HSMSG_REQUESTS_OPEN", total
, hs
->helpserv
->nick
, hs
->helpserv
->nick
);
4412 if (hs_user
->level
>= HlHelper
&& hs
->intervals
[INTERVAL_EMPTY_INTERVAL
] && hs
->helpchan_empty
) {
4413 hs
->helpchan_empty
= 0;
4414 timeq_del(0, run_empty_interval
, hs
, TIMEQ_IGNORE_WHEN
);
4415 helpserv_page(PGSRC_ALERT
, "HSMSG_PAGE_EMPTYNOMORE", user
->nick
, hs
->helpchan
->name
);
4417 continue; /* Don't want helpers to have request-on-join */
4421 if (self
->burst
&& !hs
->req_on_join
)
4424 associate_requests_bybot(hs
, user
, 1);
4426 helpserv_message(hs
, user
, MSGTYPE_GREETING
);
4428 /* Make sure this is at the end (because of the continues) */
4429 if (hs
->req_on_join
) {
4430 struct helpserv_reqlist
*reqlist
;
4433 if ((reqlist
= dict_find(helpserv_reqs_bynick_dict
, user
->nick
, NULL
))) {
4434 for (j
=0; j
< reqlist
->used
; j
++)
4435 if (reqlist
->list
[i
]->hs
== hs
)
4437 if (j
< reqlist
->used
)
4441 create_request(user
, hs
, 1);
4447 /* Update helpserv_reqs_bynick_dict upon nick change */
4448 static void handle_nickchange(struct userNode
*user
, const char *old_nick
, UNUSED_ARG(void *extra
)) {
4449 struct helpserv_reqlist
*reqlist
;
4452 if (!(reqlist
= dict_find(helpserv_reqs_bynick_dict
, old_nick
, NULL
)))
4455 /* Don't free the list when we switch it over to the new nick. */
4456 dict_remove2(helpserv_reqs_bynick_dict
, old_nick
, 1);
4457 dict_insert(helpserv_reqs_bynick_dict
, user
->nick
, reqlist
);
4459 for (i
=0; i
< reqlist
->used
; i
++) {
4460 struct helpserv_request
*req
=reqlist
->list
[i
];
4462 if (req
->helper
&& (req
->hs
->notify
>= NOTIFY_USER
))
4463 helpserv_notify(req
->helper
, "HSMSG_NOTIFY_USER_NICK", req
->id
, old_nick
, user
->nick
);
4467 /* Also update helpserv_reqs_byhand_dict upon handle rename */
4468 static void handle_nickserv_rename(struct handle_info
*handle
, const char *old_handle
, UNUSED_ARG(void *extra
)) {
4469 struct helpserv_reqlist
*reqlist
;
4470 struct helpserv_userlist
*userlist
;
4473 /* First, rename the handle in the requests dict */
4474 if ((reqlist
= dict_find(helpserv_reqs_byhand_dict
, old_handle
, NULL
))) {
4475 /* Don't free the list */
4476 dict_remove2(helpserv_reqs_byhand_dict
, old_handle
, 1);
4477 dict_insert(helpserv_reqs_byhand_dict
, handle
->handle
, reqlist
);
4480 /* Second, rename the handle in the users dict */
4481 if ((userlist
= dict_find(helpserv_users_byhand_dict
, old_handle
, NULL
))) {
4482 dict_remove2(helpserv_users_byhand_dict
, old_handle
, 1);
4484 for (i
=0; i
< userlist
->used
; i
++)
4485 dict_remove2(userlist
->list
[i
]->hs
->users
, old_handle
, 1);
4487 dict_insert(helpserv_users_byhand_dict
, handle
->handle
, userlist
);
4488 for (i
=0; i
< userlist
->used
; i
++)
4489 dict_insert(userlist
->list
[i
]->hs
->users
, handle
->handle
, userlist
->list
[i
]);
4493 for (i
=0; i
< reqlist
->used
; i
++) {
4494 struct helpserv_request
*req
=reqlist
->list
[i
];
4496 if (req
->helper
&& (req
->hs
->notify
>= NOTIFY_HANDLE
))
4497 helpserv_notify(req
->helper
, "HSMSG_NOTIFY_HAND_RENAME", req
->id
, old_handle
, handle
->handle
);
4502 /* Deals with two cases:
4503 * 1. No handle -> handle
4504 * - Bots with a request assigned to both the user (w/o handle) and the
4505 * handle can exist in this case. When a message is sent,
4506 * helpserv_usermsg will append it to the most recently opened request.
4507 * - Requests assigned to the handle are NOT assigned to the user, since
4508 * multiple people can auth to the same handle at once. Wait for them to
4509 * join / privmsg before setting req->user.
4510 * 2. Handle -> handle
4511 * - Generally avoided, but sometimes the code may allow this.
4512 * - Requests that persist only until part/quit are brought along to the
4514 * - Requests that persist until closed (stay saved with the handle) are
4515 * left with the old handle. This is to prevent the confusing situation
4516 * where some requests are carried over to the new handle, and some are
4517 * left (because req->handle is the same for all of them, but only some
4518 * have req->user set).
4519 * - In either of the above cases, if a user is on a bot's userlist and has
4520 * requests assigned to them, it will give them a list. */
4521 static void handle_nickserv_auth(struct userNode
*user
, struct handle_info
*old_handle
, UNUSED_ARG(void* extra
)) {
4522 struct helpserv_reqlist
*reqlist
, *dellist
=NULL
, *hand_reqlist
, *oldhand_reqlist
;
4523 struct helpserv_userlist
*userlist
;
4526 const int from_opserv
= 0; /* for helpserv_notice */
4528 if (!user
->handle_info
)
4529 return; /* Authed user is quitting */
4531 if ((userlist
= dict_find(helpserv_users_byhand_dict
, user
->handle_info
->handle
, NULL
))) {
4532 for (i
=0; i
< userlist
->used
; i
++) {
4533 struct helpserv_user
*hs_user
= userlist
->list
[i
];
4534 struct helpserv_bot
*hs
= hs_user
->hs
;
4535 struct helpserv_reqlist helper_reqs
;
4536 struct helpfile_table tbl
;
4538 if (!hs_user
->join_time
&& find_handle_in_channel(hs
->helpchan
, hs_user
->handle
, NULL
))
4539 hs_user
->join_time
= now
;
4541 helpserv_reqlist_init(&helper_reqs
);
4543 for (it
=dict_first(hs
->requests
); it
; it
=iter_next(it
)) {
4544 struct helpserv_request
*req
=iter_data(it
);
4546 if (req
->helper
== hs_user
)
4547 helpserv_reqlist_append(&helper_reqs
, req
);
4550 if (helper_reqs
.used
) {
4551 tbl
.length
= helper_reqs
.used
+1;
4553 tbl
.flags
= TABLE_NO_FREE
;
4554 tbl
.contents
= alloca(tbl
.length
* sizeof(*tbl
.contents
));
4555 tbl
.contents
[0] = alloca(tbl
.width
* sizeof(**tbl
.contents
));
4556 tbl
.contents
[0][0] = "Bot";
4557 tbl
.contents
[0][1] = "ID#";
4558 tbl
.contents
[0][2] = "Nick";
4559 tbl
.contents
[0][3] = "Account";
4560 tbl
.contents
[0][4] = "Opened";
4562 for (j
=1; j
<= helper_reqs
.used
; j
++) {
4563 struct helpserv_request
*req
=helper_reqs
.list
[j
-1];
4564 char reqid
[12], timestr
[MAX_LINE_SIZE
];
4566 tbl
.contents
[j
] = alloca(tbl
.width
* sizeof(**tbl
.contents
));
4567 tbl
.contents
[j
][0] = req
->hs
->helpserv
->nick
;
4568 sprintf(reqid
, "%lu", req
->id
);
4569 tbl
.contents
[j
][1] = strdup(reqid
);
4570 tbl
.contents
[j
][2] = req
->user
? req
->user
->nick
: "Not online";
4571 tbl
.contents
[j
][3] = req
->handle
? req
->handle
->handle
: "Not authed";
4572 strftime(timestr
, MAX_LINE_SIZE
, HSFMT_TIME
, localtime(&req
->opened
));
4573 tbl
.contents
[j
][4] = strdup(timestr
);
4576 helpserv_notice(user
, "HSMSG_REQLIST_AUTH");
4577 table_send(hs
->helpserv
, user
->nick
, 0, NULL
, tbl
);
4579 for (j
=1; j
<= helper_reqs
.used
; j
++) {
4580 free((char *)tbl
.contents
[j
][1]);
4581 free((char *)tbl
.contents
[j
][4]);
4585 helpserv_reqlist_clean(&helper_reqs
);
4590 if (!(reqlist
= dict_find(helpserv_reqs_bynick_dict
, user
->nick
, NULL
))) {
4591 for (i
=0; i
< user
->channels
.used
; i
++)
4592 associate_requests_bychan(user
->channels
.list
[i
]->channel
, user
, 0);
4596 if (!(hand_reqlist
= dict_find(helpserv_reqs_byhand_dict
, user
->handle_info
->handle
, NULL
))) {
4597 hand_reqlist
= helpserv_reqlist_alloc();
4598 dict_insert(helpserv_reqs_byhand_dict
, user
->handle_info
->handle
, hand_reqlist
);
4602 dellist
= helpserv_reqlist_alloc();
4603 oldhand_reqlist
= dict_find(helpserv_reqs_byhand_dict
, old_handle
->handle
, NULL
);
4605 oldhand_reqlist
= NULL
;
4608 for (i
=0; i
< reqlist
->used
; i
++) {
4609 struct helpserv_request
*req
= reqlist
->list
[i
];
4610 struct helpserv_bot
*hs
=req
->hs
;
4612 if (!old_handle
|| hs
->persist_types
[PERSIST_T_REQUEST
] == PERSIST_PART
|| hs
->persist_types
[PERSIST_T_REQUEST
] == PERSIST_QUIT
) {
4613 /* The request needs to be assigned to the new handle; either it
4614 * only persists until part/quit (so it makes sense to keep it as
4615 * close to the user as possible, and if it's made persistent later
4616 * then it's attached to the new handle) or there is no old handle.
4619 req
->handle
= user
->handle_info
;
4621 req
->parent_hand_list
= hand_reqlist
;
4622 helpserv_reqlist_append(hand_reqlist
, req
);
4624 if (oldhand_reqlist
) {
4625 if (oldhand_reqlist
->used
== 1) {
4626 dict_remove(helpserv_reqs_byhand_dict
, old_handle
->handle
);
4627 oldhand_reqlist
= NULL
;
4629 helpserv_reqlist_remove(oldhand_reqlist
, req
);
4634 char buf
[CHANNELLEN
+ 14];
4636 if (hs
->persist_types
[PERSIST_T_REQUEST
] == PERSIST_PART
) {
4637 sprintf(buf
, "part channel %s", hs
->helpchan
->name
);
4639 strcpy(buf
, "quit irc");
4642 helpserv_msguser(user
, "HSMSG_REQ_AUTH_MOVED", user
->handle_info
->handle
, hs
->helpchan
->name
, req
->id
, old_handle
->handle
, buf
);
4643 if (req
->helper
&& (hs
->notify
>= NOTIFY_HANDLE
))
4644 helpserv_notify(req
->helper
, "HSMSG_NOTIFY_HAND_MOVE", req
->id
, user
->handle_info
->handle
, old_handle
->handle
);
4646 if (req
->helper
&& (hs
->notify
>= NOTIFY_HANDLE
))
4647 helpserv_notify(req
->helper
, "HSMSG_NOTIFY_HAND_AUTH", req
->id
, user
->nick
, user
->handle_info
->handle
);
4651 req
->parent_nick_list
= NULL
;
4652 /* Would rather not mess with the list while iterating through
4654 helpserv_reqlist_append(dellist
, req
);
4656 helpserv_msguser(user
, "HSMSG_REQ_AUTH_STUCK", user
->handle_info
->handle
, hs
->helpchan
->name
, req
->id
, old_handle
->handle
);
4657 if (req
->helper
&& (hs
->notify
>= NOTIFY_HANDLE
))
4658 helpserv_notify(req
->helper
, "HSMSG_NOTIFY_HAND_STUCK", req
->id
, user
->nick
, user
->handle_info
->handle
, old_handle
->handle
);
4663 if (dellist
->used
) {
4664 if (dellist
->used
== reqlist
->used
) {
4665 dict_remove(helpserv_reqs_bynick_dict
, user
->nick
);
4667 for (i
=0; i
< dellist
->used
; i
++)
4668 helpserv_reqlist_remove(reqlist
, dellist
->list
[i
]);
4671 helpserv_reqlist_free(dellist
);
4674 for (i
=0; i
< user
->channels
.used
; i
++)
4675 associate_requests_bychan(user
->channels
.list
[i
]->channel
, user
, 0);
4679 /* Disassociate all requests from the handle. If any have req->user == NULL
4680 * then give them to the user doing the unregistration (if not an oper/helper)
4681 * otherwise the first nick it finds authed (it lets them know about this). If
4682 * there are no users authed to the handle online, the requests are lost. This
4683 * may result in the user having >1 request/bot, and messages go to the most
4684 * recently opened request.
4686 * Also, remove the user from all bots that it has access in.
4687 * helpserv_del_user() will take care of unassigning the requests. */
4688 static void handle_nickserv_unreg(struct userNode
*user
, struct handle_info
*handle
, UNUSED_ARG(void *extra
)) {
4689 struct helpserv_reqlist
*hand_reqlist
;
4690 struct helpserv_userlist
*userlist
;
4692 const int from_opserv
= 0; /* for helpserv_notice */
4693 struct helpserv_bot
*hs
; /* for helpserv_notice */
4695 if ((userlist
= dict_find(helpserv_users_byhand_dict
, handle
->handle
, NULL
))) {
4698 /* Each time helpserv_del_user is called, that entry is going to be
4699 * taken out of userlist... so this should cope with that */
4700 for (i
=0; i
< n
; i
++) {
4701 struct helpserv_user
*hs_user
=userlist
->list
[0];
4702 helpserv_del_user(hs_user
->hs
, hs_user
);
4706 if (!(hand_reqlist
= dict_find(helpserv_reqs_byhand_dict
, handle
->handle
, NULL
))) {
4710 n
= hand_reqlist
->used
;
4711 for (i
=0; i
< n
; i
++) {
4712 struct helpserv_request
*req
=hand_reqlist
->list
[0];
4716 req
->parent_hand_list
= NULL
;
4717 helpserv_reqlist_remove(hand_reqlist
, req
);
4718 if (user
&& req
->helper
&& (hs
->notify
>= NOTIFY_HANDLE
))
4719 helpserv_notify(req
->helper
, "HSMSG_NOTIFY_HAND_UNREG", req
->id
, handle
->handle
, user
->nick
);
4723 /* This is probably an expire. Silently remove everything. */
4726 if (req
->helper
&& (hs
->notify
>= NOTIFY_DROP
))
4727 helpserv_notify(req
->helper
, "HSMSG_NOTIFY_REQ_DROP_UNREGGED", req
->id
, req
->handle
->handle
);
4728 sprintf(buf
, "%lu", req
->id
);
4729 helpserv_log_request(req
, "Account unregistered");
4730 dict_remove(req
->hs
->requests
, buf
);
4731 } else if (user
->handle_info
== handle
) {
4733 if (!(req
->parent_nick_list
= dict_find(helpserv_reqs_bynick_dict
, user
->nick
, NULL
))) {
4734 req
->parent_nick_list
= helpserv_reqlist_alloc();
4735 dict_insert(helpserv_reqs_bynick_dict
, user
->nick
, req
->parent_nick_list
);
4737 helpserv_reqlist_append(req
->parent_nick_list
, req
);
4739 if (hs
->persist_types
[PERSIST_T_REQUEST
] == PERSIST_CLOSE
)
4740 helpserv_msguser(req
->user
, "HSMSG_REQ_WARN_UNREG", handle
->handle
, hs
->helpchan
->name
, req
->id
);
4742 if (handle
->users
) {
4743 req
->user
= handle
->users
;
4745 if (!(req
->parent_nick_list
= dict_find(helpserv_reqs_bynick_dict
, req
->user
->nick
, NULL
))) {
4746 req
->parent_nick_list
= helpserv_reqlist_alloc();
4747 dict_insert(helpserv_reqs_bynick_dict
, req
->user
->nick
, req
->parent_nick_list
);
4749 helpserv_reqlist_append(req
->parent_nick_list
, req
);
4751 helpserv_msguser(req
->user
, "HSMSG_REQ_ASSIGNED_UNREG", handle
->handle
, hs
->helpchan
->name
, req
->id
);
4752 if (req
->helper
&& (hs
->notify
>= NOTIFY_USER
))
4753 helpserv_notify(req
->helper
, "HSMSG_NOTIFY_USER_MOVE", req
->id
, handle
->handle
, req
->user
->nick
);
4757 helpserv_notice(user
, "HSMSG_REQ_DROPPED_UNREG", handle
->handle
, hs
->helpchan
->name
, req
->id
);
4758 if (req
->helper
&& (hs
->notify
>= NOTIFY_DROP
))
4759 helpserv_notify(req
->helper
, "HSMSG_NOTIFY_REQ_DROP_UNREGGED", req
->id
, req
->handle
->handle
);
4760 sprintf(buf
, "%lu", req
->id
);
4761 helpserv_log_request(req
, "Account unregistered");
4762 dict_remove(req
->hs
->requests
, buf
);
4768 dict_remove(helpserv_reqs_byhand_dict
, handle
->handle
);
4771 static void handle_nickserv_merge(struct userNode
*user
, struct handle_info
*handle_to
, struct handle_info
*handle_from
, UNUSED_ARG(void *extra
)) {
4772 struct helpserv_reqlist
*reqlist_from
, *reqlist_to
;
4775 reqlist_to
= dict_find(helpserv_reqs_byhand_dict
, handle_to
->handle
, NULL
);
4777 if ((reqlist_from
= dict_find(helpserv_reqs_byhand_dict
, handle_from
->handle
, NULL
))) {
4778 for (i
=0; i
< reqlist_from
->used
; i
++) {
4779 struct helpserv_request
*req
=reqlist_from
->list
[i
];
4782 reqlist_to
= helpserv_reqlist_alloc();
4783 dict_insert(helpserv_reqs_byhand_dict
, handle_to
->handle
, reqlist_to
);
4785 req
->parent_hand_list
= reqlist_to
;
4786 req
->handle
= handle_to
;
4787 helpserv_reqlist_append(reqlist_to
, req
);
4789 dict_remove(helpserv_reqs_byhand_dict
, handle_from
->handle
);
4793 for (i
=0; i
< reqlist_to
->used
; i
++) {
4794 struct helpserv_request
*req
=reqlist_to
->list
[i
];
4796 if (req
->helper
&& (req
->hs
->notify
>= NOTIFY_HANDLE
)) {
4797 helpserv_notify(req
->helper
, "HSMSG_NOTIFY_HAND_MERGE", req
->id
, handle_to
->handle
, handle_from
->handle
, user
->nick
);
4803 static void handle_nickserv_allowauth(struct userNode
*user
, struct userNode
*target
, struct handle_info
*handle
, UNUSED_ARG(void *extra
)) {
4804 struct helpserv_reqlist
*reqlist
;
4807 if ((reqlist
= dict_find(helpserv_reqs_bynick_dict
, target
->nick
, NULL
))) {
4808 for (i
=0; i
< reqlist
->used
; i
++) {
4809 struct helpserv_request
*req
=reqlist
->list
[i
];
4811 if (req
->helper
&& (req
->hs
->notify
>= NOTIFY_HANDLE
)) {
4813 helpserv_notify(req
->helper
, "HSMSG_NOTIFY_ALLOWAUTH", req
->id
, target
->nick
, user
->nick
, handle
->handle
);
4815 helpserv_notify(req
->helper
, "HSMSG_NOTIFY_UNALLOWAUTH", req
->id
, target
->nick
, user
->nick
);
4822 static void handle_nickserv_failpw(struct userNode
*user
, struct handle_info
*handle
, UNUSED_ARG(void *extra
)) {
4823 struct helpserv_reqlist
*reqlist
;
4826 if ((reqlist
= dict_find(helpserv_reqs_bynick_dict
, user
->nick
, NULL
))) {
4827 for (i
=0; i
< reqlist
->used
; i
++) {
4828 struct helpserv_request
*req
=reqlist
->list
[i
];
4829 if (req
->helper
&& (req
->hs
->notify
>= NOTIFY_HANDLE
))
4830 helpserv_notify(req
->helper
, "HSMSG_NOTIFY_FAILPW", req
->id
, user
->nick
, handle
->handle
);
4835 static time_t helpserv_next_stats(time_t after_when
) {
4836 struct tm
*timeinfo
= localtime(&after_when
);
4838 /* This works because mktime(3) says it will accept out-of-range values
4839 * and fix them for us. tm_wday and tm_yday are ignored. */
4840 timeinfo
->tm_mday
++;
4842 /* We want to run stats at midnight (local time). */
4843 timeinfo
->tm_sec
= timeinfo
->tm_min
= timeinfo
->tm_hour
= 0;
4845 return mktime(timeinfo
);
4848 /* If data != NULL, then don't add to the timeq */
4849 static void helpserv_run_stats(time_t when
) {
4851 struct helpserv_bot
*hs
;
4852 struct helpserv_user
*hs_user
;
4854 dict_iterator_t it
, it2
;
4856 last_stats_update
= when
;
4857 localtime_r(&when
, &when_s
);
4858 for (it
=dict_first(helpserv_bots_dict
); it
; it
=iter_next(it
)) {
4861 for (it2
=dict_first(hs
->users
); it2
; it2
=iter_next(it2
)) {
4862 hs_user
= iter_data(it2
);
4864 /* Skip the helper if it's not their week-start day. */
4865 if (hs_user
->week_start
!= when_s
.tm_wday
)
4868 /* Adjust their credit if they are in-channel at rollover. */
4869 if (hs_user
->join_time
) {
4870 hs_user
->time_per_week
[0] += when
- hs_user
->join_time
;
4871 hs_user
->time_per_week
[4] += when
- hs_user
->join_time
;
4872 hs_user
->join_time
= when
;
4875 /* Shift everything */
4876 for (i
=3; i
> 0; i
--) {
4877 hs_user
->time_per_week
[i
] = hs_user
->time_per_week
[i
-1];
4878 hs_user
->picked_up
[i
] = hs_user
->picked_up
[i
-1];
4879 hs_user
->closed
[i
] = hs_user
->closed
[i
-1];
4880 hs_user
->reassigned_from
[i
] = hs_user
->reassigned_from
[i
-1];
4881 hs_user
->reassigned_to
[i
] = hs_user
->reassigned_to
[i
-1];
4884 /* Reset it for this week */
4885 hs_user
->time_per_week
[0] = hs_user
->picked_up
[0] = hs_user
->closed
[0] = hs_user
->reassigned_from
[0] = hs_user
->reassigned_to
[0] = 0;
4890 static void helpserv_timed_run_stats(UNUSED_ARG(void *data
)) {
4891 helpserv_run_stats(now
);
4892 timeq_add(helpserv_next_stats(now
), helpserv_timed_run_stats
, data
);
4896 helpserv_define_option(const char *name
, helpserv_option_func_t
*func
) {
4897 dict_insert(helpserv_option_dict
, name
, func
);
4900 static void helpserv_db_cleanup(UNUSED_ARG(void *extra
)) {
4902 unreg_part_func(handle_part
, NULL
);
4903 unreg_del_user_func(handle_quit
, NULL
);
4904 close_helpfile(helpserv_helpfile
);
4905 dict_delete(helpserv_func_dict
);
4906 dict_delete(helpserv_option_dict
);
4907 dict_delete(helpserv_usercmd_dict
);
4908 dict_delete(helpserv_bots_dict
);
4909 dict_delete(helpserv_bots_bychan_dict
);
4910 dict_delete(helpserv_reqs_bynick_dict
);
4911 dict_delete(helpserv_reqs_byhand_dict
);
4912 dict_delete(helpserv_users_byhand_dict
);
4918 int helpserv_init() {
4919 HS_LOG
= log_register_type("HelpServ", "file:helpserv.log");
4920 conf_register_reload(helpserv_conf_read
);
4922 helpserv_func_dict
= dict_new();
4923 dict_set_free_data(helpserv_func_dict
, free
);
4924 helpserv_define_func("HELP", cmd_help
, HlNone
, CMD_NOT_OVERRIDE
|CMD_IGNORE_EVENT
);
4925 helpserv_define_func("LIST", cmd_list
, HlTrial
, CMD_NEED_BOT
|CMD_IGNORE_EVENT
);
4926 helpserv_define_func("NEXT", cmd_next
, HlTrial
, CMD_NEED_BOT
|CMD_NEVER_FROM_OPSERV
);
4927 helpserv_define_func("PICKUP", cmd_pickup
, HlTrial
, CMD_NEED_BOT
|CMD_NEVER_FROM_OPSERV
);
4928 helpserv_define_func("REASSIGN", cmd_reassign
, HlManager
, CMD_NEED_BOT
|CMD_NEVER_FROM_OPSERV
);
4929 helpserv_define_func("CLOSE", cmd_close
, HlTrial
, CMD_NEED_BOT
|CMD_NEVER_FROM_OPSERV
);
4930 helpserv_define_func("SHOW", cmd_show
, HlTrial
, CMD_NEED_BOT
|CMD_IGNORE_EVENT
);
4931 helpserv_define_func("ADDNOTE", cmd_addnote
, HlTrial
, CMD_NEED_BOT
);
4932 helpserv_define_func("ADDOWNER", cmd_addowner
, HlOper
, CMD_NEED_BOT
|CMD_FROM_OPSERV_ONLY
);
4933 helpserv_define_func("DELOWNER", cmd_deluser
, HlOper
, CMD_NEED_BOT
|CMD_FROM_OPSERV_ONLY
);
4934 helpserv_define_func("ADDTRIAL", cmd_addtrial
, HlManager
, CMD_NEED_BOT
);
4935 helpserv_define_func("ADDHELPER", cmd_addhelper
, HlManager
, CMD_NEED_BOT
);
4936 helpserv_define_func("ADDMANAGER", cmd_addmanager
, HlOwner
, CMD_NEED_BOT
);
4937 helpserv_define_func("GIVEOWNERSHIP", cmd_giveownership
, HlOwner
, CMD_NEED_BOT
);
4938 helpserv_define_func("DELUSER", cmd_deluser
, HlManager
, CMD_NEED_BOT
);
4939 helpserv_define_func("HELPERS", cmd_helpers
, HlNone
, CMD_NEED_BOT
);
4940 helpserv_define_func("WLIST", cmd_wlist
, HlNone
, CMD_NEED_BOT
);
4941 helpserv_define_func("MLIST", cmd_mlist
, HlNone
, CMD_NEED_BOT
);
4942 helpserv_define_func("HLIST", cmd_hlist
, HlNone
, CMD_NEED_BOT
);
4943 helpserv_define_func("TLIST", cmd_tlist
, HlNone
, CMD_NEED_BOT
);
4944 helpserv_define_func("CLVL", cmd_clvl
, HlManager
, CMD_NEED_BOT
);
4945 helpserv_define_func("PAGE", cmd_page
, HlTrial
, CMD_NEED_BOT
);
4946 helpserv_define_func("SET", cmd_set
, HlHelper
, CMD_NEED_BOT
);
4947 helpserv_define_func("STATS", cmd_stats
, HlTrial
, CMD_NEED_BOT
);
4948 helpserv_define_func("STATSREPORT", cmd_statsreport
, HlManager
, CMD_NEED_BOT
);
4949 helpserv_define_func("UNREGISTER", cmd_unregister
, HlOwner
, CMD_NEED_BOT
);
4950 helpserv_define_func("READHELP", cmd_readhelp
, HlOper
, CMD_FROM_OPSERV_ONLY
);
4951 helpserv_define_func("REGISTER", cmd_register
, HlOper
, CMD_FROM_OPSERV_ONLY
);
4952 helpserv_define_func("MOVE", cmd_move
, HlOper
, CMD_FROM_OPSERV_ONLY
|CMD_NEED_BOT
);
4953 helpserv_define_func("BOTS", cmd_bots
, HlOper
, CMD_FROM_OPSERV_ONLY
|CMD_IGNORE_EVENT
);
4954 helpserv_define_func("EXPIRE", cmd_expire
, HlOper
, CMD_FROM_OPSERV_ONLY
);
4955 helpserv_define_func("SUSPEND", cmd_suspend
, HlOper
, CMD_FROM_OPSERV_ONLY
);
4956 helpserv_define_func("UNSUSPEND", cmd_unsuspend
, HlOper
, CMD_FROM_OPSERV_ONLY
);
4957 helpserv_define_func("WEEKSTART", cmd_weekstart
, HlTrial
, CMD_NEED_BOT
);
4959 helpserv_option_dict
= dict_new();
4960 helpserv_define_option("PAGETARGET", opt_pagetarget_command
);
4961 helpserv_define_option("ALERTPAGETARGET", opt_pagetarget_alert
);
4962 helpserv_define_option("STATUSPAGETARGET", opt_pagetarget_status
);
4963 helpserv_define_option("PAGE", opt_pagetype
);
4964 helpserv_define_option("PAGETYPE", opt_pagetype
);
4965 helpserv_define_option("ALERTPAGETYPE", opt_alert_page_type
);
4966 helpserv_define_option("STATUSPAGETYPE", opt_status_page_type
);
4967 helpserv_define_option("GREETING", opt_greeting
);
4968 helpserv_define_option("REQOPENED", opt_req_opened
);
4969 helpserv_define_option("REQASSIGNED", opt_req_assigned
);
4970 helpserv_define_option("REQCLOSED", opt_req_closed
);
4971 helpserv_define_option("IDLEDELAY", opt_idle_delay
);
4972 helpserv_define_option("WHINEDELAY", opt_whine_delay
);
4973 helpserv_define_option("WHINEINTERVAL", opt_whine_interval
);
4974 helpserv_define_option("EMPTYINTERVAL", opt_empty_interval
);
4975 helpserv_define_option("STALEDELAY", opt_stale_delay
);
4976 helpserv_define_option("REQPERSIST", opt_request_persistence
);
4977 helpserv_define_option("HELPERPERSIST", opt_helper_persistence
);
4978 helpserv_define_option("NOTIFICATION", opt_notification
);
4979 helpserv_define_option("REQMAXLEN", opt_req_maxlen
);
4980 helpserv_define_option("IDWRAP", opt_id_wrap
);
4981 helpserv_define_option("PRIVMSGONLY", opt_privmsg_only
);
4982 helpserv_define_option("REQONJOIN", opt_req_on_join
);
4983 helpserv_define_option("AUTOVOICE", opt_auto_voice
);
4984 helpserv_define_option("AUTODEVOICE", opt_auto_devoice
);
4985 helpserv_define_option("JOINTOTAL", opt_join_total
);
4986 helpserv_define_option("ALERTNEW", opt_alert_new
);
4988 helpserv_usercmd_dict
= dict_new();
4989 dict_insert(helpserv_usercmd_dict
, "CLOSEREQ", usercmd_close
);
4990 dict_insert(helpserv_usercmd_dict
, "HELP", usercmd_help
);
4991 dict_insert(helpserv_usercmd_dict
, "HELPER", usercmd_helper
);
4992 dict_insert(helpserv_usercmd_dict
, "SHOWREQ", usercmd_show
);
4993 dict_insert(helpserv_usercmd_dict
, "WAIT", usercmd_wait
);
4995 helpserv_bots_dict
= dict_new();
4996 dict_set_free_data(helpserv_bots_dict
, helpserv_free_bot
);
4998 helpserv_bots_bychan_dict
= dict_new();
4999 dict_set_free_data(helpserv_bots_bychan_dict
, helpserv_botlist_free
);
5001 helpserv_reqs_bynick_dict
= dict_new();
5002 dict_set_free_data(helpserv_reqs_bynick_dict
, helpserv_reqlist_free
);
5003 helpserv_reqs_byhand_dict
= dict_new();
5004 dict_set_free_data(helpserv_reqs_byhand_dict
, helpserv_reqlist_free
);
5006 helpserv_users_byhand_dict
= dict_new();
5007 dict_set_free_data(helpserv_users_byhand_dict
, helpserv_userlist_free
);
5009 saxdb_register("HelpServ", helpserv_saxdb_read
, helpserv_saxdb_write
);
5010 helpserv_helpfile_read();
5012 /* Make up for downtime... though this will only really affect the
5014 if (last_stats_update
&& (helpserv_next_stats(last_stats_update
) < now
)) {
5015 time_t statsrun
= last_stats_update
;
5016 while ((statsrun
= helpserv_next_stats(statsrun
)) < now
)
5017 helpserv_run_stats(statsrun
);
5019 timeq_add(helpserv_next_stats(now
), helpserv_timed_run_stats
, NULL
);
5021 reg_join_func(handle_join
, NULL
);
5022 reg_part_func(handle_part
, NULL
); /* also deals with kick */
5023 reg_nick_change_func(handle_nickchange
, NULL
);
5024 reg_del_user_func(handle_quit
, NULL
);
5026 reg_auth_func(handle_nickserv_auth
, NULL
);
5027 reg_handle_rename_func(handle_nickserv_rename
, NULL
);
5028 reg_unreg_func(handle_nickserv_unreg
, NULL
);
5029 reg_allowauth_func(handle_nickserv_allowauth
, NULL
);
5030 reg_failpw_func(handle_nickserv_failpw
, NULL
);
5031 reg_handle_merge_func(handle_nickserv_merge
, NULL
);
5033 reg_exit_func(helpserv_db_cleanup
, NULL
);
5035 helpserv_module
= module_register("helpserv", HS_LOG
, HELPSERV_HELPFILE_NAME
, helpserv_expand_variable
);
5036 modcmd_register(helpserv_module
, "helpserv", cmd_helpserv
, 1, MODCMD_REQUIRE_AUTHED
|MODCMD_NO_LOG
|MODCMD_NO_DEFAULT_BIND
, "level", "800", NULL
);
5037 message_register_table(msgtab
);
5042 helpserv_finalize(void) {