5
\$\begingroup\$

All of the other Python IRC packages I've seen out there are overly complicated for simple tasks, and they don't abstract away much of the complexity of the protocol and asynchronicity. I've taken a crack at a better interface, but I'm new to the IRC protocol. It runs under Python2 and 3. How's it look?

"""
simple_irc: a simple, Pythonic IRC interface.
This module contains 2 classes: an IRC adapter, and a message wrapper.
Basic usage:
 >>> irc = simple_irc.IRC('mynick', '#python', 'irc.myserver.net')
 >>> for msg in irc: print(msg.sender + ': ' + msg)
 somebody: Hello, simple_irc!
 somebody_else: Oh hai there!
 >>> irc.write('Hi guys!')
"""
import socket, threading, time, sys
if sys.version_info.major < 3:
 import Queue
else: import queue as Queue
_wait = .01
class IRC(object):
 '''A simple IRC interface that can handle simultaneous reading and writing.
 Its interface is similar to those in the io module.'''
 def __init__(self, nick, channel, network, port=6667, future=False,
 mode=2, realname='Python simpleirc bot'):
 '''Initialize an IRC connection. By default, does not return until
 the connection is established.
 nick: the nickname to connect with
 channel: the name of the channel to connect to (e.g. "#python")
 network: the network to connect to (e.g. "irc.freenode.net")
 port: the port to connect on
 future: if True, will not connect until open() is called
 mode: bitmask of initial user mode. Only 'w' (2) and 'i' (4) are available.
 realname: real name field. Can be any string.
 '''
 self.nick = nick
 self.channel = channel
 self.network = network
 self.port = port
 self.mode = mode
 self.realname = realname
 self._readqueue = Queue.Queue()
 self._writequeue = Queue.Queue()
 self._closed = True
 if not future:
 self.open()
 def open(self):
 '''Open the connection. Does not return until the connection has been established.'''
 if not self._closed:
 raise IOError(self + " is already open")
 self._soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 self._soc.connect((self.network, self.port))
 self._soc.send('NICK {}\r\n'.format(self.nick).encode('UTF-8'))
 self._soc.send('USER {} {} * :{}\r\n'.format(self.nick, self.mode, self.realname).encode('UTF-8'))
 self._soc.send('JOIN {}\r\n'.format(self.channel).encode('UTF-8'))
 while True:
 #Read through all of the opening garbage
 data = self._soc.recv(4096).decode('UTF-8')
 #Error messages don't follow the same format as others, 
 #so we parse the code out early
 code = data.split()[1]
 if code.isdigit() and 400<=int(code)<=499: 
 raise ValueError(data.split(':')[2])
 elif "End of /NAMES list." in data: #This is the last message before it's all good for some reason
 break
 self._soc.settimeout(_wait) #the threads use this for nonblocking io
 self._closed = False
 tread = threading.Thread(target=self._reader)
 tread.daemon = True
 tread.start()
 twrite = threading.Thread(target=self._writer)
 twrite.daemon = True
 twrite.start()
 def _reader(self):
 '''Message reader thread'''
 while not self._closed:
 try: data = self._soc.recv(4096).decode('UTF-8')
 except socket.timeout: continue
 if 'PING' in data: #so we don't get booted
 self._soc.send ('PONG ' + data.split()[1] + '\r\n')
 else:
 self._readqueue.put(message(data))
 def _writer(self):
 '''Message writer thread'''
 while not self._closed:
 time.sleep(_wait)
 if not self._writequeue.empty():
 msg = self._writequeue.get()
 self._writequeue.task_done()
 self._soc.send("PRIVMSG {} :{}\r\n".format(self.channel, msg).encode('UTF-8'))
 def __next__(self):
 msg = self.read()
 if msg is None:
 raise StopIteration
 else:
 return msg
 #Python2 compatability
 next = __next__
 def __iter__(self):
 return iter(self.read, None)
 def read(self):
 '''Returns the oldest unread message, or None if there are no unread messages.'''
 if self._readqueue.empty():
 return None
 else:
 msg = self._readqueue.get()
 self._readqueue.task_done()
 return msg
 def readall(self, limit=None):
 '''Returns a list of at most <limit> unread messages.
 If limit is unspecified, returns all unread messages.'''
 i = 0
 l = []
 for m in self: #self is an iterable!
 l.append(m)
 i += 1
 if limit is not None and i>=limit:
 break
 return l
 def write(self, msg):
 '''Write a single message to the connected channel'''
 self._writequeue.put(msg)
 def writeall(self, msgs):
 '''Write all messages in an iterable to the connected channel'''
 for m in msgs:
 self.write(m)
 def close(self):
 '''Close the IRC connection'''
 self._closed = True
 time.sleep(2*_wait) #wait for threads to terminate
 self._soc.send("QUIT\r\n".encode('UTF-8'))
 self._soc.close()
 del self._writequeue, self._readqueue
 @property
 def closed(self):
 '''True if the IRC connection is closed'''
 return self._closed
 def __enter__(self):
 return self
 def __exit__(self, exception_type, exception_value, traceback):
 self.close()
 return True
class message(str):
 '''An IRC message.
 Properties:
 - sender: the sender
 - hostname: the hostname of the sender
 '''
 def __new__(cls, raw):
 self = str.__new__(cls, raw.split(':')[2].rstrip())
 raw = raw.split(':')
 self.sender = raw[1].split('!')[0]
 self.hostname = raw[1].split()[0].split('@')[1]
 return self
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Nov 14, 2014 at 15:33
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

It's hard to read this code because it doesn't follow PEP8. The violations that stick in the eye the most:

  • Indentation should be 4 spaces
  • Class names should follow CamelCase (message violates that)
  • Do break lines at :, for example in else: import queue as Queue
  • Put spaces around operators, for example 400 <= int(code) <= 499 instead of 400<=int(code)<=499
  • Put a single blank line in front of method declarations of a class
  • Put a space after #comment in comments

There is a pep8 command line utility in a package with the same name. I recommend to install it and run against your script and correct all reported violations.

Some other tips:

  • You do self._soc.send("........".encode('UTF-8')) a lot. It would be better to add a helper method so that you can do self.send("........") instead.
    • I recommend the same for the self._soc.recv calls, even though you only do that twice, for now.
  • IRC is not a great name for a bot. It doesn't have a single clear purpose as a class should. It looks headed to become a God class, doing everything. And why should it be iterable?
  • Avoid single letter variable names like l in the readall method. The letter l is probably one of the worst possible single letter variables, as it's easy mistake it for the number 1, or capital I. In this example I would recommend items instead.
answered Nov 14, 2014 at 20:34
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.