# -*- coding: utf-8 -*- import socket import threading import re class IRC(threading.Thread): def __init__(self, loggingEnabled = True): super(IRC, self).__init__() self.setDaemon(True) self.connected = False self.loggingEnabled = loggingEnabled self.enabled = True def IsMe(self, umask): if isinstance(umask, UserMask): nick = umask.nick else: nick = umask return nick.lower() == self.myNick.lower() def IsChannel(self, text): return text.startswith('#') def Connect(self, host, port = 6667): '''Etablish a connection to a server''' self._Log('@ Connecting to %s port %d' % (host, port)) self.sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sk.connect((host, port)) self._Log('@ Connected') self.connected = True self.callback('OnConnected') def run(self): buff = '' cr = False while True: try: txt = self.sk.recv(1) except: break if (txt == ''): break elif (txt == '\r'): cr = True elif (cr): cr = False if (txt == '\n'): self._ProcessMsg(buff) buff = '' else: buff += txt self.connected = False self._Log('@ Disconnected') self.callback('OnDisconnected') self.enabled = False def callback(self, name, *parameters): f = getattr(self, name, None) if callable(f): f(*parameters) # self._Log('%s() called' % name) #else: # self._Log('%s() NOT called' % name) def Raw(self, raw): '''Send a raw message to server (don't add \\r\\n)''' self.sk.send(raw + '\r\n') self._Log('> ' + raw) def Send(self, msg, *params): '''Send a message to the server with the specified parameters''' text = msg; last = False for prm in params: if (' ' in prm) and (not last): last = True prm = ':' + prm text += ' ' + prm self.Raw(text.replace('\n',' ').replace('\r','')) def Ident(self, nick, ident = None, realname='Python IRC Lib v0.1 by Mick@el', password = None): '''Identify with nick, password and real name, must be called after Connect()''' self.myNick = nick if not ident: ident = nick self.Send('NICK', nick) self.Send('USER', ident, nick, nick, realname) if password: self.Send('PASS', password) self.start() def Join(self, channel, password=''): '''Join a channel''' if password: self.Send('JOIN', channel, password) else: self.Send('JOIN', channel) def Part(self, channel, reason=''): if reason: self.Send('PART', channel, reason) else: self.Send('PART', channel) def Message(self, target, text): '''Send a message to a nick / channel''' parts = text.split('\n') for part in parts: self.Send('PRIVMSG', target, part) def Notice(self, target, text): '''Send a notice to a nick / channel''' parts = text.split('\n') for part in parts: self.Send('NOTICE', target, part) def Topic(self, chan, newtopic): '''Change the topic of chan to newtopic''' self.Send('TOPIC', chan, newtopic) def Kick(self, chan, user): '''Kick user on chan''' self.Send('KICK', chan, user) def CtcpRequest(self, to, type): self.Message(to, '\1' + type + '\1') def CtcpReply(self, to, type, value): text = '\1' + type if value: text += ' ' + value text += '\1' self.Notice(to, text) def _ProcessMsg(self, text): self._Log('< ' + text) prefix = '' umask = None if (text.startswith(':')): # Prefix parsing pos = text.find(' ') if (pos > 0): prefix = text[1:pos] text = text[pos + 1:] umask = UserMask(prefix) pos = text.index(' ') # Command name if (pos > 0): cmd = text[:pos] prms = text[pos + 1:] else: cmd = text prms = '' # Parameters parsing params = [] last = False for prm in prms.split(' '): if (prm.startswith(':')) and (not last): last = True prm = prm[1:] params.append(prm) elif last: params[len(params)-1] += ' ' + prm else: params.append(prm) cmd = cmd.upper() if (cmd == 'PING'): self.Send('PONG', params[0]) elif (cmd == 'NICK') and (umask != None) and (umask.nick.lower() == self.myNick.lower()): self.myNick = params[0] if (cmd == '001'): # Welcome server message self.callback('OnReady') else: self.callback('OnServer' + cmd.title(), umask, *params) if cmd == 'PRIVMSG': if params[1].startswith('\1') and params[1].endswith('\1'): name = params[1][1:][:-1] self.callback('OnCtcpRequest', umask, name) self.callback('OnCtcp' + name.title() + 'Request', umask) else: self.callback('OnMessage', umask, *params) if self.IsChannel(params[0]): self.callback('OnChannelMessage', umask, *params) elif self.IsMe(params[0]): self.callback('OnPrivateMessage', umask, params[1]) elif cmd == 'NOTICE' and umask != None: if params[1].startswith('\1') and params[1].endswith('\1'): name = params[1][1:][:-1] value = '' pos = name.find(' ') if pos > -1: value = name[pos+1:] name = name[:pos] self.callback('OnCtcpReply', umask, name, value) self.callback('OnCtcp'+ name.title() + 'Reply', umask, value) else: self.callback('OnNotice', umask, *params) if self.IsChannel(params[0]): self.callback('OnChannelNotice', umask, *params) elif self.IsMe(params[0]): self.callback('OnPrivateNotice', umask, params[1]) elif (cmd == 'JOIN' or cmd == 'PART') and umask and self.IsMe(umask): self.callback('OnSelf' + cmd.title(), *params) def _Log(self, txt): if (self.loggingEnabled): print txt class UserMask: maskRegex = re.compile(r'([^!]+)!([^@]+)@(.+)') def __init__(self, mask): self.nick = '' self.user = '' self.host = '' match = self.maskRegex.match(mask) if (match): self.nick, self.user, self.host = match.groups() else: self.nick = mask def __str__(self): return '%s!%s@%s' % (self.nick, self.user, self.host)