Mon, 08 Dec 2014 16:12:51 -0500
- now in color!
import asyncore import socket import sys import re import modulecore as ModuleCore import traceback from configfile import Config import bt as Bt import hgpoll as HgPoll CLIF_CONNECTED = (1 << 1) all_clients = [] class RestartError (Exception): def __init__ (self, value): self.message = value def __str__ (self): return self.message # # Prints a line to log channel(s) # def broadcast (line): print line for client in all_clients: if not client.flags & CLIF_CONNECTED: continue for channel in client.channels: if channel.get_value ('logchannel', default=False): client.write ("PRIVMSG %s :%s" % (channel.get_value ('name'), line)) class logical_exception (Exception): def __init__ (self, value): self.value = value def __str__ (self): return self.value # # Main IRC client class # class irc_client (asyncore.dispatcher): def __init__ (self, cfg, flags): global all_clients self.name = cfg.get_value ('name') self.host = cfg.get_value ('address') self.port = cfg.get_value ('port', default=6667) self.password = cfg.get_value ('password', default='') self.channels = cfg.get_nodelist ('channels') self.flags = flags self.send_buffer = [] self.umode = cfg.get_value ('umode', default='') self.cfg = cfg self.desired_name = Config.get_value ('nickname', default='cobalt') self.mynick = self.desired_name self.verbose = Config.get_value ('verbose', default=False) self.commandprefix = Config.get_value ('commandprefix', default='.') all_clients.append (self) asyncore.dispatcher.__init__ (self) self.create_socket (socket.AF_INET, socket.SOCK_STREAM) self.connect ((self.host, self.port)) def register_to_irc (self): ident = Config.get_value ('ident', default='cobalt') gecos = Config.get_value ('gecos', default='cobalt') self.write ("PASS %s" % self.password) self.write ("USER %s * * :%s" % (ident, gecos)) self.write ("NICK %s" % self.mynick) def handle_connect (self): print "Connected to [%s] %s:%d" % (self.name, self.host, self.port) self.register_to_irc() def write (self, utfdata): try: self.send_buffer.append ("%s" % utfdata.decode("utf-8","ignore").encode("ascii","ignore")) except UnicodeEncodeError: pass def handle_close (self): print "Connection to [%s] %s:%d terminated." % (self.name, self.host, self.port) self.close() def handle_write (self): self.send_all_now() def readable (self): return True def writable (self): return len (self.send_buffer) > 0 def send_all_now (self): for line in self.send_buffer: if self.verbose: print "[%s] <- %s" % (self.name, line) self.send ("%s\n" % line) self.send_buffer = [] def handle_read (self): lines = self.recv (4096).splitlines() for utfline in lines: try: line = utfline.decode("utf-8","ignore").encode("ascii","ignore") except UnicodeDecodeError: continue if self.verbose: print "[%s] -> %s" % (self.name, line) if line.startswith ("PING :"): self.write ("PONG :%s" % line[6:]) else: words = line.split(" ") if len(words) >= 2: if words[1] == "001": self.flags |= CLIF_CONNECTED for channel in self.channels: self.write ("JOIN %s %s" % (channel.get_value ('name'), channel.get_value ('password', default=''))) umode = self.cfg.get_value ('umode', '') if umode != '': self.write ('MODE %s %s' % (self.mynick, self.cfg.get_value ('umode', ''))) elif words[1] == "PRIVMSG": self.handle_privmsg (line) elif words[1] == 'QUIT': rex = re.compile (r'^:([^!]+)!([^@]+)@([^ ]+) QUIT') match = rex.match (line) # Try reclaim our nickname if possible if match and match.group(1) == self.desired_name: self.mynick = self.desired_name self.write ("NICK %s" % self.mynick) elif words[1] == "433": #:irc.localhost 433 * cobalt :Nickname is already in use. self.mynick += self.cfg.get_value ('conflictsuffix', default='`') self.write ("NICK " + self.mynick) # Check for new issues on the bugtracker Bt.poll() # Check for new commits in the repositories HgPoll.poll() # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Handle a PRIVMSG line from the IRC server # def handle_privmsg (self, line): rex = re.compile (r'^:([^!]+)!([^@]+)@([^ ]+) PRIVMSG ([^ ]+) :(.+)$') match = rex.match (line) if not match: broadcast ("Recieved bad PRIVMSG: %s" % line) sender = match.group (1) user = match.group (2) host = match.group (3) channel = match.group (4) message = match.group (5) replyto = channel if channel != self.mynick else sender # Check for command. if len(message) >= 2 and message[0] == self.commandprefix and message[1] != self.commandprefix: stuff = message[1:].split(' ') command = stuff[0] args = stuff[1:] self.handle_command (sender, user, host, replyto, command, args, message) return Bt.process_message (self, line, replyto) def add_irc_channel (self, channame): for channel in self.channels: if channel.get_value ('name').upper() == channame.upper(): return channel = self.cfg.append_nodelist ('channels') channel.set_value ('name', channame) self.channels = self.cfg.get_nodelist ('channels') self.write ('JOIN ' + channame) self.cfg.save() def remove_irc_channel (self, channame): for channel in self.channels: if channel.get_value ('name') == channame: self.channels.remove (channel) break else: return self.write ('PART ' + channame) self.cfg.save() def handle_command (self, sender, ident, host, replyto, command, args, message): kvargs = {'sender': sender, 'ident': ident, 'host': host, 'replyto': replyto, 'cmdname': command, 'message': message} try: result = ModuleCore.call_command (self, **kvargs) if result: return except ModuleCore.CommandError as e: lines = str (e).split ('\n') self.privmsg (replyto, 'error: %s' % lines[0]) for line in lines[1:]: self.privmsg (replyto, ' ' + line) return def handle_error(self): raise RestartError (traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback)) def restart(self): raise RestartError('') def privmsg (self, channel, msg): self.write ("PRIVMSG %s :%s" % (channel, msg)) def close_connection (self, message): if self.flags & CLIF_CONNECTED: self.write ("QUIT :" + message) self.send_all_now() self.close() def quit_irc (self): self.close_connection ('Leaving') def exceptdie (self): self.close_connection ('Caught exception') def keyboardinterrupt (self): self.close_connection ('KeyboardInterrupt')