$help!
[halbot.git] / reminders.py
bloba774924ef5671975b59c68b2dfb8131b13f552f0
1 from __future__ import generators
2 import re, time, os
3 from sets import Set
4 from dateutil.parser import parse as raw_time_parser
6 import whois, perms
7 from common import get_timestamp, resize, extract_my_db, real_where, safeint, \
8 extract
9 from basic_commands import reply
10 from connection import hal
11 from globals import reminder_dbs, subscription_dbs, scheduler, private, \
12 commands, unlower
13 from safety import safe
14 from ircbot import IRCDict, irc_lower
16 os.environ['TZ'] = "GMT"
17 time.tzset()
19 def parse_time(time_string):
20 return time.mktime(raw_time_parser(time_string).timetuple())
22 def get_reminder_db(who, where):
23 return extract_my_db(reminder_dbs, who, where, lambda: [])
25 def get_subscription_db(who, where):
26 return extract_my_db(subscription_dbs, who, where, IRCDict)
28 def describe_reminder(reminder):
29 (when, message, groups, repeat) = reminder
30 description = '"%s" at %s.' % (message, get_timestamp(when))
31 if groups:
32 groupstr = ",".join(groups)
33 description += " Groups: " + groupstr
34 if repeat:
35 hours = repeat / 3600
36 minutes = str(repeat / 60 % 60).zfill(2)
37 description += " Repeating every %d:%s." % (hours, minutes)
38 return description
40 reminder_res = {"add": re.compile(r'^"((?:[^"]|\")*)" +at +([^"]*)$',
41 re.IGNORECASE),
42 "set": re.compile(r'^(\d+) +(\w+) +(.*)$', re.IGNORECASE),
43 "repeat h+:mm": re.compile(r'^(\d+) +every +(\d+):(\d\d)$',
44 re.IGNORECASE),
45 "repeat x units": re.compile(r'^(\d+) +every +(\d+) +' +
46 r'(week|day|hour|minute)s?$',
47 re.IGNORECASE),
48 "repeat off": re.compile(r'^(\d+) +(off|disable|stop|never|none'
49 +r'|remove)$',
50 re.IGNORECASE)
53 time_units = {"minute": 60,
54 "hour": 60*60,
55 "day": 60*60*24,
56 "week": 60*60*24*7}
58 def reminder_index(who, where, reminders, whichstr):
59 which = safeint(whichstr)
60 if which < 1:
61 reply(who, where, "Which reminder?")
62 return -1
63 if which > len(reminders):
64 reply(who, where, "I don't have that many reminders.")
65 return -1
66 return which - 1
68 def fire_reminder(where, reminders, reminder):
69 (when, message, groups, repeat) = reminder
70 curtime = time.time()
71 if when < (curtime - 60):
72 delay = curtime - when
73 reply('', where, "Reminder delayed by %d minutes: %s" % (delay // 60,
74 message), all=True)
75 else:
76 delay = 0
77 reply('', where, message, all=True)
78 if where in hal.channels:
79 subscriptions = get_subscription_db("", where)
80 please_inform_us = Set()
81 for group in groups:
82 if group in subscriptions:
83 please_inform_us = please_inform_us.union(subscriptions[group])
84 informable = whois.voice_or_better(where)
85 inform_us = informable.intersection(please_inform_us)
86 if inform_us:
87 poke_list = [unlower.get(who, who) for who in inform_us]
88 poke_str = ", ".join(poke_list)
89 reply("", where, poke_str + ", that one's for you!")
90 if repeat:
91 if repeat < delay:
92 skip = delay // repeat
93 reply('', where, "(skipping %d delayed repititions)" % skip, all=True)
94 real_repeat = (skip + 1) * repeat
95 else:
96 real_repeat = repeat
97 reminder[0] += real_repeat
98 schedule_reminder(where, reminders, reminder)
99 else:
100 reminders.remove(reminder)
101 fire_reminder = safe(fire_reminder)
103 def schedule_reminder(where, reminders, reminder):
104 scheduler.enterabs(reminder[0], 0, fire_reminder, (where, reminders, reminder))
106 def add_reminder(who, where, reminders, reminder):
107 if len(reminders) < 10 or where == "#casualgameplay":
108 reminders.append(reminder)
109 schedule_reminder(real_where(who, where), reminders, reminder)
110 reply(who, where, "Done.")
111 else:
112 reply(who, where,
113 "I'm sorry, %s, I'm afraid I can't do that. You're limited to 10."
114 % who)
116 def cancel_reminder(where, reminders, reminder):
117 try:
118 scheduler.cancel((reminder[0], 0, fire_reminder, (where, reminders, reminder)))
119 except ValueError:
120 pass
122 def reschedule(who, where, reminders, reminder, when):
123 rwhere = real_where(who, where)
124 cancel_reminder(rwhere, reminders, reminder)
125 reminder[0] = when
126 schedule_reminder(rwhere, reminders, reminder)
128 def reminder(who, where, reminder_args):
129 "$reminder is so complex it has its own help system: $reminder help"
130 (command, args) = resize(reminder_args.split(" ", 1), 2)
131 reminders = get_reminder_db(who, where)
132 if not command:
133 reply(who, where, "Available reminder commands: add del list set repeat help")
134 elif command in ("add", "new", "create", "make"):
135 parsed = reminder_res["add"].search(args)
136 if not parsed:
137 reply(who, where, "I don't understand that. Try $reminder help add")
138 return
139 (msg, whenstr) = parsed.groups()
140 try:
141 when = parse_time(whenstr)
142 except ValueError:
143 reply(who, where, "I don't understand that time.")
144 return
145 reminder = [when, msg, [], 0]
146 add_reminder(who, where, reminders, reminder)
147 elif command in ("del", "delete", "remove", "rm"):
148 which = reminder_index(who, where, reminders, args)
149 if which == -1:
150 return
151 reminder = reminders[which]
152 del reminders[which]
153 cancel_reminder(real_where(who, where), reminders, reminder)
154 reply(who, where, "Deleted reminder %d: %s" %
155 (which+1, describe_reminder(reminder)))
156 elif command in ("list", "show"):
157 if len(reminders) > 5:
158 reply(who, where, "Listing %d reminders in private " % len(reminders)
159 + "to avoid channel clutter")
160 where = private
161 if not reminders:
162 reply(who, where, "No reminders.")
163 index = 1
164 for reminder in reminders:
165 reply(who, where, "%2d. %s" % (index, describe_reminder(reminder)))
166 index += 1
167 elif command in ("change", "alter", "set"):
168 parsed = reminder_res["set"].search(args)
169 if not parsed:
170 reply(who, where, "I don't understand that. Try $reminder help set")
171 return
172 (whichstr, property, value) = parsed.groups()
173 which = reminder_index(who, where, reminders, whichstr)
174 if which == -1:
175 return
176 reminder = reminders[which]
177 lproperty = property.lower()
178 if lproperty in ("message", "msg"):
179 reminder[1] = value
180 elif lproperty == "time":
181 try:
182 when = parse_time(value)
183 reschedule(who, where, reminders, reminder, when)
184 except ValueError:
185 reply(who, where, "I don't understand that time.")
186 return
187 elif lproperty in ("group", "groups"):
188 groups = value.split(",")
189 groups = [group.strip() for group in groups]
190 reminder[2] = groups
191 reply(who, where, "Done.")
192 elif command == "repeat":
193 parse_hm = reminder_res["repeat h+:mm"].search(args)
194 parse_units = reminder_res["repeat x units"].search(args)
195 parse_off = reminder_res["repeat off"].search(args)
196 parsed = parse_hm or parse_units or parse_off
197 if parsed:
198 which = reminder_index(who, where, reminders, parsed.groups()[0])
199 if which == -1:
200 return
201 reminder = reminders[which]
202 else:
203 reply(who, where, "I don't understand that. Try $reminder help repeat")
204 return
205 if parse_hm:
206 (whichstr, hourstr, minutestr) = parsed.groups()
207 hours = safeint(hourstr)
208 if hours < 0:
209 reply(who, where, "Bad number of hours.")
210 return
211 minutes = safeint(minutestr)
212 # Mathematicians, read at your own peril.
213 if 59 < minutes < 0:
214 reply(who, where, "Bad number of minutes.")
215 return
216 if hours == minutes == 0:
217 reply(who, where, "Repeating continuously sounds like a bad idea.")
218 return
219 reminder[3] = 60 * (minutes + 60 * hours)
220 reply(who, where,
221 "Reminder number %d now repeating every %d hours and %d minutes."
222 % (which+1, hours, minutes))
223 elif parse_units:
224 (whichstr, numstr, unit) = parsed.groups()
225 unit = unit.lower()
226 num = safeint(numstr)
227 if numstr < 1:
228 reply(who, where, "Bad number of %ss." % unit)
229 return
230 if unit not in time_units:
231 reply(who, where, "I don't know that unit.")
232 return
233 reminder[3] = num * time_units[unit]
234 reply(who, where, "Reminder number %d now repeating every %d %ss."
235 % (which+1, num, unit))
236 else: # parse_off
237 reminder[3] = 0
238 reply(who, where, "Repeating disabled on reminder %d." % which+1)
240 elif command == "help":
241 if not args:
242 reply(who, where, "To get help on a specific command, type $reminder " \
243 + "help <command> (e.g. $reminder help add). " \
244 + "Available reminder commands: add del list set "\
245 + "repeat help")
246 elif args in ("add", "new", "create", "make"):
247 reply(who, where, '$reminder add "<msg>" at <time>: Adds a new ' \
248 + "reminder. When <time> comes, I will say <msg>. A" \
249 + " variety of time formats are supported. Use $time" \
250 + " to get my current time for comparison.")
251 elif args in ("del", "delete", "remove", "rm"):
252 reply(who, where, "$reminder del <reminder>: Delete reminder number " \
253 + "<reminder>.");
254 elif args in ("change", "alter", "set"):
255 reply(who, where, "$reminder set <reminder> <property> <value>: Change" \
256 + " <property> of reminder number <reminder> to " \
257 + "<value>. Availale properties: message, time, group")
258 elif args in ("list", "show"):
259 reply(who, where, "$reminder list: Print a list of reminders. If there" \
260 + " are more than 5, I will reply in private.")
261 elif args == "repeat":
262 reply(who, where, "$reminder repeat <reminder> every <interval>: " \
263 + "Reminder number <reminder> will be repeated every " \
264 + "<interval>. Use $reminder repeat <reminder> off to"\
265 + " stop repeating a reminder.")
266 elif args == "help":
267 reply(who, where, "$reminder help <command>: get help on <command>. " \
268 + "You know, like you just did.")
269 else:
270 reply(who, where, "I don't understand that.")
271 commands['reminder'] = (perms.admin, reminder)
272 commands['reminders'] = (perms.admin, reminder)
274 def reminders_list(who, where, args):
275 "$reminders-list: Asks Hal for the current list of reminders."
276 reminder(who, where, "list")
277 commands['reminders-list'] = (perms.voice, reminders_list)
279 def subscribe(who, where, args):
280 "$subscribe <groups>: Subscribes you to the (comma-separated) list of reminder groups."
281 if where == private:
282 reply(who, where, "Don't you think subscribing to reminders that are" +
283 " already in PM is a bit... pointless?")
284 return
286 unlower[irc_lower(who)] = who
287 subscriptions = get_subscription_db(who, where)
288 targets = [target.strip() for target in args.split(",")]
289 new_subscription = False
290 for target in targets:
291 subscribers = extract(subscriptions, target, Set)
292 user = irc_lower(who)
293 if user not in subscribers:
294 subscribers.add(user)
295 new_subscription = True
296 if new_subscription:
297 reply(who, where, "Done.")
298 else:
299 reply(who, where, "You're already subscribed.")
300 commands['subscribe'] = (perms.voice, subscribe)
302 def unsubscribe(who, where, args):
303 "$unsubscribe <groups>: Unsubscribes you from the (comma-separated) list of reminder groups."
304 subscriptions = get_subscription_db(who, where)
305 targets = [target.strip() for target in args.split(",")]
306 lost_subscription = False
307 for target in targets:
308 subscribers = extract(subscriptions, target, Set)
309 user = irc_lower(who)
310 if user in subscribers:
311 subscribers.discard(user)
312 lost_subscription = True
313 if lost_subscription:
314 reply(who, where, "Done.")
315 else:
316 reply(who, where, "You weren't subscribed.")
317 commands['unsubscribe'] = (perms.voice, unsubscribe)
319 def run_reminders():
320 for where in reminder_dbs.keys():
321 db = reminder_dbs[where]
322 for reminder in db:
323 schedule_reminder(where, db, reminder)
324 while True: # Looping in case of errors.
325 safe(scheduler.run_forever)()