class IRCProxyClient(object): def __init__(self, conf): self.proxy_address = conf.proxy_address self.proxy_port = conf.proxy_port self.interface = None # Defined later in self.run self.formatter = Formatter(self) # Makes sure files exist #TODO: Reference documentation on how to generate these files. if not os.path.exists(conf.cert_file): raise RuntimeError('cert_file "%s" not found!' % conf.cert_file) if conf.key_file and not os.path.exists(conf.key_file): raise RuntimeError('key_file "%s" not found!' % conf.key_file) if not os.path.exists(conf.accepted_certs_file): raise RuntimeError('accepted_certs_file "%s" not found!' % conf.accepted_certs_file) #TODO: proxy and port need to be parsed more robustly with urlparse self.proxy = xmlrpclib.ServerProxy( "%s:%s/" % (self.proxy_address, self.proxy_port), transport=securexmlrpc.HTTPSTransport( certfile=conf.cert_file, keyfile=conf.key_file, ca_certs=conf.accepted_certs_file, )) # Start event subprocess self.event_pipe, child_conn = Pipe() self.event_process = Process(target=event_loop, args=( self.proxy, child_conn, )) self.event_process.start() def get_events(self): if self.event_pipe.poll(0.1): events = self.event_pipe.recv() if isinstance(events, _KillSignal): exit(1) return events else: return [] def run(self, interface): self.interface = interface try: self.interface.initialize() self.interface.run() finally: self.exit() def exit(self): self.interface.uninitialize() self.event_process.terminate() def handle_events(self): events = self.get_events() for event in events: self.handle_event(event) def handle_event(self, event): '''Called when an event comes in. Responsible for: * Calling self.interface if necessary. For example, on an incoming channel_join event, the interface must be notified so it can create a new window or tab. * Give self.formatter.print_event() the event which will then give the interface text to display. This method is called in the order that the events happen. Event sorting is handled elsewhere. :param event: The event dictionary. See the server spec for details on the event format. .. todo:: Reference event specification for event parameter. .. todo:: Some basic event checking: ex, make sure type field is present. ''' event_type = event['type'] # Notify interface, if applicable if event_type == "server_connect": self.interface.on_server_connect(event['server']) elif event_type == "server_disconnect": self.interface.on_server_disconnect(event['server']) elif event_type == "channel_join": if event['this_user']: self.interface.on_channel_join(event['server'], event['target']) return elif event_type == "channel_part": if event['this_user']: self.interface.on_channel_part(event['server'], event['target']) return self.formatter.print_event(event) def run_command(self, command_string, server=None, channel=None): '''Called by self.interface when the user inputs text. Messages prefixed with '/' are interpreted as functional commands. Otherwise, the command is interpreted as a chat message. The server and channel arguments are meaningful in commands such as chat messages, where the user does not specify a server and channel, but the command still must be routed correctly. Giving a server and channel when it is not needed will never cause problems. The interface should always specify a server and channel if one is available. :param command_string: The raw string of text that was inputted by the user. May or may not start with '/'. :param server: The server that the command corresponds to, or None if no server is appropriate. :param channel: The channel that the command corresponds to, or None if no channel is appropriate. .. todo:: Reference command specification ''' try: # Command if len(command_string) and command_string[0] == '/': terms = command_string.split(' ') command = terms[0][1:] args = terms[1:] if command == "connect": if len(args) == 2: self.proxy.server_connect(args[0], args[1], self.conf.default_username) elif len(args) == 3: self.proxy.server_connect(*args) else: #TODO: Print useage message print "Wrong number of args" elif command == "join": if len(args) != 1: #TODO: Print useage message print "Wrong number of args" elif not server: #TODO: Print useage message print "You must connect a server before joining a channel" else: self.proxy.channel_join(server, args[0]) elif command == "leave" or command == "part": if not server or not channel: if len(args) == 2: self.proxy.channel_part(*args) else: #TODO: Print useage message print "Wrong number of args" else: self.proxy.channel_part(server, channel) else: print "Warning: Bad command!" pass #TODO: Invalid command, provide help! # Privmsg elif server and channel: self.proxy.channel_message(server, channel, command_string) # Nothing else: #TODO: Provide better help! print "Cannot chat outside of a channel!" except xmlrpclib.Fault as fault: self.put_text(server, channel, "Error: %s" % fault.faultString) def put_text(self, server, channel, text): '''Pass through function for interface.put_text. Called by self.formatter to give text to self.interface. ''' self.interface.put_text(server, channel, text)