import socket import threading import re import time import messages DEFAULT_PORT = 6667 DEFAULT_BUFSIZE = 512 """ Client provides the outer and friendlier layer for connecting to an IRC server and interacting with it. The client will handle all messages received and invoke message handlers. """ class Client: """ Initialize Client """ def __init__(self, host, **kwargs): self.host = host self.port = kwargs.get('port' , DEFAULT_PORT) self.bufsize = kwargs.get('bufsize' , DEFAULT_BUFSIZE) self.messages = kwargs.get('messages', messages.DEFAULT) self.sock = Socket(host, self.port) self.lock = threading.Lock() self.handlers = [] # Append default handlers. # self.handlers.append(...) """ Parse a given line and return its corresponding message object. """ def parse(self, line): # Take the origin out of the line, for easier parsing. origin, line = re.match('^(?::(\S+) )?(.+?)$', line).groups() # Attempt to match an IRC message. match = re.match('^(\S+)(?: (.*?))?(?: :(.+?))?$', line) # Is it a match? If not, create the UnknownMessage. if match: command, args, data = match.groups() # Empty args list. if not args : args = [] else : args = args.split(' ') # Special treatment for NOTICE AUTH. if command == 'NOTICE' and args and args[0] == 'AUTH': args.pop(0) command = 'NOTICE AUTH' # Check for a NOTICE AUTH, a numeric, or any other # message and attempt to create their message objects. if command == 'NOTICE AUTH' : key = 'noticeauth' elif command.isdigit() : key = 'numeric' else : key = command.lower() else: # Defaults for an unknown message. args = [] data = None key = 'unknown' # Now create the message object and return it. return self.messages[key](self, line, command, args, data) """ Call this to put the client into its main loop. During this time the client will receive data from the server, parse it, and invoke handlers. """ def main(self): while 1: line = self.sock.gets(self.bufsize) # - Read data message = self.parse(line.strip()) # - Parse into message """ The Socket class is a light wrapper around standard sockets that makes it easy to receive *lines* from a socket, rather than uncomfortably having to deal with bytes. """ class Socket: """ Initialize Socket """ def __init__(self, host, port): self.buffer = [] self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.SOL_TCP) self.sock.connect((host, port)) """ Write to the socket """ def send(data): self.sock.send(data) """ Read a line from the socket """ def gets(self, bufsize = DEFAULT_BUFSIZE): # Simple switch indicating whether a buffer needs # concatenating (continuing off last line) or # appending (adding a whole new line) buffer_is_incomplete = False # Loop until there is even a single line to return. while 1: # Test whether the buffer is empty or not. if self.buffer: line = self.buffer[0] # Even if the buffer isn't empty, we still need to # make sure there's a *complete* line to return. if line.endswith('\n'): self.buffer.pop(0) return line else: buffer_is_incomplete = True # Attempt to read from the socket... could be nothing. data = self.sock.recv(bufsize) if data: lines = data.splitlines(True) # If the buffer isn't complete, that means we # need to concatenate, not append. if buffer_is_incomplete: self.buffer[0] += lines[0] lines.pop(0) # Here we go, appending new lines! for line in lines: self.buffer.append(line) else: # Let's sleep a little if there was nothing to read. time.sleep(0.1) """ A blank object used for the sake of being able to set attributes on it. """ class OpenStruct: pass