So I felt like creating a small library to make IRC bots. It's separated in two classes. TelClient is a class that handles the low-level socket programming and message splitting for Telnet connections. IRC is a class built on top of TelClient which provides an abstraction layer for IRC bot development. Bots are built around a message loop, like shown in the hugbot example below. It's also possible to poll with bot.poll(timeout=20) to not block endlessly until a new message arrives.One of the issues I can already see is thread safety: calling bot.receive() in two different threads messes up the messages coming in, because TelClient reads one character at a time. Also I feel like the __setattr__ and __getattribute__ overrides are dangerous and overkill...irc.pyimport socketimport selectorsif hasattr(selectors, 'PollSelector'):    selector = selectors.PollSelectorelse:    selector = selectors.SelectSelector# Splits at every ':' until it finds ' :'# Last element of the list is True if it found a trailing messagedef _my_split(raw):    stt = 0    end = 0    trailing = False    while end < len(raw):        if raw[end] == ':':            yield raw[stt:end]            stt = end + 1            if raw[(end - 1):(end + 1)] == ' :':                trailing = True                end = len(raw)                break        end += 1    yield raw[stt:end]    yield trailingclass TelClient(object):    def __init__(self, host, port, *, debug=False):        self.sock = None        self.write_file = None        self._debug = debug        self._addr = (host, port)    def connect(self):        self.sock = socket.create_connection(self._addr)        self.write_file = self.sock.makefile('w')    def close(self):        self.sock.close()    def msend(self, message):        self.write_file.write(message + '\r\n')        self.write_file.flush()    def mavail(self, *, timeout = 0):        if not self.sock:            return False        with selector() as select:            select.register(self.sock, selectors.EVENT_READ)            return bool(select.select(timeout=timeout))    def mread(self):        buf = b''        c   = b''        while c != b'\n':            c = self.sock.recv(1)            if c == b'':                return ''            buf += c        msg = buf.decode('ascii').rstrip()        return msgclass IRCMessage(object):    def __init__(self, raw):        self.raw = raw        assert self.raw != ''        split = list(_my_split(self.raw))        self.trailing = split.pop() if split.pop() else None        split = sum([chunk.split(' ') for chunk in split], [])        if split[0] == '':            split.pop(0)            self.prefix = split.pop(0)        else:            self.prefix = None        self.command = split.pop(0)        self.args = split        if self.trailing:            self.ctcp = (self.trailing[0] == '\x01')        self._parse_prefix()    def _parse_prefix(self):        if self.prefix:            self.sender = type('', (), {}) # Initiating empty object            if '@' in self.prefix:                # Assuming it came.sender a client                self.sender.type = 'client'                client, host = self.prefix.split('@')                nick, name = client.split('!')                self.sender.nick = nick                self.sender.name = name                self.sender.host = host            else:                self.sender.type = 'server'                self.sender.nick = None                self.sender.name = None                self.sender.host = self.prefix        else:            self.sender = Noneclass IRC(object):    def __init__(self, hostname, port=6667, *, debug=False):        object.__setattr__(self, 'connected', False)        self._debug = debug        self.nick = None        self.name = None        self.realname = None        self._hostname = hostname        self._port = port        self._channels = []    def __getattribute__(self, attr):        if attr == 'channels':            attr = '_channels'        return object.__getattribute__(self, attr)    def __setattr__(self, attr, value):        if attr in ['client', 'connected', 'channels']:            raise AttributeError('\'%s\' is a read-only value' % attr)        if attr in ['name', 'realname'] and self.connected:            raise AttributeError('Cannot change \'%s\' while connected' % attr)        ret = object.__setattr__(self, attr, value)        if attr in ['name', 'realname']:            self._update_user()        if attr == 'nick':            self._update_nick()        return ret    def _update_user(self):        if self.connected:            self.client.msend('USER %s * * :%s' % (self.name, self.realname))    def _update_nick(self):        if self.connected:            self.client.msend('NICK %s' % self.nick)    def connect(self):        if not self.nick:            raise ValueError('\'nick\' value not initialized')        if not self.name:            raise ValueError('\'name\' value not initialized')        if not self.realname:            raise ValueError('\'realname\' value not initialized')        object.__setattr__(self, 'client', TelClient(self._hostname,            self._port, debug=self._debug))        self.client.connect()        object.__setattr__(self, 'connected', True)        self._update_user()        self._update_nick()    def disconnect(self, message = ''):        self.client.msend('QUIT :%s' % message)        self.client.close()        object.__setattr__(self, 'connected', False)    def poll(self, timeout = 0):        return self.client.mavail(timeout=timeout)    def receive(self):        return IRCMessage(self.client.mread())    def join(self, channel):        if channel in self._channels:            return        self._channels.append(channel)        self.client.msend('JOIN %s' % channel)    def part(self, channel, message='Leaving'):        if not channel[0] == '#':            raise ValueError('Invalid channel name')        if channel in self._channels:            self._channels.remove(channel)            self.client.msend('PART %s :%s' % (channel, message))    def send(self, target, message):        self.client.msend('PRIVMSG %s :%s' % (target, message))    def action(self, target, message):        self.send(target, '\x01ACTION ' + message + '\x01')    def pong(self, target):        self.client.msend('PONG :%s' % target)    def mode(self, target, mode):        first = mode.split(' ')[0]        if not first[0] in '+-' and not first[1:].isalpha():            raise ValueError('Invalid mode')        self.client.msend('MODE %s %s' % (target, mode))    def topic(self, channel, text):        if not channel[0] == '#':            raise ValueError('Invalid channel name')        self.client.msend('TOPIC %s :%s' % (channel, text))    def kick(self, channel, nick):        if not channel[0] == '#':            raise ValueError('Invalid channel name')        self.client.msend('KICK %s %s' % (channel, nick))Example usage: hugbot.pyfrom irc import IRC# A small bot that gives hugs in #freehugs on freenodedef example_hug_bot():    bot = IRC('irc.freenode.net')    bot.nick = 'hugbot'    bot.name = 'HugBot'    bot.realname = 'I give out hugs'    bot.connect()    #  Message loop    while True:        msg = bot.receive()        print(msg.raw)        # Make sure nickname is not taken        # Will try hugbot, hugbot_, hugbot__ etc...        if msg.command == '433':            bot.nick += '_'        # Once registered, join the channel        if msg.command == '001':            bot.join('#freehugs')        # Handle server pings to not get kicked        if msg.command == 'PING':            bot.pong(msg.trailing)        # Handle any chat message        if msg.command == 'PRIVMSG':            # Give out hug if hugged (/me hug [nick])            if msg.ctcp and ('ACTION' in msg.trailing):                if ('hugs %s' % bot.nick) == msg.trailing[8:-1].strip():                    bot.action(msg.args[0], 'hugs %s' % (msg.sender.nick))            # Handle commands (like 'hugbot !join #channel')            elif (msg.trailing.split(' ')[0] == bot.nick) \                    and (' !' in msg.trailing):                args = msg.trailing.split(' ')                if args[1] == '!join':                    bot.join(args[2])                if args[1] == '!exit':                    break # Breaking from message loop and exiting                if args[1] == '!ping':                    bot.send(msg.args[0], 'Pong!')                # Proof of concept. Do not use.                # if args[1] == '!eval':                #    ret = eval(msg.trailing.split('\'')[1])                #    bot.send(msg.args[0], 'returned: %s' % ret)    for channel in bot.channels:        bot.part(channel, 'Goodbye everyone')    bot.disconnect()if __name__ == '__main__':    example_hug_bot()
Small IRC library w/ message loop
python;library;socket;chat