Whoops, missed a spot.
[halbot.git] / hal.py
blob5f80d18c6631d1a5ce33ecc245c6f0fbc6ce5736
1 #!/usr/bin/env python
2 import html, re, irclib, ircbot, thread, sys, atexit, bz2, logging, traceback, time, threadsafe_sched, os
3 from dateutil.parser import parse as raw_time_parser
4 from cPickle import dumps as pickle, loads as depickle
5 DEBUG = 0
6 irclib.DEBUG = DEBUG
8 nick = "Hal"
9 hal = ircbot.SingleServerIRCBot([("localhost", 6667)], nick, "Halbot")
10 hal.connect("localhost", 6667, nick)
11 time.sleep(0.1)
12 hal.connection.join("#casualgameplay")
13 hal.connection.privmsg("NickServ", "identify Orqv)>y!")
14 is_re = re.compile(" (is|are|was|were)( |$)", re.IGNORECASE)
15 nick_re = re.compile(nick + "[:,] ", re.IGNORECASE)
16 private = "p"
17 scheduler = threadsafe_sched.scheduler()
18 os.environ['TZ'] = "GMT"
19 time.tzset()
20 SAVE_VERSION = 2
22 my_users = ircbot.IRCDict()
23 factoid_dbs = ircbot.IRCDict()
24 locked_dbs = ircbot.IRCDict()
25 reminder_dbs = ircbot.IRCDict()
26 try:
27 db = open("hal.db")
28 zipped = db.read()
29 pickled = bz2.decompress(zipped)
30 (version, data) = depickle(pickled)
31 if version == SAVE_VERSION:
32 (my_users, factoid_dbs, locked_dbs, reminder_dbs) = data
33 elif version == 1:
34 (my_users, factoid_dbs, locked_dbs) = data
35 else:
36 sys.exit("Unrecognized database version.")
37 db.close()
38 del db, zipped, pickled
39 except IOError:
40 pass
42 logging.getLogger().addHandler(logging.FileHandler("error.log"))
44 class Buffer(object):
45 def __init__(self, prefix=""):
46 self.data = prefix
47 def write(self, unbuffered):
48 self.data += unbuffered
50 def get_timestamp(when=None):
51 if when == None:
52 when = time.time()
53 return time.ctime(when) + " " + time.tzname[time.daylight]
55 def parse_time(time_string):
56 return time.mktime(raw_time_parser(time_string).timetuple())
58 def safe_call(func, args):
59 try:
60 func(*args)
61 except Exception, e:
62 if isinstance(e, SystemExit):
63 raise
64 buffer = Buffer("Exception in function %s at %s:\n"
65 % (func.__name__, get_timestamp()))
66 traceback.print_exc(file=buffer)
67 logging.getLogger().error(buffer.data)
68 # Try to report the error interactively.
69 if len(args) >= 2 and type(args[0]) == type(args[1]) == str:
70 try:
71 reply(args[0], args[1], "Ow! Ohh, man, that didn't feel good. " \
72 +"Somebody get FunnyMan, quick!")
73 except Exception:
74 pass
75 elif len(args) and type(args[0]) == str:
76 try:
77 reply('', args[0], "Ow! Ohh, man, that didn't feel good. " \
78 +"Somebody get FunnyMan, quick!", all=True)
79 except Exception:
80 pass
82 def safe(func):
83 return lambda *args: safe_call(func, args)
85 def resize(list_or_tuple, length):
86 '''Creates a new list or tuple of length 'length', using as many elements from
87 list_or_tuple as possible and right-padding with "". Does not modify
88 list_or_tuple.'''
89 if len(list_or_tuple) < length:
90 if type(list_or_tuple) == list:
91 return list_or_tuple + [""] * (length - len(list_or_tuple))
92 else:
93 return list_or_tuple + ("",) * (length - len(list_or_tuple))
94 else:
95 return list_or_tuple[:length]
97 irc = hal.ircobj
98 class Whois(object):
99 registered = False
100 ircop = False
101 channel_perms = None
102 def __init__(self):
103 self.channel_perms = {}
105 class Perms(object):
106 symbols = {}
107 def __init__(self, level, symbol=None):
108 self.level = level
109 self.symbol = symbol
110 if symbol:
111 Perms.symbols[symbol] = self
112 def __cmp__(self, other):
113 return self.level - other.level
114 def __str__(self):
115 if self.symbol:
116 return self.symbol
117 else:
118 return str(self.level)
119 #@staticmethod
120 def from_symbol(symbol):
121 return Perms.symbols.get(symbol, Perms.present)
122 from_symbol = staticmethod(from_symbol)
123 Perms.ban = Perms(-2)
124 Perms.none = Perms(-1)
125 Perms.present = Perms(0)
126 Perms.voice = Perms(1, "+")
127 Perms.hop = Perms(2, "%")
128 Perms.op = Perms(3, "@")
129 Perms.admin = Perms(4, "&")
130 Perms.owner = Perms(5, "~")
131 Perms.ircop = Perms(6)
133 waiting_commands = {}
134 whois_info = {}
136 def got_command(who, where, command, args=""):
137 if not command in commands:
138 command = 'raise error'
139 args = "I don't understand that."
140 waiting = waiting_commands.get(who, [])
141 if not waiting:
142 (required_perms, do_it) = commands[command]
143 if where == private and required_perms <= Perms.owner:
144 safe_call(do_it, (who, where, args))
145 elif required_perms <= Perms.voice and where in hal.channels:
146 chan = hal.channels[where]
147 if who in chan.voiceddict or who in chan.hopdict or who in chan.operdict \
148 or who in chan.admindict or who in chan.ownerdict:
149 safe_call(do_it, (who, where, args))
150 else:
151 return
152 else:
153 waiting_commands[who] = [(where, command, args)]
154 hal.connection.whois((who,))
155 else:
156 waiting.append((where, command, args))
158 def reply(who, where, what, all=False):
159 if where == private:
160 hal.connection.privmsg(who, what)
161 elif all:
162 hal.connection.privmsg(where, what)
163 else:
164 hal.connection.privmsg(where, "%s: %s" % (who, what))
166 def get_perms(who, where):
167 user = whois_info[who]
168 if user.ircop:
169 return Perms.ircop
170 if where == private:
171 server_perm = Perms.owner
172 else:
173 server_perm = whois_info[who].channel_perms.get(where, Perms.none)
174 if user.registered:
175 my_perm = my_users.get(who, Perms.none)
176 else:
177 my_perm = Perms.none
178 return max(server_perm, my_perm)
180 commands = {}
182 commands['raise error'] = (Perms.voice, reply)
184 def ping(who, where, args):
185 reply(who, where, "Pong!")
186 commands['ping'] = (Perms.voice, ping)
188 class TestException(Exception): pass
190 def error(who, where, args):
191 raise TestException, str(args)
192 commands['error'] = (Perms.ircop, error)
194 def join(who, where, args):
195 hal.connection.join(where)
196 commands['do join'] = (Perms.op, join)
198 def save(who=None, where=None, args=None):
199 pickled = pickle((
200 SAVE_VERSION,
201 (my_users, factoid_dbs, locked_dbs, reminder_dbs)
203 zipped = bz2.compress(pickled)
204 db = open("hal.db", "w")
205 db.write(zipped)
206 db.close()
207 if who:
208 reply(who, where, "Database saved.")
209 commands['save'] = (Perms.ircop, save)
210 atexit.register(save)
212 def shutdown(who, where, args):
213 hal.connection.quit("Daisy, daaaisy...")
214 raise SystemExit
215 commands['shutdown'] = (Perms.ircop, shutdown)
217 def title(who, where, args, all=False):
218 try:
219 reply(who, where, "[%s]" % (html.get_title(args)), all)
220 except Exception, e:
221 print e
222 reply(who, where, "Error retrieving URL '%s'." % args, all)
223 commands['title'] = (Perms.voice, title)
225 def title_implicit(who, where, args):
226 title(who, where, args, all=True)
227 commands['title implicit'] = (Perms.voice, title_implicit)
229 def google(who, where, args):
230 try:
231 url = html.first_google(args)
232 reply(who, where, "Google says: %s [%s]." % (url, html.get_title(url)), all=True)
233 except Exception, e:
234 print e
235 reply(who, where, "Ewwww... Google barfed on me.", all=True)
236 commands['google'] = (Perms.voice, google)
238 def jig(who, where, args):
239 google(who, where, args + " site:jayisgames.com")
240 commands['jig'] = (Perms.voice, jig)
242 def wp(who, where, args):
243 google(who, where, args + " site:en.wikipedia.org")
244 commands['wp'] = (Perms.voice, wp)
245 commands['wikipedia'] = (Perms.voice, wp)
247 def extract_my_db(dbs, who, where, default_maker):
248 if where == private:
249 key = who
250 else:
251 key = where
252 if key not in dbs:
253 default = default_maker()
254 dbs[key] = default
255 return dbs[key]
257 def get_fact_dbs(who, where):
258 factoids = extract_my_db(factoid_dbs, who, where, ircbot.IRCDict)
259 locked = extract_my_db(locked_dbs, who, where, ircbot.IRCDict)
260 return (factoids, locked)
262 def do_is(who, where, args):
263 (key, to_be, garbage, fact) = args
264 (factoids, locked) = get_fact_dbs(who, where)
265 if key.endswith('?'):
266 key = key[:-1]
267 if locked.get(key):
268 reply(who, where, "I'm sorry, %s, I'm afraid I can't do that." % who, all=True)
269 return
270 elif fact:
271 factoids[key] = (to_be, fact)
272 elif key in factoids:
273 del factoids[key]
274 commands['do is'] = (Perms.voice, do_is)
276 def forget(who, where, args):
277 (factoids, locked) = get_fact_dbs(who, where)
278 key = args
279 if locked.get(key):
280 reply(who, where, "I'm sorry, %s, I'm afraid I can't do that." % who, all=True)
281 elif key in factoids:
282 del factoids[key]
283 reply(who, where, "I forgot %s." % key)
284 else:
285 reply(who, where, "I don't have anything matching '%s'." % key)
286 commands['forget'] = (Perms.voice, forget)
288 def force(who, where, args):
289 (factoids, locked) = get_fact_dbs(who, where)
290 if not is_re.search(args):
291 reply(who, where, "Syntax: $force a (is/are/was/were) b")
292 return
293 (key, to_be, garbage, fact) = resize(is_re.split(args, 1),4)
294 if key.endswith('?'):
295 key = key[:-1]
296 if fact:
297 factoids[key] = (to_be, fact)
298 elif key in factoids:
299 del factoids[key]
300 commands['force'] = (Perms.op, force)
302 def lock(who, where, args):
303 (factoids, locked) = get_fact_dbs(who, where)
304 if args.endswith('?'):
305 args = args[:-1]
306 if not locked.get(args):
307 locked[args] = True
308 reply(who, where, "Done.")
309 else:
310 reply(who, where, "It's already locked.")
311 commands['lock'] = (Perms.op, lock)
313 def unlock(who, where, args):
314 (factoids, locked) = get_fact_dbs(who, where)
315 if args.endswith('?'):
316 args = args[:-1]
317 if locked.get(args):
318 locked[args] = False
319 reply(who, where, "Done.")
320 else:
321 reply(who, where, "It's not locked.")
322 commands['unlock'] = (Perms.op, unlock)
324 def factoid(who, where, args, implicit=False):
325 (factoids, locked) = get_fact_dbs(who, where)
326 if args.endswith('?'):
327 args = args[:-1]
328 if args in factoids:
329 (to_be, fact) = factoids[args]
330 if not implicit and fact.count("$who"):
331 implicit = True
332 response = ""
333 lfact = fact.lower()
334 if lfact.startswith("<"):
335 if lfact.startswith("<raw>"):
336 response = fact[5:].lstrip()
337 elif lfact.startswith("<reply>"):
338 response = fact[7:].lstrip()
339 elif lfact.startswith("<action>"):
340 response = "\x01ACTION %s\x01" % fact[8:].lstrip()
341 if not response:
342 response = "I hear that %s %s %s" % (args, to_be, fact)
343 response = response.replace("$who", who)
344 reply(who, where, response, all=implicit)
345 elif not implicit:
346 reply(who, where, "I don't have anything matching '%s'." % args)
347 commands['fact'] = (Perms.voice, factoid)
348 commands['factoid'] = (Perms.voice, factoid)
350 def factoid_implicit(who, where, args):
351 factoid(who, where, args, implicit=True)
352 commands['factoid implicit'] = (Perms.voice, factoid_implicit)
354 def get_time(who, where, args):
355 reply(who, where, "Current time is: " + get_timestamp())
356 commands['time'] = (Perms.voice, get_time)
358 def get_reminder_db(who, where):
359 return extract_my_db(reminder_dbs, who, where, lambda: [])
361 def describe_reminder(reminder):
362 (when, message, groups, repeat) = reminder
363 description = '"%s" at %s.' % (message, get_timestamp(when))
364 if groups:
365 groupstr = ",".join(groups)
366 description += " Groups: " + groupstr
367 if repeat:
368 hours = repeat / 3600
369 minutes = str(repeat / 60 % 60).zfill(2)
370 description += " Repeating every %d:%s." % (hours, minutes)
371 return description
373 reminder_res = {"add": re.compile(r'^"((?:[^"]|\")*)" +at +([^"]*)$',
374 re.IGNORECASE),
375 "set": re.compile(r'^(\d+) +(\w+) +(.*)$', re.IGNORECASE),
376 "repeat h+:mm": re.compile(r'^(\d+) +every +(\d+):(\d\d)$',
377 re.IGNORECASE),
378 "repeat x units": re.compile(r'^(\d+) +every +(\d+) +' +
379 r'(week|day|hour|minute)s?$',
380 re.IGNORECASE),
381 "repeat off": re.compile(r'^(\d+) +(off|disable|stop|never|none'
382 +r'|remove)$',
383 re.IGNORECASE)
386 time_units = {"minute": 60,
387 "hour": 60*60,
388 "day": 60*60*24,
389 "week": 60*60*24*7}
391 def safeint(intstr):
392 try:
393 return int(intstr)
394 except ValueError:
395 return -1
397 def reminder_index(who, where, reminders, whichstr):
398 which = safeint(whichstr)
399 if which < 1:
400 reply(who, where, "Which reminder?")
401 return -1
402 if which > len(reminders):
403 reply(who, where, "I don't have that many reminders.")
404 return -1
405 return which - 1
407 class workaround(object):
408 pass
410 def real_where(who, where):
411 if where == private:
412 return who
413 return where
415 def fire_reminder(where, reminders, reminder):
416 (when, message, groups, repeat) = reminder
417 curtime = time.time()
418 if when < (curtime - 60):
419 delay = curtime - when
420 reply('', where, "Reminder delayed by %d minutes: %s" % (delay // 60,
421 message), all=True)
422 else:
423 delay = 0
424 reply('', where, message, all=True)
425 if repeat:
426 if repeat < delay:
427 skip = delay // repeat
428 reply('', where, "(skipping %d delayed repititions)" % skip, all=True)
429 real_repeat = (skip + 1) * repeat
430 else:
431 real_repeat = repeat
432 reminder[0] += real_repeat
433 workaround.schedule_reminder(where, reminders, reminder)
434 else:
435 reminders.remove(reminder)
436 fire_reminder = safe(fire_reminder)
438 def schedule_reminder(where, reminders, reminder):
439 scheduler.enterabs(reminder[0], 0, fire_reminder, (where, reminders, reminder))
440 workaround.schedule_reminder = staticmethod(schedule_reminder)
442 def add_reminder(who, where, reminders, reminder):
443 if len(reminders) < 10 or where == "#casualgameplay":
444 reminders.append(reminder)
445 schedule_reminder(real_where(who, where), reminders, reminder)
446 reply(who, where, "Done.")
447 else:
448 reply(who, where,
449 "I'm sorry, %s, I'm afraid I can't do that. You're limited to 10."
450 % who)
452 def cancel_reminder(where, reminders, reminder):
453 try:
454 scheduler.cancel((reminder[0], 0, fire_reminder, (where, reminders, reminder)))
455 except ValueError:
456 pass
458 def reschedule(who, where, reminders, reminder, when):
459 rwhere = real_where(who, where)
460 cancel_reminder(rwhere, reminders, reminder)
461 reminder[0] = when
462 schedule_reminder(rwhere, reminders, reminder)
464 def reminder(who, where, reminder_args):
465 (command, args) = resize(reminder_args.split(" ", 1), 2)
466 reminders = get_reminder_db(who, where)
467 if not command:
468 reply(who, where, "Available reminder commands: add del list set repeat help")
469 elif command in ("add", "new", "create", "make"):
470 parsed = reminder_res["add"].search(args)
471 if not parsed:
472 reply(who, where, "I don't understand that. Try $reminder help add")
473 return
474 (msg, whenstr) = parsed.groups()
475 try:
476 when = parse_time(whenstr)
477 except ValueError:
478 reply(who, where, "I don't understand that time.")
479 return
480 reminder = [when, msg, [], 0]
481 add_reminder(who, where, reminders, reminder)
482 elif command in ("del", "delete", "remove", "rm"):
483 which = reminder_index(who, where, reminders, args)
484 if which == -1:
485 return
486 reminder = reminders[which]
487 del reminders[which]
488 cancel_reminder(real_where(who, where), reminders, reminder)
489 reply(who, where, "Deleted reminder %d: %s" %
490 (which+1, describe_reminder(reminder)))
491 elif command in ("list", "show"):
492 if len(reminders) > 5:
493 reply(who, where, "Listing %d reminders in private " % len(reminders)
494 + "to avoid channel clutter")
495 where = private
496 if not reminders:
497 reply(who, where, "No reminders.")
498 index = 1
499 for reminder in reminders:
500 reply(who, where, "%2d. %s" % (index, describe_reminder(reminder)))
501 index += 1
502 elif command in ("change", "alter", "set"):
503 parsed = reminder_res["set"].search(args)
504 if not parsed:
505 reply(who, where, "I don't understand that. Try $reminder help set")
506 return
507 (whichstr, property, value) = parsed.groups()
508 which = reminder_index(who, where, reminders, whichstr)
509 if which == -1:
510 return
511 reminder = reminders[which]
512 lproperty = property.lower()
513 if lproperty in ("message", "msg"):
514 reminder[1] = value
515 elif lproperty == "time":
516 try:
517 when = parse_time(value)
518 reschedule(who, where, reminders, reminder, when)
519 except ValueError:
520 reply(who, where, "I don't understand that time.")
521 return
522 elif lproperty in ("group", "groups"):
523 groups = value.split(",")
524 groups = [group.strip() for group in groups]
525 reminder[2] = groups
526 reply(who, where, "Done.")
527 elif command == "repeat":
528 parse_hm = reminder_res["repeat h+:mm"].search(args)
529 parse_units = reminder_res["repeat x units"].search(args)
530 parse_off = reminder_res["repeat off"].search(args)
531 parsed = parse_hm or parse_units or parse_off
532 if parsed:
533 which = reminder_index(who, where, reminders, parsed.groups()[0])
534 if which == -1:
535 return
536 reminder = reminders[which]
537 else:
538 reply(who, where, "I don't understand that. Try $reminder help repeat")
539 return
540 if parse_hm:
541 (whichstr, hourstr, minutestr) = parsed.groups()
542 hours = safeint(hourstr)
543 if hours < 0:
544 reply(who, where, "Bad number of hours.")
545 return
546 minutes = safeint(minutestr)
547 # Mathematicians, read at your own peril.
548 if 59 < minutes < 0:
549 reply(who, where, "Bad number of minutes.")
550 return
551 if hours == minutes == 0:
552 reply(who, where, "Repeating continuously sounds like a bad idea.")
553 return
554 reminder[3] = 60 * (minutes + 60 * hours)
555 reply(who, where,
556 "Reminder number %d now repeating every %d hours and %d minutes."
557 % (which+1, hours, minutes))
558 elif parse_units:
559 (whichstr, numstr, unit) = parsed.groups()
560 unit = unit.lower()
561 num = safeint(numstr)
562 if numstr < 1:
563 reply(who, where, "Bad number of %ss." % unit)
564 return
565 if unit not in time_units:
566 reply(who, where, "I don't know that unit.")
567 return
568 reminder[3] = num * time_units[unit]
569 reply(who, where, "Reminder number %d now repeating every %d %ss."
570 % (which+1, num, unit))
571 else: # parse_off
572 reminder[3] = 0
573 reply(who, where, "Repeating disabled on reminder %d." % which+1)
575 elif command == "help":
576 if not args:
577 reply(who, where, "To get help on a specific command, type $reminder " \
578 + "help <command> (e.g. $reminder help add). " \
579 + "Available reminder commands: add del list set "\
580 + "repeat help")
581 elif args in ("add", "new", "create", "make"):
582 reply(who, where, '$reminder add "<msg>" at <time>: Adds a new ' \
583 + "reminder. When <time> comes, I will say <msg>. A" \
584 + " variety of time formats are supported. Use $time" \
585 + " to get my current time for comparison.")
586 elif args in ("del", "delete", "remove", "rm"):
587 reply(who, where, "$reminder del <reminder>: Delete reminder number " \
588 + "<reminder>.");
589 elif args in ("change", "alter", "set"):
590 reply(who, where, "$reminder set <reminder> <property> <value>: Change" \
591 + " <property> of reminder number <reminder> to " \
592 + "<value>. Availale properties: message, time, group")
593 elif args in ("list", "show"):
594 reply(who, where, "$reminder list: Print a list of reminders. If there" \
595 + " are more than 5, I will reply in private.")
596 elif args == "repeat":
597 reply(who, where, "$reminder repeat <reminder> every <interval>: " \
598 + "Reminder number <reminder> will be repeated every " \
599 + "<interval>. Use $reminder repeat <reminder> off to"\
600 + " stop repeating a reminder.")
601 elif args == "help":
602 reply(who, where, "$reminder help <command>: get help on <command>. " \
603 + "You know, like you just did.")
604 else:
605 reply(who, where, "I don't understand that.")
606 commands['reminder'] = (Perms.admin, reminder)
607 commands['reminders'] = (Perms.admin, reminder)
609 def do_commands(who):
610 user = whois_info[who]
611 for where, command, args in waiting_commands[who]:
612 if command not in commands:
613 command = 'raise error'
614 args = "I don't understand that."
615 (required_perms, do_it) = commands[command]
616 if required_perms <= get_perms(who, where):
617 safe_call(do_it, (who, where, args))
618 waiting_commands[who] = []
620 #@safe
621 def got_whois(conn, event):
622 user = event.arguments()[0]
623 whois_info[user] = Whois()
624 got_whois = safe(got_whois)
625 irc.add_global_handler("whoisuser", got_whois)
626 irc.add_global_handler("nosuchnick", got_whois)
628 #@safe
629 def got_registered(conn, event):
630 args = event.arguments()
631 if len(args) == 2 and args[1] == "is a registered nick":
632 user = args[0]
633 whois_info[user].registered = True
634 got_registered = safe(got_registered)
635 irc.add_global_handler("307", got_registered)
637 #@safe
638 def got_channels(conn, event):
639 args = event.arguments()
640 if len(args) == 2:
641 user = args[0]
642 channels = args[1].split()
643 for c in channels:
644 if c[0] != "#":
645 whois_info[user].channel_perms[c[1:]] = Perms.from_symbol(c[0])
646 else:
647 whois_info[user].channel_perms[c] = Perms.present
648 got_channels = safe(got_channels)
649 irc.add_global_handler("whoischannels", got_channels)
651 #@safe
652 def got_servop(conn, event):
653 args = event.arguments()
654 if len(args) == 2:
655 user = args[0]
656 whois_info[user].ircop = True
657 got_servop = safe(got_servop)
658 irc.add_global_handler("whoisoperator", got_servop)
660 #@safe
661 def got_whoend(conn, event):
662 who = event.arguments()[0]
663 do_commands(who)
664 if False:
665 user = whois_info[who]
666 print "Who info for %s:" % who
667 print "Registered: %s" % user.registered
668 print "IRCop: %s" % user.ircop
669 p = user.channel_perms
670 perms = "".join([("%s: %s, " % (k, p[k])) for k in p])[:-2]
671 print "Channel info: %s" % perms
672 got_whoend = safe(got_whoend)
673 irc.add_global_handler("endofwhois", got_whoend)
675 #@safe
676 def got_invite(conn, event):
677 who = irclib.nm_to_n(event.source())
678 where = event.arguments()[0]
679 got_command(who, where, "do join")
680 got_invite = safe(got_invite)
681 irc.add_global_handler("invite", got_invite)
683 #@safe
684 def got_msg(conn, event):
685 msg = event.arguments()[0]
686 who = irclib.nm_to_n(event.source())
687 (command, args) = resize(msg.split(" ", 1),2)
688 if command and command[0] == "$":
689 command = command[1:]
691 if event.eventtype() == 'privmsg':
692 where = private
693 else:
694 where = event.target()
695 if msg.count("http://"):
696 got_command(who, where, "title implicit", html.extract_url(msg))
697 if msg[0] != "$":
698 if nick_re.match(msg):
699 (me, command, args) = resize(msg.split(" ", 2),3)
700 if command in commands:
701 pass
702 elif is_re.search(msg):
703 command = "do is"
704 args = is_re.split(msg.split(" ", 1)[1], 1)
705 else:
706 command = "factoid implicit"
707 args = msg.split(" ", 1)[1]
708 else:
709 if command in commands and commands[command][0] <= Perms.voice:
710 pass
711 elif is_re.search(msg):
712 command = "do is"
713 args = is_re.split(msg, 1)
714 else:
715 command = "factoid implicit"
716 args = msg
717 got_command(who, where, command, args)
718 got_msg = safe(got_msg)
719 irc.add_global_handler("privmsg", got_msg)
720 irc.add_global_handler("pubmsg", got_msg)
723 def debug_console():
724 while DEBUG:
725 cmd = sys.stdin.readline().strip()
726 if cmd:
727 hal.connection.send_raw(cmd)
728 thread.start_new_thread(debug_console, ())
730 def run_reminders():
731 for where in reminder_dbs.keys():
732 db = reminder_dbs[where]
733 for reminder in db:
734 schedule_reminder(where, db, reminder)
735 while True: # Looping in case of errors.
736 safe(scheduler.run_forever)()
737 thread.start_new_thread(run_reminders, ())
739 hal.ircobj.process_forever(None)