diff -r 2266d6d73de3 -r d67cc4fbc3f1 irc.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/irc.py Mon Nov 10 02:06:06 2014 +0200 @@ -0,0 +1,235 @@ +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): + 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 = cfg.get_nodelist ('channels') + self.write ('JOIN ' + channame) + self.save_config() + + 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.save_config() + + 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') \ No newline at end of file