1 from __future__
import generators
4 from dateutil
.parser
import parse
as raw_time_parser
7 from common
import get_timestamp
, resize
, extract_my_db
, real_where
, safeint
, \
9 from basic_commands
import reply
10 from connection
import hal
11 from globals import reminder_dbs
, subscription_dbs
, scheduler
, private
, \
12 commands
, unlower
, poke_via_msg
, yes
, no
13 from safety
import safe
14 from ircbot
import IRCDict
, irc_lower
16 os
.environ
['TZ'] = "GMT"
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
))
32 groupstr
= ",".join(groups
)
33 description
+= " Groups: " + groupstr
36 minutes
= str(repeat
/ 60 % 60).zfill(2)
37 description
+= " Repeating every %d:%s." % (hours
, minutes
)
40 reminder_res
= {"add": re
.compile(r
'^"((?:[^"]|\")*)" +at +([^"]*)$',
42 "set": re
.compile(r
'^(\d+) +(\w+) +(.*)$', re
.IGNORECASE
),
43 "repeat h+:mm": re
.compile(r
'^(\d+) +every +(\d+):(\d\d)$',
45 "repeat x units": re
.compile(r
'^(\d+) +every +(\d+) +' +
46 r
'(week|day|hour|minute)s?$',
48 "repeat off": re
.compile(r
'^(\d+) +(off|disable|stop|never|none'
53 time_units
= {"minute": 60,
58 def reminder_index(who
, where
, reminders
, whichstr
):
59 which
= safeint(whichstr
)
61 reply(who
, where
, "Which reminder?")
63 if which
> len(reminders
):
64 reply(who
, where
, "I don't have that many reminders.")
68 def fire_reminder(where
, reminders
, reminder
):
69 (when
, message
, groups
, repeat
) = reminder
71 if when
< (curtime
- 60):
72 delay
= curtime
- when
73 reply('', where
, "Reminder delayed by %d minutes: %s" % (delay
// 60,
77 reply('', where
, message
, all
=True)
78 if where
in hal
.channels
:
79 subscriptions
= get_subscription_db("", where
)
80 please_inform_us
= Set()
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 poke_list
= [unlower
.get(who
, who
) for who
in inform_us
87 if not poke_via_msg
.get(who
, False)]
89 poke_str
= ", ".join(poke_list
)
90 reply("", where
, poke_str
+ ": I'm in yer reminders, informin' you.",
93 if poke_via_msg
.get(who
, False):
94 reply(who
, private
, "(%s) %s" % (where
, message
))
97 skip
= delay
// repeat
98 reply('', where
, "(skipping %d delayed repititions)" % skip
, all
=True)
99 real_repeat
= (skip
+ 1) * repeat
102 reminder
[0] += real_repeat
103 schedule_reminder(where
, reminders
, reminder
)
105 reminders
.remove(reminder
)
106 fire_reminder
= safe(fire_reminder
)
108 def schedule_reminder(where
, reminders
, reminder
):
109 scheduler
.enterabs(reminder
[0], 0, fire_reminder
, (where
, reminders
, reminder
))
111 def add_reminder(who
, where
, reminders
, reminder
):
112 if len(reminders
) < 10 or where
== "#casualgameplay":
113 reminders
.append(reminder
)
114 schedule_reminder(real_where(who
, where
), reminders
, reminder
)
115 reply(who
, where
, "Reminder %d added." % len(reminders
))
118 "I'm sorry, %s, I'm afraid I can't do that. You're limited to 10."
121 def cancel_reminder(where
, reminders
, reminder
):
123 scheduler
.cancel((reminder
[0], 0, fire_reminder
, (where
, reminders
, reminder
)))
127 def reschedule(who
, where
, reminders
, reminder
, when
):
128 rwhere
= real_where(who
, where
)
129 cancel_reminder(rwhere
, reminders
, reminder
)
131 schedule_reminder(rwhere
, reminders
, reminder
)
133 def reminder(who
, where
, reminder_args
):
134 "$reminder is so complex it has its own help system: $reminder help"
135 (command
, args
) = resize(reminder_args
.split(" ", 1), 2)
136 reminders
= get_reminder_db(who
, where
)
138 reply(who
, where
, "Available reminder commands: add del list set repeat help")
139 elif command
in ("add", "new", "create", "make"):
140 parsed
= reminder_res
["add"].search(args
)
142 reply(who
, where
, "I don't understand that. Try $reminder help add")
144 (msg
, whenstr
) = parsed
.groups()
146 when
= parse_time(whenstr
)
148 reply(who
, where
, "I don't understand that time.")
150 reminder
= [when
, msg
, [], 0]
151 add_reminder(who
, where
, reminders
, reminder
)
152 elif command
in ("del", "delete", "remove", "rm"):
153 which
= reminder_index(who
, where
, reminders
, args
)
156 reminder
= reminders
[which
]
158 cancel_reminder(real_where(who
, where
), reminders
, reminder
)
159 reply(who
, where
, "Deleted reminder %d: %s" %
160 (which
+1, describe_reminder(reminder
)))
161 elif command
in ("list", "show"):
162 if len(reminders
) > 5:
163 reply(who
, where
, "Listing %d reminders in private " % len(reminders
)
164 + "to avoid channel clutter")
167 reply(who
, where
, "No reminders.")
169 for reminder
in reminders
:
170 reply(who
, where
, "%2d. %s" % (index
, describe_reminder(reminder
)))
172 elif command
in ("change", "alter", "set"):
173 parsed
= reminder_res
["set"].search(args
)
175 reply(who
, where
, "I don't understand that. Try $reminder help set")
177 (whichstr
, property, value
) = parsed
.groups()
178 which
= reminder_index(who
, where
, reminders
, whichstr
)
181 reminder
= reminders
[which
]
182 lproperty
= property.lower()
183 if lproperty
in ("message", "msg"):
185 elif lproperty
== "time":
187 when
= parse_time(value
)
188 reschedule(who
, where
, reminders
, reminder
, when
)
190 reply(who
, where
, "I don't understand that time.")
192 elif lproperty
in ("group", "groups"):
193 groups
= value
.split(",")
194 groups
= [group
.strip() for group
in groups
]
196 reply(who
, where
, "Done.")
197 elif command
== "repeat":
198 parse_hm
= reminder_res
["repeat h+:mm"].search(args
)
199 parse_units
= reminder_res
["repeat x units"].search(args
)
200 parse_off
= reminder_res
["repeat off"].search(args
)
201 parsed
= parse_hm
or parse_units
or parse_off
203 which
= reminder_index(who
, where
, reminders
, parsed
.groups()[0])
206 reminder
= reminders
[which
]
208 reply(who
, where
, "I don't understand that. Try $reminder help repeat")
211 (whichstr
, hourstr
, minutestr
) = parsed
.groups()
212 hours
= safeint(hourstr
)
214 reply(who
, where
, "Bad number of hours.")
216 minutes
= safeint(minutestr
)
217 if not (0 <= minutes
<= 59):
218 reply(who
, where
, "Bad number of minutes.")
220 if hours
== minutes
== 0:
221 reply(who
, where
, "Repeating continuously sounds like a bad idea.")
223 reminder
[3] = 60 * (minutes
+ 60 * hours
)
225 "Reminder number %d now repeating every %d hours and %d minutes."
226 % (which
+1, hours
, minutes
))
228 (whichstr
, numstr
, unit
) = parsed
.groups()
230 num
= safeint(numstr
)
232 reply(who
, where
, "Bad number of %ss." % unit
)
234 if unit
not in time_units
:
235 reply(who
, where
, "I don't know that unit.")
237 reminder
[3] = num
* time_units
[unit
]
238 reply(who
, where
, "Reminder number %d now repeating every %d %ss."
239 % (which
+1, num
, unit
))
242 reply(who
, where
, "Repeating disabled on reminder %d." % which
+1)
244 elif command
== "help":
246 reply(who
, where
, "To get help on a specific command, type $reminder " \
247 + "help <command> (e.g. $reminder help add). " \
248 + "Available reminder commands: add del list set "\
250 elif args
in ("add", "new", "create", "make"):
251 reply(who
, where
, '$reminder add "<msg>" at <time>: Adds a new ' \
252 + "reminder. When <time> comes, I will say <msg>. A" \
253 + " variety of time formats are supported. Use $time" \
254 + " to get my current time for comparison.")
255 elif args
in ("del", "delete", "remove", "rm"):
256 reply(who
, where
, "$reminder del <reminder>: Delete reminder number " \
258 elif args
in ("change", "alter", "set"):
259 reply(who
, where
, "$reminder set <reminder> <property> <value>: Change" \
260 + " <property> of reminder number <reminder> to " \
261 + "<value>. Availale properties: message, time, group")
262 elif args
in ("list", "show"):
263 reply(who
, where
, "$reminder list: Print a list of reminders. If there" \
264 + " are more than 5, I will reply in private.")
265 elif args
== "repeat":
266 reply(who
, where
, "$reminder repeat <reminder> every <interval>: " \
267 + "Reminder number <reminder> will be repeated every " \
268 + "<interval>. Use $reminder repeat <reminder> off to"\
269 + " stop repeating a reminder.")
271 reply(who
, where
, "$reminder help <command>: get help on <command>. " \
272 + "You know, like you just did.")
274 reply(who
, where
, "I don't understand that.")
275 commands
['reminder'] = (perms
.admin
, reminder
)
276 commands
['reminders'] = (perms
.admin
, reminder
)
278 def reminders_list(who
, where
, args
):
279 "$reminders-list: Asks Hal for the current list of reminders."
280 reminder(who
, where
, "list")
281 commands
['reminders-list'] = (perms
.voice
, reminders_list
)
283 def subscribe(who
, where
, args
):
284 "$subscribe <groups>: Subscribes you to the (comma-separated) list of reminder groups. Hal will try to get your attention when a reminder in one of those groups is triggered. Reminder groups are listed in $reminders-list"
286 reply(who
, where
, "Don't you think subscribing to reminders that are" +
287 " already in PM is a bit... pointless?")
290 unlower
[irc_lower(who
)] = who
291 subscriptions
= get_subscription_db(who
, where
)
292 targets
= [target
.strip() for target
in args
.split(",")]
293 new_subscription
= False
294 for target
in targets
:
295 subscribers
= extract(subscriptions
, target
, Set
)
296 user
= irc_lower(who
)
297 if user
not in subscribers
:
298 subscribers
.add(user
)
299 new_subscription
= True
301 reply(who
, where
, "Done.")
303 reply(who
, where
, "You're already subscribed.")
304 commands
['subscribe'] = (perms
.voice
, subscribe
)
306 def unsubscribe(who
, where
, args
):
307 "$unsubscribe <groups>: Unsubscribes you from the (comma-separated) list of reminder groups."
308 subscriptions
= get_subscription_db(who
, where
)
309 targets
= [target
.strip() for target
in args
.split(",")]
310 lost_subscription
= False
311 for target
in targets
:
312 subscribers
= extract(subscriptions
, target
, Set
)
313 user
= irc_lower(who
)
314 if user
in subscribers
:
315 subscribers
.discard(user
)
316 lost_subscription
= True
317 if lost_subscription
:
318 reply(who
, where
, "Done.")
320 reply(who
, where
, "You weren't subscribed.")
321 commands
['unsubscribe'] = (perms
.voice
, unsubscribe
)
323 def poke_msg(who
, where
, args
):
324 "$poke-msg (on/off): Normally, Hal pokes you in the channel. If you turn $poke-msg on, he'll send you a /msg instead. Note that unlike most commands, this is global and will affect Hal's behavior in all channels."
326 state
= poke_via_msg
.get(who
, False)
329 reply(who
, where
, "Already sending pokes via /msg.")
331 poke_via_msg
[who
] = True
332 reply(who
, where
, "Done.")
335 reply(who
, where
, "I wasn't sending pokes via /msg.")
337 poke_via_msg
[who
] = False
338 reply(who
, where
, "Done.")
340 reply(who
, where
, poke_msg
.__doc
__)
341 commands
['poke-msg'] = (perms
.voice
, poke_msg
)
344 for where
in reminder_dbs
.keys():
345 db
= reminder_dbs
[where
]
347 schedule_reminder(where
, db
, reminder
)
348 while True: # Looping in case of errors.
349 safe(scheduler
.run_forever
)()