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