From 2011bf9bbd042bba4a649f9e52a52c1149ff09c8 Mon Sep 17 00:00:00 2001 From: Brian Sherson Date: Tue, 27 Aug 2013 23:47:24 -0700 Subject: --- irc.py | 2227 +++++++++++++++++++++++++++++++++++----------------------------- 1 file changed, 1220 insertions(+), 1007 deletions(-) (limited to 'irc.py') diff --git a/irc.py b/irc.py index fa7e7a2..d8d564e 100644 --- a/irc.py +++ b/irc.py @@ -1,1021 +1,1234 @@ #!/usr/bin/python from threading import Thread, Event, Lock -import re, time, sys, string, socket, os, platform, traceback, Queue, ssl +import re +import time +import sys +import string +import socket +import os +import platform +import traceback +import Queue +import ssl -class Connection(Thread): - def __init__(self, server, nick="ircbot", ident="python", realname="Python IRC Library", passwd=None, port=None, ipv6=False, ssl=False, autoreconnect=True, log=sys.stderr, timeout=300, retrysleep=5, maxretries=15, onlogin=None): - self.__name__="pyIRC" - self.__version__="1.0.0rc2" - self.__author__="Brian Sherson" - self.__date__="August 26, 2013" - - if port==None: - self.port=6667 if not ssl else 6697 - else: - self.port=port - - if type(nick) in (str, unicode): self.nick=[nick] - elif type(nick) in (list, tuple): self.nick=nick - - self.realname=realname - self.idnt=ident - self.passwd=passwd - self.server=server - self.ssl=ssl - self.ipv6=ipv6 - self.identity=None - - self.connected=False - self.registered=False - self.connection=None - - self.autoreconnect=autoreconnect - self.maxretries=maxretries - self.timeout=timeout - self.retrysleep=retrysleep - - self.quitexpected=False - self.log=log - - self.modules=[] - self.trusted=[] - - - ### Initialize IRC environment variables - self.motdgreet="" - self.motd=[] - self.identity=None - self.users=[] - self.channels=[] - self.supports={} - - self.lock=Lock() - self.loglock=Lock() - self.sendlock=Lock() - self.outgoing=Queue.Queue() - - Thread.__init__(self) - - def event(self, method, modlist, exceptions=False, **params): - #print method, modlist - timestamp=reduce(lambda x,y: x+":"+y,[str(t).rjust(2,"0") for t in time.localtime()[0:6]]) - for module in modlist: - #print module, dir(module) - #if modlist is not self.modules and module in self.modules: continue - if method in dir(module) and callable(getattr(module, method)): - try: - getattr(module, method)(self, **params) - except: - with self.loglock: - exc,excmsg,tb=sys.exc_info() - print >>self.log, "%(timestamp)s !!! Exception in module %(module)s" % vars() - for tbline in traceback.format_exc().split("\n"): - print >>self.log, "%(timestamp)s !!! %(tbline)s" % vars() - self.log.flush() - if exceptions: ### If set to true, we raise the exception. - raise - - def addModule(self, module, trusted=False, **params): - if module in self.modules: - raise BaseException, "Module already added." - with self.lock: - self.event("onModuleAdd", [module], exceptions=True, **params) - self.modules.append(module) - if trusted: - self.trusted.append(module) - - def insertModule(self, index, module, trusted=False, **params): - if module in self.modules: - raise BaseException, "Module already added." - with self.lock: - self.event("onModuleAdd", [module], exceptions=True, **params) - self.modules.insert(index, module) - if trusted: - self.trusted.append(module) - - def rmModule(self, module, **params): - with self.lock: - self.modules.remove(module) - self.event("onModuleRem", [module], exceptions=True, **params) - if module in self.trusted: - self.trusted.remove(module) - - def run(self): - self.quitexpected=False - server=self.server - if self.ipv6 and ":" in server: - server="[%s]"%server - port=self.port - try: - modules=list(self.modules) - for channel in self.channels: - for module in channel.modules: - if module not in modules: modules.append(module) - with self.lock: - self.event("onLogOpen", modules) - - with self.loglock: - timestamp=reduce(lambda x,y: x+":"+y,[str(t).rjust(2,"0") for t in time.localtime()[0:6]]) - print >>self.log, "%(timestamp)s ### Log session started" % vars() - self.log.flush() - - attempt=1 - while True: ### An entire connection lives within this while loop. When the connection fails, will try to reestablish, unless self.autoreconnect is set to False. - while True: ### Enter retry loop - with self.loglock: - timestamp=reduce(lambda x,y: x+":"+y,[str(t).rjust(2,"0") for t in time.localtime()[0:6]]) - print >>self.log, "%(timestamp)s *** Attempting connection to %(server)s:%(port)s." % vars() - self.log.flush() - - with self.lock: - self.event("onConnectAttempt", self.modules) - - try: - if self.ssl: - s=socket.socket(socket.AF_INET6 if self.ipv6 else socket.AF_INET, socket.SOCK_STREAM) - s.settimeout(self.timeout) - self.connection=ssl.wrap_socket(s, cert_reqs=ssl.CERT_NONE) - else: - self.connection=socket.socket(socket.AF_INET6 if self.ipv6 else socket.AF_INET, socket.SOCK_STREAM) - self.connection.connect((self.server, self.port, 0, 0) if self.ipv6 else (self.server, self.port)) - self.connected=True - self.connection.settimeout(self.timeout) - - ### Setting up thread responsible for sending data back to IRC server. - outgoingthread=Outgoing(self) - outgoingthread.daemon=True - outgoingthread.start() - - ### Run onConnect on all modules to signal connection was established. - modules=list(self.modules) - for channel in self.channels: - for module in channel.modules: - if module not in modules: modules.append(module) - self.event("onConnect", modules) - with self.loglock: - timestamp=reduce(lambda x,y: x+":"+y,[str(t).rjust(2,"0") for t in time.localtime()[0:6]]) - print >>self.log, "%(timestamp)s *** Connection to %(server)s:%(port)s established." % vars() - self.log.flush() - break - except socket.error: - with self.loglock: - exc,excmsg,tb=sys.exc_info() - timestamp=reduce(lambda x,y: x+":"+y,[str(t).rjust(2,"0") for t in time.localtime()[0:6]]) - print >>self.log, "%(timestamp)s *** Connection to %(server)s:%(port)s failed: %(excmsg)s." % vars() - self.log.flush() - - if self.quitexpected: sys.exit() - if attempt>self.log, "%(timestamp)s *** Maximum number of attempts reached. Giving up. (%(server)s:%(port)s)" % vars() - self.log.flush() - break - - ### Connection succeeded - try: - ### Attempt initial registration. - nick=self.nick[0] - trynick=0 - if self.passwd: self.raw("PASS :%(passwd)s" % vars(self)) - self.raw("NICK :%(nick)s" % vars()) - self.raw("USER %(idnt)s * * :%(realname)s" % vars(self)) - - ### Initialize buffers - linebuf=[] - readbuf="" - - while True: ### Main loop of IRC connection. - while len(linebuf)==0: ### Need Moar Data - read=self.connection.recv(512) - - ### If read was empty, connection is terminated. - if read=="": sys.exit() - - ### If read was successful, parse away! - readbuf+=read - lastlf=readbuf.rfind("\n") - if lastlf>=0: - linebuf.extend(string.split(readbuf[0:lastlf], "\n")) - readbuf=readbuf[lastlf+1:] - - line=string.rstrip(linebuf.pop(0)) - - ### If received PING, then just pong back transparently. - ping=re.findall("^PING :?(.*)$",line) - if len(ping): - with self.lock: - self.connection.send("PONG :%s\n" % ping[0]) - continue - - with self.loglock: - timestamp=reduce(lambda x,y: x+":"+y,[str(t).rjust(2,"0") for t in time.localtime()[0:6]]) - print >> self.log, "%(timestamp)s <<< %(line)s" % vars() - self.log.flush() - - ### Attempts to match against pattern ":src cmd target params :extinfo" - matches=re.findall("^:(.+?)(?:!(.+?)@(.+?))?\\s+(.+?)(?:\\s+(.+?)(?:\\s+(.+?))??)??(?:\\s+:(.*))?$",line) - - ### We have a match! - if len(matches): - parsed=(origin, ident, host, cmd, target, params, extinfo)=matches[0] - if re.match("^\\d+$",cmd): cmd=int(cmd) ### Code is a numerical response - else: cmd=cmd.upper() - - if not self.registered: - if type(cmd)==int and target!="*": ### Registration complete! - with self.lock: - self.registered=True - self.identity=self.user(target) - self.serv=origin - - modules=list(self.modules) - for channel in self.channels: - for module in channel.modules: - if module not in modules: modules.append(module) - self.event("onRegistered", modules) - - elif cmd==433 and target=="*": ### Server reports nick taken, so we need to try another. - trynick+=1 - (q,s)=divmod(trynick,len(self.nick)) - nick=self.nick[s] - if q>0: nick+=str(q) - self.raw("NICK :%(nick)s" % vars()) - if not self.registered: ### Registration is not yet complete - continue - - ### Major codeblock here! Track IRC state. - ### Send line to modules first - with self.lock: - self.event("onRecv", self.modules, line=line, data=parsed) - if cmd==1: self.welcome=extinfo # Welcome message - elif cmd==2: self.hostinfo=extinfo # Your Host - elif cmd==3: self.servinfo=extinfo # Server Created - elif cmd==4: self.serv004=params # What is this code? - elif cmd==5: # Server Supports - support=dict(re.findall("([A-Za-z0-9]+)(?:=(\\S*))?",params)) - if support.has_key("CHANMODES"): support["CHANMODES"]=support["CHANMODES"].split(",") - if support.has_key("PREFIX"): - matches=re.findall("\\((.*)\\)(.*)",support["PREFIX"]) - if matches: support["PREFIX"]=matches[0] - else: del support["PREFIX"] # Might as well delete the info if it doesn't match expected pattern - self.supports.update(support) - if "serv005" in dir(self) and type(self.serv005)==list: - self.serv005.append(params) - else: self.serv005=[params] - elif cmd==8: # Channel Modes - self.identity.snomask=params.lstrip("+") - if "s" not in self.identity.modes: self.snomask="" - elif cmd==221: # Channel Modes - self.identity.modes=(params if params else extinfo).lstrip("+") - if "s" not in self.identity.modes: self.snomask="" - elif cmd==251: self.netstats=extinfo - elif cmd==252: self.opcount=int(params) - elif cmd==254: self.chancount=int(params) - elif cmd==311: # WHOIS data - pass - elif cmd==321: # Start LIST - self.chanlistbegin=(params,extinfo) - self.chanlist={} - elif cmd==322: # LIST item - (chan,pop)=params.split(" ",1) - self.chanlist[chan]=(pop,extinfo) - elif cmd==323: self.chanlistend=extinfo # End of LIST - elif cmd==324: # Channel Modes - modeparams=params.split() - channame=modeparams.pop(0) - channel=self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name!=channame: channel.name=channame ### Server seems to have changed the idea of the case of the channel name - setmodes=modeparams.pop(0) - for mode in setmodes: - if mode=="+": continue - elif mode in self.supports["CHANMODES"][2]: - param=modeparams.pop(0) - channel.modes[mode]=param - elif mode in self.supports["CHANMODES"][3]: channel.modes[mode]=True - elif cmd==329: # Channel created - channame, created=params.split() - channel=self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name!=channame: channel.name=channame ### Server seems to have changed the idea of the case of the channel name - channel.created=int(created) - elif cmd==332: # Channel Topic - channame=params - channel=self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name!=channame: channel.name=channame ### Server seems to have changed the idea of the case of the channel name - channel.topic=extinfo - elif cmd==333: # Channel Topic info - (channame,nick,dt)=params.split() - channel=self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name!=channame: channel.name=channame ### Server seems to have changed the idea of the case of the channel name - channel.topicsetby=nick - channel.topictime=dt - elif cmd==346: # Invite - (channame,invite,nick,invtime)=params.split() - channel=self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name!=channame: channel.name=channame ### Server seems to have changed the idea of the case of the channel name - if channel.modes.has_key("I"): - if invite.lower() not in [m.lower() for (m, b, t) in channel.modes["I"]]: channel.modes["I"].append((invite,nick,int(invtime))) - else: channel.modes["I"]=[(invite,nick,int(invtime))] - elif cmd==348: # Ban Exception - (channame,exception,nick,exctime)=params.split() - channel=self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name!=channame: channel.name=channame ### Server seems to have changed the idea of the case of the channel name - if channel.modes.has_key("e"): - if exception.lower() not in [m.lower() for (m, b, t) in channel.modes["e"]]: channel.modes["e"].append((exception, nick, int(exctime))) - else: channel.modes["e"]=[(exception, nick, int(exctime))] - elif cmd==352: # WHO reply - (channame,ident,host,serv,nick,flags)=params.split() - (hops,realname)=extinfo.split(" ",1) - channel=self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name!=channame: channel.name=channame ### Server seems to have changed the idea of the case of the channel name - user=self.user(nick) - if user.nick!=nick: user.nick=nick - user.hops=hops - user.realname=realname - user.idnt=ident - user.host=host - user.server=serv - user.away="G" in flags - user.ircop="*" in flags - if user not in channel.users: channel.users.append(user) - if channel not in user.channels: user.channels.append(channel) - for (mode, prefix) in zip(*self.supports["PREFIX"]): - if prefix in flags: - if mode in channel.modes.keys() and user not in channel.modes[mode]: - channel.modes[mode].append(user) - elif mode not in channel.modes.keys(): - channel.modes[mode]=[user] - elif cmd==353: # NAMES reply - (devnull,channame)=params.split() - channel=self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name!=channame: channel.name=channame ### Server seems to have changed the idea of the case of the channel name - if self.supports.has_key("PREFIX"): - names=re.findall("(["+re.escape(self.supports["PREFIX"][1])+"]*)(\\S+)",extinfo) - else: names=[("",name) for name in extinfo.split()] # Still put it into tuple form for compatibility in the next structure - for (symbs,nick) in names: - user=self.user(nick) - if user.nick!=nick: user.nick=nick - if channel not in user.channels: user.channels.append(channel) - if user not in channel.users: channel.users.append(user) - if self.supports.has_key("PREFIX"): - for symb in symbs: - mode=self.supports["PREFIX"][0][self.supports["PREFIX"][1].index(symb)] - if not channel.modes.has_key(mode): - channel.modes[mode]=[user] - elif user not in channel.modes[mode]: channel.modes[mode].append(user) - elif cmd==367: # Channel Ban - (channame,ban,nick,bantime)=params.split() - channel=self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name!=channame: channel.name=channame ### Server seems to have changed the idea of the case of the channel name - if "b" in channel.modes.keys(): - if ban.lower() not in [m.lower() for (m, b, t) in channel.modes["b"]]: - channel.modes["b"].append((ban,nick,int(bantime))) - else: channel.modes["b"]=[(ban,nick,int(bantime))] - elif cmd==372: self.motd.append(extinfo) # MOTD item - elif cmd==375: # Begin MOTD - self.motdgreet=extinfo - self.motd=[] - elif cmd==376: self.motdend=extinfo # End of MOTD - elif cmd==386 and "q" in self.supports["PREFIX"][0]: # Channel Admin (Unreal) - (channame,admin)=params.split() - channel=self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name!=channame: channel.name=channame ### Server seems to have changed the idea of the case of the channel name - user=self.user(owner) - if user.nick!=owner: user.nick=owner - if channel.modes.has_key("q"): - if user not in channel.modes["q"]: channel.modes["q"].append(user) - else: channel.modes["q"]=[user] - elif cmd==388 and "a" in self.supports["PREFIX"][0]: # Channel Admin (Unreal) - (channame,admin)=params.split() - channel=self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name!=channame: channel.name=channame ### Server seems to have changed the idea of the case of the channel name - user=self.user(admin) - if user.nick!=admin: user.nick=admin - if channel.modes.has_key("a"): - if user not in channel.modes["a"]: channel.modes["a"].append(user) - else: channel.modes["a"]=[user] - elif cmd=="NICK": - user=self.user(origin) - modules=[] - for channel in user.channels: - for module in channel.modules: - if module not in modules: modules.append(module) - self.event(modules, line, parsed) - newnick=extinfo if len(extinfo) else target - #print user, newnick - ### Need to check if newnick is still listed - for u in self.users: - #print u - #print [u.nick, newnick, u.nick.lower(), newnick.lower()] - if u.nick.lower()==newnick.lower(): - with self.loglock: - print >>self.log, "%s *** Orphaned user %s!%s@%s was removed when %s!%s@%s changed his/her nick to %s."%(timestamp, u.nick, u.idnt, u.host, user.nick, user.idnt, user.host, newnick) - self.log.flush() - self.users.remove(u) - for channel in self.channels: - if u in channel.users: - channel.users.remove(u) - user.nick=newnick - elif cmd=="JOIN": - channame=target if len(target) else extinfo - user=self.user(origin) - if user.nick!=origin: user.nick=origin - if user.idnt!=ident: user.idnt=ident - if user.host!=host: user.host=host - - channel=self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name!=channame: channel.name=channame ### Server seems to have changed the idea of the case of the channel name - - if user==self.identity: # This means the bot is entering the room, - # and will reset all the channel data, on the assumption that such data may have changed. - # Also, the bot must request modes - channel.topic="" - channel.topicmod="" - channel.modes={} - channel.users=[] - self.raw("MODE %(channame)s" % vars()) - self.raw("WHO %(channame)s" % vars()) - self.raw("MODE %s :%s" % (channame, self.supports["CHANMODES"][0])) - if channel not in user.channels: user.channels.append(channel) - if user not in channel.users: channel.users.append(user) - elif cmd=="KICK": - kicker=self.user(origin) - if kicker.nick!=origin: kicker.nick=origin - if kicker.idnt!=ident: kicker.idnt=ident - if kicker.host!=host: kicker.host=host - - kicked=self.user(params) - if kicked.nick!=params: kicked.nick=params - - channel=self.channel(target) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name!=target: channel.name=target ### Server seems to have changed the idea of the case of the channel name - if channel in kicked.channels: kicked.channels.remove(channel) - if kicked in channel.users: channel.users.remove(kicked) - if self.supports.has_key("PREFIX"): - for mode in self.supports["PREFIX"][0]: - if channel.modes.has_key(mode) and kicked in channel.modes[mode]: channel.modes[mode].remove(kicked) - #if not len(chanobj.users): #Let's remove this channel - # del self.channels[target.lower()] - if all([kicked not in c.users for c in self.identity.channels]): - with self.loglock: - print >>self.log, "%s *** User %s!%s@%s was orphaned when being kicked from %s."%(timestamp, kicked.nick, kicked.idnt, kicked.host, channel.name) - self.log.flush() - elif cmd=="PART": - user=self.user(origin) - if user.nick!=origin: user.nick=origin - if user.idnt!=ident: user.idnt=ident - if user.host!=host: user.host=host - - channel=self.channel(target) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name!=target: channel.name=target ### Server seems to have changed the idea of the case of the channel name - - if channel in user.channels: user.channels.remove(channel) - if user in channel.users: channel.users.remove(user) - if self.supports.has_key("PREFIX"): - for mode in self.supports["PREFIX"][0]: - if channel.modes.has_key(mode) and user in channel.modes[mode]: channel.modes[mode].remove(user) - if all([user not in c.users for c in self.identity.channels]): - with self.loglock: - print >>self.log, "%s *** User %s!%s@%s was orphaned when parting %s."%(timestamp, user.nick, user.idnt, user.host, channel.name) - self.log.flush() - elif cmd=="QUIT": - user=self.user(origin) - if user.nick!=origin: user.nick=origin - if user.idnt!=ident: user.idnt=ident - if user.host!=host: user.host=host - channels=list(user.channels) - for channel in user.channels: - for module in channel.modules: - if module not in modules: modules.append(module) - self.event(modules, line, parsed) - for channel in channels: - channel.lock.acquire(True) - for channel in user.channels: - if user in channel.users: channel.users.remove(user) - if self.supports.has_key("PREFIX"): - for mode in self.supports["PREFIX"][0]: - if channel.modes.has_key(mode) and user in channel.modes[mode]: channel.modes[mode].remove(user) - #if not len(chanobj.users): #Let's remove this channel - # del self.channels[chan] - # On second thought, no, since we may want to save certain info - user.channels=[] - for channel in channels: - channel.lock.release() - print >>self.log, "%s *** User %s!%s@%s was orphaned when quitting IRC."%(timestamp, user.nick, user.idnt, user.host) - elif cmd=="MODE": - if target[0] in self.supports["CHANTYPES"]: - user=self.user(origin) - if user.nick!=origin: user.nick=origin - if user.idnt!=ident: user.idnt=ident - if user.host!=host: user.host=host - - channel=self.channel(target) - self.event("onRecv", channel.modules, line=line, data=parsed) - with channel.lock: - if channel.name!=target: channel.name=target ### Server seems to have changed the idea of the case of the channel name - - modeparams=params.split() - setmodes=modeparams.pop(0) - modeset="+" - for mode in setmodes: - if mode in "+-": modeset=mode - else: - if mode in self.supports["CHANMODES"][0]: - param=modeparams.pop(0) - if modeset=="+": - if channel.modes.has_key(mode): - if param.lower() not in [mask.lower() for (mask, setby, settime) in channel.modes[mode]]: channel.modes[mode].append((param, origin, int(time.time()))) - else: channel.modes[mode]=[(param, origin, int(time.time()))] - else: - if mode in channel.modes.keys(): - if mode=="b": ### Inspircd mode is case insentive when unsetting the mode - masks=[mask.lower() for (mask, setby, settime) in channel.modes[mode]] - if param.lower() in masks: - index=masks.index(param.lower()) - #print "Index: %d"%index - del channel.modes[mode][index] - else: - masks=[mask for (mask, setby, settime) in channel.modes[mode]] - if param in masks: - index=masks.index(param) - del channel.modes[mode][index] - elif mode in self.supports["CHANMODES"][1]: - param=modeparams.pop(0) - if modeset=="+": channel.modes[mode]=param - else: channel.modes[mode]=None - elif mode in self.supports["CHANMODES"][2]: - if modeset=="+": - param=modeparams.pop(0) - channel.modes[mode]=param - else: channel.modes[mode]=None - elif mode in self.supports["CHANMODES"][3]: - if modeset=="+": channel.modes[mode]=True - else: channel.modes[mode]=False - elif self.supports.has_key("PREFIX") and mode in self.supports["PREFIX"][0]: - modenick=modeparams.pop(0) - modeuser=self.user(modenick) - if modeuser.nick!=modenick: modeuser.nick=modenick - if modeset=="+": - if channel.modes.has_key(mode) and modeuser not in channel.modes[mode]: channel.modes[mode].append(modeuser) - if not channel.modes.has_key(mode): channel.modes[mode]=[modeuser] - elif channel.modes.has_key(mode) and modeuser in channel.modes[mode]: channel.modes[mode].remove(modeuser) - else: - user=self.user(target) - modeparams=(params if params else extinfo).split() - setmodes=modeparams.pop(0) - modeset="+" - for mode in setmodes: - if mode in "+-": - modeset=mode - continue - if modeset=="+": - if mode not in user.modes: user.modes+=mode - if mode=="s" and len(modeparams): - snomask=modeparams.pop(0) - for snomode in snomask: - if snomode in "+-": - snomodeset=snomode - continue - if snomodeset=="+" and snomode not in user.snomask: user.snomask+=snomode - if snomodeset=="-" and snomode in user.snomask: user.snomask=user.snomask.replace(snomode,"") - if modeset=="-": - if mode in user.modes: user.modes=user.modes.replace(mode,"") - if mode=="s": user.snomask="" - elif cmd=="TOPIC": - user=self.user(origin) - if user.nick!=origin: user.nick=origin - if user.idnt!=ident: user.idnt=ident - if user.host!=host: user.host=host - - channel=self.channel(target) - self.event("onRecv", channel.modules, line=line, data=parsed) - - with channel.lock: - if channel.name!=target: channel.name=target ### Server seems to have changed the idea of the case of the channel name - channel.topic=extinfo - elif cmd=="PRIVMSG": - user=self.user(origin) - if user.nick!=origin: user.nick=origin - if user.idnt!=ident: user.idnt=ident - if user.host!=host: user.host=host - - if target[0] in self.supports["CHANTYPES"]: - channel=self.channel(target) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name!=target: channel.name=target ### Server seems to have changed the idea of the case of the channel name - elif target[0]=="$": - pass ### Server message -- Not implemented - else: - targetuser=self.user(target) - if targetuser.nick!=target: targetuser.nick=target ### Server seems to have changed the idea of the case of the nickname - - ### CTCP handling - ctcp=re.findall("^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$",extinfo) - if ctcp: - (ctcptype,ext)=ctcp[0] - if ctcptype.upper()=="VERSION": - user.ctcpreply("VERSION",self.ctcpversion()) - if ctcptype.upper()=="TIME": - tformat=time.ctime() - tz=time.tzname[0] - user.ctcpreply("TIME","%(tformat)s %(tz)s" % vars()) - if ctcptype.upper()=="PING": user.ctcpreply("PING","%(ext)s" % vars()) - if ctcptype.upper()=="FINGER": user.ctcpreply("FINGER","%(ext)s" % vars()) - elif cmd==910: # Channel Access List - (channame,mask,setby,settime)=params.split() - channel=self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name!=channame: channel.name=channame ### Server seems to have changed the idea of the case of the channel name - if "w" in channel.modes.keys(): - if mask not in [m for (m, b, t) in channel.modes["w"]]: channel.modes["w"].append((mask, setby, int(settime))) - else: channel.modes["w"]=[(mask, setby, int(settime))] - elif cmd==941: # Channel spamfilter List - (channame,mask,setby,settime)=params.split() - channel=self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name!=channame: channel.name=channame ### Server seems to have changed the idea of the case of the channel name - if "g" in channel.modes.keys(): - if mask not in [m for (m, b, t) in channel.modes["g"]]: channel.modes["g"].append((mask, setby, int(settime))) - else: channel.modes["g"]=[(mask, setby, int(settime))] - elif cmd==954: # Channel spamfilter List - (channame,mask,setby,settime)=params.split() - channel=self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name!=channame: channel.name=channame ### Server seems to have changed the idea of the case of the channel name - if "X" in channel.modes.keys(): - if mask not in [m for (m, b, t) in channel.modes["X"]]: channel.modes["X"].append((mask, setby, int(settime))) - else: channel.modes["X"]=[(mask, setby, int(settime))] - elif cmd in (495, 384, 385, 386, 468, 470, 366, 315, 482, 484, 953, 368, 482, 349, 940, 911, 489, 490, 492, 520, 530): # Channels which appear in params - for param in params.split(): - if len(param) and param[0] in self.supports["CHANTYPES"]: - channel=self.channel(param) - self.event("onRecv", channel.modules, line=line, data=parsed) - - else: ### Line does NOT match ":src cmd target params :extinfo" - self.event("onRecv", self.modules, line=line, data=None) - except SystemExit: ### Connection lost normally. - pass - except socket.error: ### Connection lost due to either ping timeout or connection reset by peer. Not a fatal error. - with self.loglock: - exc,excmsg,tb=sys.exc_info() - timestamp=reduce(lambda x,y: x+":"+y,[str(t).rjust(2,"0") for t in time.localtime()[0:6]]) - print >>self.log, "%(timestamp)s *** Connection to %(server)s:%(port)s failed: %(excmsg)s." % vars() - self.log.flush() - except: ### Unknown exception, treated as FATAL. Try to quit IRC and terminate thread with exception. - ### Quit with a (hopefully) useful quit message, or die trying. - try: - self.quit("%s" % traceback.format_exc().rstrip().split("\n")[-1]) - except: pass - raise - finally: ### Post-connection operations after connection is lost, and must be executed, even if exception occurred. - with self.lock: - modules=list(self.modules) - for channel in self.channels: - for module in channel.modules: - if module not in modules: modules.append(module) - self.event("onDisconnect", self.modules) - - ### Tell outgoing thread to quit. - self.outgoing.put("quit") - - ### Wait until the outgoing thread dies. - outgoingthread.join() - - self.connected=False - self.registered=False - - try: self.connection.close() - except: pass - - with self.loglock: - timestamp=reduce(lambda x,y: x+":"+y,[str(t).rjust(2,"0") for t in time.localtime()[0:6]]) - print >>self.log, "%(timestamp)s *** Connection Terminated." % vars() - self.log.flush() - - if self.quitexpected or not self.autoreconnect: sys.exit() - - ### If we make it to this point, then it is because connection was lost unexpectedly, and will attempt to reconnect if self.autoreconnect is True. - time.sleep(self.retrysleep) - - except SystemExit: - pass - - except: ### Print exception to log file - with self.loglock: - timestamp=reduce(lambda x,y: x+":"+y,[str(t).rjust(2,"0") for t in time.localtime()[0:6]]) - print >>self.log, "%(timestamp)s !!! Fatal Exception" % vars() - for tbline in traceback.format_exc().split("\n"): - print >>self.log, "%(timestamp)s !!! %(tbline)s" % vars() - self.log.flush() - sys.exit() - - finally: - with self.loglock: - timestamp=reduce(lambda x,y: x+":"+y,[str(t).rjust(2,"0") for t in time.localtime()[0:6]]) - print >>self.log, "%(timestamp)s ### Log session ended" % vars() - self.log.flush() - - Thread.__init__(self) ### Makes thread restartable - - def __repr__(self): - server=self.server - if self.ipv6 and ":" in server: - server="[%s]"%server - port=self.port - if self.identity: - nick=self.identity.nick - ident=self.identity.idnt if self.identity.idnt else "*" - host=self.identity.host if self.identity.host else "*" - else: - nick="*" - ident="*" - host="*" - if self.ssl and self.ipv6: - protocol="ircs6" - elif self.ssl: - protocol="ircs" - elif self.ipv6: - protocol="irc6" - else: - protocol="irc" - return "" % locals() - #else: return "" % locals() - def quit(self, msg="", origin=None): - with self.lock: - self.quitexpected=True - if self.connected: - if len(msg): self.raw("QUIT :%(msg)s" % vars(), origin=origin) - else: self.raw("QUIT", origin=origin) - def ctcpversion(self): - reply=[] - ### Prepare reply for module - reply.append("%(__name__)s %(__version__)s, %(__author__)s" % vars(self)) - - ### Prepare reply for Python and OS versions - pyver=sys.version.split("\n") - pyver[0]="Python "+pyver[0] - reply.extend(pyver) - reply.extend(platform.platform().split("\n")) - ### Prepare reply for extension modules - for module in self.modules: - try: - r="%(__name__)s %(__version__)s" % vars(module) - if "__extinfo__" in vars(module): r+=", %(__extinfo__)s" % vars() - reply.append(r) - except: - pass - return reduce(lambda x, y: "%s; %s" % (x,y),reply) - - def raw(self, line, origin=None): - self.outgoing.put((line, origin)) - - - def user(self,nick): - users=[user for user in self.users if user.nick.lower()==nick.lower()] - if len(users): - return users[0] - else: - user=User(nick,self) - self.users.append(user) - timestamp=reduce(lambda x,y: x+":"+y,[str(t).rjust(2,"0") for t in time.localtime()[0:6]]) - with self.loglock: - print >>self.log, "%s *** User %s created."%(timestamp, nick) - self.log.flush() - return user - def channel(self,name): - channels=[chan for chan in self.channels if name.lower()==chan.name.lower()] - if len(channels): - return channels[0] - else: - timestamp=reduce(lambda x,y: x+":"+y,[str(t).rjust(2,"0") for t in time.localtime()[0:6]]) - chan=Channel(name,self) - self.channels.append(chan) - with self.loglock: - print >>self.log, "%s *** Channel %s created."%(timestamp, name) - self.log.flush() - return chan +class Connection(Thread): + def __init__(self, server, nick="ircbot", ident="python", realname="Python IRC Library", passwd=None, port=None, ipv6=False, ssl=False, autoreconnect=True, log=sys.stderr, timeout=300, retrysleep=5, maxretries=15, onlogin=None): + self.__name__ = "pyIRC" + self.__version__ = "1.0.0rc2" + self.__author__ = "Brian Sherson" + self.__date__ = "August 26, 2013" + + if port is None: + self.port = 6667 if not ssl else 6697 + else: + self.port = port + + if type(nick) in (str, unicode): + self.nick = [nick] + elif type(nick) in (list, tuple): + self.nick = nick + + self.realname = realname + self.idnt = ident + self.passwd = passwd + self.server = server + self.ssl = ssl + self.ipv6 = ipv6 + + self.connected = False + self.registered = False + self.connection = None + + self.autoreconnect = autoreconnect + self.maxretries = maxretries + self.timeout = timeout + self.retrysleep = retrysleep + + self.quitexpected = False + self.log = log + + self.modules = [] + self.trusted = [] + + ### Initialize IRC environment variables + self.motdgreet = "" + self.motd = [] + self.identity = None + self.users = [] + self.channels = [] + self.supports = {} + + self.lock = Lock() + self.loglock = Lock() + self.sendlock = Lock() + self.outgoing = Queue.Queue() + + Thread.__init__(self) + + def event(self, method, modlist, exceptions=False, **params): + #print method, modlist + timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, + "0") for t in time.localtime()[0:6]]) + for module in modlist: + #print module, dir(module) + #if modlist is not self.modules and module in self.modules: continue + if method in dir(module) and callable(getattr(module, method)): + try: + getattr(module, method)(self, **params) + except: + with self.loglock: + exc, excmsg, tb = sys.exc_info() + print >>self.log, "%(timestamp)s !!! Exception in module %(module)s" % vars() + for tbline in traceback.format_exc().split("\n"): + print >>self.log, "%(timestamp)s !!! %(tbline)s" % vars() + self.log.flush() + if exceptions: # If set to true, we raise the exception. + raise + + def addModule(self, module, trusted=False, **params): + if module in self.modules: + raise BaseException("Module already added.") + with self.lock: + self.event("onModuleAdd", [module], exceptions=True, **params) + self.modules.append(module) + if trusted: + self.trusted.append(module) + + def insertModule(self, index, module, trusted=False, **params): + if module in self.modules: + raise BaseException("Module already added.") + with self.lock: + self.event("onModuleAdd", [module], exceptions=True, **params) + self.modules.insert(index, module) + if trusted: + self.trusted.append(module) + + def rmModule(self, module, **params): + with self.lock: + self.modules.remove(module) + self.event("onModuleRem", [module], exceptions=True, **params) + if module in self.trusted: + self.trusted.remove(module) + + def run(self): + self.quitexpected = False + outgoingthread = None + server = self.server + if self.ipv6 and ":" in server: + server = "[%s]"%server + port = self.port + try: + modules = list(self.modules) + for channel in self.channels: + for module in channel.modules: + if module not in modules: + modules.append(module) + with self.lock: + self.event("onLogOpen", modules) + + with self.loglock: + timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust( + 2, "0") for t in time.localtime()[0:6]]) + print >>self.log, "%(timestamp)s ### Log session started" % vars() + self.log.flush() + + attempt = 1 + while True: # An entire connection lives within this while loop. When the connection fails, will try to reestablish, unless self.autoreconnect is set to False. + while True: # Enter retry loop + with self.loglock: + timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) + print >>self.log, "%(timestamp)s *** Attempting connection to %(server)s:%(port)s." % vars() + self.log.flush() + + with self.lock: + self.event("onConnectAttempt", self.modules) + + try: + if self.ssl: + s = socket.socket(socket.AF_INET6 if self.ipv6 else socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(self.timeout) + self.connection = ssl.wrap_socket(s, cert_reqs=ssl.CERT_NONE) + else: + self.connection = socket.socket(socket.AF_INET6 if self.ipv6 else socket.AF_INET, socket.SOCK_STREAM) + self.connection.connect((self.server, self.port, 0, 0) if self.ipv6 else (self.server, self.port)) + self.connected = True + self.connection.settimeout(self.timeout) + + ### Setting up thread responsible for sending data back to IRC server. + outgoingthread = Outgoing(self) + outgoingthread.daemon = True + outgoingthread.start() + + ### Run onConnect on all modules to signal connection was established. + modules = list(self.modules) + for channel in self.channels: + for module in channel.modules: + if module not in modules: + modules.append(module) + self.event("onConnect", modules) + with self.loglock: + timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) + print >>self.log, "%(timestamp)s *** Connection to %(server)s:%(port)s established." % vars() + self.log.flush() + break + except socket.error: + with self.loglock: + exc, excmsg, tb = sys.exc_info() + timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) + print >>self.log, "%(timestamp)s *** Connection to %(server)s:%(port)s failed: %(excmsg)s." % vars() + self.log.flush() + + if self.quitexpected: + sys.exit() + if attempt < self.maxretries or self.maxretries == -1: + time.sleep(self.retrysleep) + attempt += 1 + else: + with self.loglock: + timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) + print >>self.log, "%(timestamp)s *** Maximum number of attempts reached. Giving up. (%(server)s:%(port)s)" % vars() + self.log.flush() + break + + ### Connection succeeded + try: + ### Attempt initial registration. + nick = self.nick[0] + trynick = 0 + if self.passwd: + self.raw("PASS :%(passwd)s" % vars(self)) + self.raw("NICK :%(nick)s" % vars()) + self.raw("USER %(idnt)s * * :%(realname)s" % vars(self)) + + ### Initialize buffers + linebuf = [] + readbuf = "" + + while True: # Main loop of IRC connection. + while len(linebuf) == 0: # Need Moar Data + read = self.connection.recv(512) + + ### If read was empty, connection is terminated. + if read == "": + sys.exit() + + ### If read was successful, parse away! + readbuf += read + lastlf = readbuf.rfind("\n") + if lastlf >= 0: + linebuf.extend(string.split(readbuf[0:lastlf], + "\n")) + readbuf = readbuf[lastlf+1:] + + line = string.rstrip(linebuf.pop(0)) + + ### If received PING, then just pong back transparently. + ping = re.findall("^PING :?(.*)$", line) + if len(ping): + with self.lock: + self.connection.send("PONG :%s\n" % ping[0]) + continue + + with self.loglock: + timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) + print >> self.log, "%(timestamp)s <<< %(line)s" % vars() + self.log.flush() + + ### Attempts to match against pattern ":src cmd target params :extinfo" + matches = re.findall("^:(.+?)(?:!(.+?)@(.+?))?\\s+(.+?)(?:\\s+(.+?)(?:\\s+(.+?))??)??(?:\\s+:(.*))?$", line) + + ### We have a match! + if len(matches): + parsed = (origin, ident, host, cmd, target, params, extinfo) = matches[0] + if re.match("^\\d+$", cmd): + cmd = int(cmd) # Code is a numerical response + else: + cmd = cmd.upper() + + if not self.registered: + if type(cmd) == int and target != "*": # Registration complete! + with self.lock: + self.registered = True + self.identity = self.user(target) + self.serv = origin + + modules = list(self.modules) + for channel in self.channels: + for module in channel.modules: + if module not in modules: + modules.append(module) + self.event("onRegistered", modules) + + elif cmd == 433 and target == "*": # Server reports nick taken, so we need to try another. + trynick += 1 + (q, s) = divmod(trynick, len(self.nick)) + nick = self.nick[s] + if q > 0: + nick += str(q) + self.raw("NICK :%(nick)s" % vars()) + if not self.registered: # Registration is not yet complete + continue + + ### Major codeblock here! Track IRC state. + ### Send line to modules first + with self.lock: + self.event("onRecv", self.modules, line=line, data=parsed) + if cmd == 1: + self.welcome = extinfo # Welcome message + elif cmd == 2: + self.hostinfo = extinfo # Your Host + elif cmd == 3: + self.servinfo = extinfo # Server Created + elif cmd == 4: + self.serv004 = params # What is this code? + elif cmd == 5: # Server Supports + support = dict(re.findall("([A-Za-z0-9]+)(?:=(\\S*))?", params)) + if "CHANMODES" in support: + support["CHANMODES"] = support["CHANMODES"].split(",") + if "PREFIX" in support: + matches = re.findall("\\((.*)\\)(.*)", support["PREFIX"]) + if matches: + support["PREFIX"] = matches[0] + else: + del support["PREFIX"] # Might as well delete the info if it doesn't match expected pattern + self.supports.update(support) + if "serv005" in dir(self) and type(self.serv005) == list: + self.serv005.append(params) + else: + self.serv005 = [params] + elif cmd == 8: # Channel Modes + self.identity.snomask = params.lstrip("+") + if "s" not in self.identity.modes: + self.snomask = "" + elif cmd == 221: # Channel Modes + self.identity.modes = (params if params else extinfo).lstrip("+") + if "s" not in self.identity.modes: + self.snomask = "" + elif cmd == 251: + self.netstats = extinfo + elif cmd == 252: + self.opcount = int(params) + elif cmd == 254: + self.chancount = int(params) + elif cmd == 311: # WHOIS data + pass + elif cmd == 321: # Start LIST + self.chanlistbegin = (params, extinfo) + self.chanlist = {} + elif cmd == 322: # LIST item + (chan, pop) = params.split(" ", 1) + self.chanlist[chan] = (pop, extinfo) + elif cmd == 323: + self.chanlistend = extinfo # End of LIST + elif cmd == 324: # Channel Modes + modeparams = params.split() + channame = modeparams.pop(0) + channel = self.channel(channame) + self.event("onRecv", channel.modules, line=line, data=parsed) + if channel.name != channame: + channel.name = channame # Server seems to have changed the idea of the case of the channel name + setmodes = modeparams.pop(0) + for mode in setmodes: + if mode == "+": + continue + elif mode in self.supports["CHANMODES"][2]: + param = modeparams.pop(0) + channel.modes[mode] = param + elif mode in self.supports["CHANMODES"][3]: + channel.modes[mode] = True + elif cmd == 329: # Channel created + channame, created = params.split() + channel = self.channel(channame) + self.event("onRecv", channel.modules, line=line, data=parsed) + if channel.name != channame: + channel.name = channame # Server seems to have changed the idea of the case of the channel name + channel.created = int(created) + elif cmd == 332: # Channel Topic + channame = params + channel = self.channel(channame) + self.event("onRecv", channel.modules, line=line, data=parsed) + if channel.name != channame: + channel.name = channame # Server seems to have changed the idea of the case of the channel name + channel.topic = extinfo + elif cmd == 333: # Channel Topic info + (channame, nick, dt) = params.split() + channel = self.channel(channame) + self.event("onRecv", channel.modules, line=line, data=parsed) + if channel.name != channame: + channel.name = channame # Server seems to have changed the idea of the case of the channel name + channel.topicsetby = nick + channel.topictime = dt + elif cmd == 346: # Invite + (channame, invite, nick, invtime) = params.split() + channel = self.channel(channame) + self.event("onRecv", channel.modules, line=line, data=parsed) + if channel.name != channame: + channel.name = channame # Server seems to have changed the idea of the case of the channel name + if "I" in channel.modes: + if invite.lower() not in [m.lower() for (m, b, t) in channel.modes["I"]]: + channel.modes["I"].append((invite, nick, int(invtime))) + else: + channel.modes["I"] = [(invite, nick, int(invtime))] + elif cmd == 348: # Ban Exception + (channame, exception, nick, exctime) = params.split() + channel = self.channel(channame) + self.event("onRecv", channel.modules, line=line, data=parsed) + if channel.name != channame: + channel.name = channame # Server seems to have changed the idea of the case of the channel name + if "e" in channel.modes: + if exception.lower() not in [m.lower() for (m, b, t) in channel.modes["e"]]: + channel.modes["e"].append((exception, nick, int(exctime))) + else: + channel.modes["e"] = [(exception, nick, int(exctime))] + elif cmd == 352: # WHO reply + (channame, ident, host, serv, nick, flags) = params.split() + (hops, realname) = extinfo.split(" ", 1) + channel = self.channel(channame) + self.event("onRecv", channel.modules, line=line, data=parsed) + if channel.name != channame: + channel.name = channame # Server seems to have changed the idea of the case of the channel name + user = self.user(nick) + if user.nick != nick: + user.nick = nick + user.hops = hops + user.realname = realname + user.idnt = ident + user.host = host + user.server = serv + user.away = "G" in flags + user.ircop = "*" in flags + if user not in channel.users: + channel.users.append(user) + if channel not in user.channels: + user.channels.append(channel) + for (mode, prefix) in zip(*self.supports["PREFIX"]): + if prefix in flags: + if mode in channel.modes.keys() and user not in channel.modes[mode]: + channel.modes[mode].append(user) + elif mode not in channel.modes.keys(): + channel.modes[mode] = [user] + elif cmd == 353: # NAMES reply + (devnull, channame) = params.split() + channel = self.channel(channame) + self.event("onRecv", channel.modules, line=line, data=parsed) + if channel.name != channame: + channel.name = channame # Server seems to have changed the idea of the case of the channel name + if "PREFIX" in self.supports: + names = re.findall("(["+re.escape(self.supports["PREFIX"][1])+"]*)(\\S+)", extinfo) + else: + names = [("", name) for name in extinfo.split()] # Still put it into tuple form for compatibility in the next structure + for (symbs, nick) in names: + user = self.user(nick) + if user.nick != nick: + user.nick = nick + if channel not in user.channels: + user.channels.append(channel) + if user not in channel.users: + channel.users.append(user) + if "PREFIX" in self.supports: + for symb in symbs: + mode = self.supports["PREFIX"][0][self.supports["PREFIX"][1].index(symb)] + if mode not in channel.modes: + channel.modes[mode] = [user] + elif user not in channel.modes[mode]: + channel.modes[mode].append(user) + elif cmd == 367: # Channel Ban + (channame, ban, nick, bantime) = params.split() + channel = self.channel(channame) + self.event("onRecv", channel.modules, line=line, data=parsed) + if channel.name != channame: + channel.name = channame # Server seems to have changed the idea of the case of the channel name + if "b" in channel.modes.keys(): + if ban.lower() not in [m.lower() for (m, b, t) in channel.modes["b"]]: + channel.modes["b"].append((ban, nick, int(bantime))) + else: + channel.modes["b"] = [(ban, nick, int(bantime))] + elif cmd == 372: + self.motd.append(extinfo) # MOTD item + elif cmd == 375: # Begin MOTD + self.motdgreet = extinfo + self.motd = [] + elif cmd == 376: + self.motdend = extinfo # End of MOTD + elif cmd == 386 and "q" in self.supports["PREFIX"][0]: # Channel Admin (Unreal) + (channame, admin) = params.split() + channel = self.channel(channame) + self.event("onRecv", channel.modules, line=line, data=parsed) + if channel.name != channame: + channel.name = channame # Server seems to have changed the idea of the case of the channel name + user = self.user(owner) + if user.nick != owner: + user.nick = owner + if "q" in channel.modes: + if user not in channel.modes["q"]: + channel.modes["q"].append(user) + else: + channel.modes["q"] = [user] + elif cmd == 388 and "a" in self.supports["PREFIX"][0]: # Channel Admin (Unreal) + (channame, admin) = params.split() + channel = self.channel(channame) + self.event("onRecv", channel.modules, line=line, data=parsed) + if channel.name != channame: + channel.name = channame # Server seems to have changed the idea of the case of the channel name + user = self.user(admin) + if user.nick != admin: + user.nick = admin + if "a" in channel.modes: + if user not in channel.modes["a"]: + channel.modes["a"].append(user) + else: + channel.modes["a"] = [user] + elif cmd == "NICK": + user = self.user(origin) + modules = [] + for channel in user.channels: + for module in channel.modules: + if module not in modules: + modules.append(module) + self.event(modules, line, parsed) + newnick = extinfo if len(extinfo) else target + #print user, newnick + ### Need to check if newnick is still listed + for u in self.users: + #print u + #print [u.nick, newnick, u.nick.lower(), newnick.lower()] + if u.nick.lower() == newnick.lower(): + with self.loglock: + print >>self.log, "%s *** Orphaned user %s!%s@%s was removed when %s!%s@%s changed his/her nick to %s."%(timestamp, u.nick, u.idnt, u.host, user.nick, user.idnt, user.host, newnick) + self.log.flush() + self.users.remove(u) + for channel in self.channels: + if u in channel.users: + channel.users.remove(u) + user.nick = newnick + elif cmd == "JOIN": + channame = target if len(target) else extinfo + user = self.user(origin) + if user.nick != origin: + user.nick = origin + if user.idnt != ident: + user.idnt = ident + if user.host != host: + user.host = host + + channel = self.channel(channame) + self.event("onRecv", channel.modules, line=line, data=parsed) + if channel.name != channame: + channel.name = channame # Server seems to have changed the idea of the case of the channel name + + if user == self.identity: # This means the bot is entering the room, + # and will reset all the channel data, on the assumption that such data may have changed. + # Also, the bot must request modes + channel.topic = "" + channel.topicmod = "" + channel.modes = {} + channel.users = [] + self.raw("MODE %(channame)s" % vars()) + self.raw("WHO %(channame)s" % vars()) + self.raw("MODE %s :%s" % (channame, self.supports["CHANMODES"][0])) + if channel not in user.channels: + user.channels.append(channel) + if user not in channel.users: + channel.users.append(user) + elif cmd == "KICK": + kicker = self.user(origin) + if kicker.nick != origin: + kicker.nick = origin + if kicker.idnt != ident: + kicker.idnt = ident + if kicker.host != host: + kicker.host = host + + kicked = self.user(params) + if kicked.nick != params: + kicked.nick = params + + channel = self.channel(target) + self.event("onRecv", channel.modules, line=line, data=parsed) + if channel.name != target: + channel.name = target # Server seems to have changed the idea of the case of the channel name + if channel in kicked.channels: + kicked.channels.remove(channel) + if kicked in channel.users: + channel.users.remove(kicked) + if "PREFIX" in self.supports: + for mode in self.supports["PREFIX"][0]: + if mode in channel.modes and kicked in channel.modes[mode]: + channel.modes[mode].remove(kicked) + #if not len(chanobj.users): #Let's remove this channel + # del self.channels[target.lower()] + if all([kicked not in c.users for c in self.identity.channels]): + with self.loglock: + print >>self.log, "%s *** User %s!%s@%s was orphaned when being kicked from %s."%(timestamp, kicked.nick, kicked.idnt, kicked.host, channel.name) + self.log.flush() + elif cmd == "PART": + user = self.user(origin) + if user.nick != origin: + user.nick = origin + if user.idnt != ident: + user.idnt = ident + if user.host != host: + user.host = host + + channel = self.channel(target) + self.event("onRecv", channel.modules, line=line, data=parsed) + if channel.name != target: + channel.name = target # Server seems to have changed the idea of the case of the channel name + + if channel in user.channels: + user.channels.remove(channel) + if user in channel.users: + channel.users.remove(user) + if "PREFIX" in self.supports: + for mode in self.supports["PREFIX"][0]: + if mode in channel.modes and user in channel.modes[mode]: + channel.modes[mode].remove(user) + if all([user not in c.users for c in self.identity.channels]): + with self.loglock: + print >>self.log, "%s *** User %s!%s@%s was orphaned when parting %s."%(timestamp, user.nick, user.idnt, user.host, channel.name) + self.log.flush() + elif cmd == "QUIT": + user = self.user(origin) + if user.nick != origin: + user.nick = origin + if user.idnt != ident: + user.idnt = ident + if user.host != host: + user.host = host + channels = list(user.channels) + for channel in user.channels: + for module in channel.modules: + if module not in modules: + modules.append(module) + self.event(modules, line, parsed) + for channel in channels: + channel.lock.acquire(True) + for channel in user.channels: + if user in channel.users: + channel.users.remove(user) + if "PREFIX" in self.supports: + for mode in self.supports["PREFIX"][0]: + if mode in channel.modes and user in channel.modes[mode]: + channel.modes[mode].remove(user) + #if not len(chanobj.users): #Let's remove this channel + # del self.channels[chan] + # On second thought, no, since we may want to save certain info + user.channels = [] + for channel in channels: + channel.lock.release() + print >>self.log, "%s *** User %s!%s@%s was orphaned when quitting IRC."%(timestamp, user.nick, user.idnt, user.host) + elif cmd == "MODE": + if target[0] in self.supports["CHANTYPES"]: + user = self.user(origin) + if user.nick != origin: + user.nick = origin + if user.idnt != ident: + user.idnt = ident + if user.host != host: + user.host = host + + channel = self.channel(target) + self.event("onRecv", channel.modules, line=line, data=parsed) + with channel.lock: + if channel.name != target: + channel.name = target # Server seems to have changed the idea of the case of the channel name + + modeparams = params.split() + setmodes = modeparams.pop(0) + modeset = "+" + for mode in setmodes: + if mode in "+-": + modeset = mode + else: + if mode in self.supports["CHANMODES"][0]: + param = modeparams.pop(0) + if modeset == "+": + if mode in channel.modes: + if param.lower() not in [mask.lower() for (mask, setby, settime) in channel.modes[mode]]: + channel.modes[mode].append((param, origin, int(time.time()))) + else: + channel.modes[mode] = [(param, origin, int(time.time()))] + else: + if mode in channel.modes.keys(): + if mode == "b": # Inspircd mode is case insentive when unsetting the mode + masks = [mask.lower() for (mask, setby, settime) in channel.modes[mode]] + if param.lower() in masks: + index = masks.index(param.lower()) + #print "Index: %d"%index + del channel.modes[mode][index] + else: + masks = [mask for (mask, setby, settime) in channel.modes[mode]] + if param in masks: + index = masks.index(param) + del channel.modes[mode][index] + elif mode in self.supports["CHANMODES"][1]: + param = modeparams.pop(0) + if modeset == "+": + channel.modes[mode] = param + else: + channel.modes[mode] = None + elif mode in self.supports["CHANMODES"][2]: + if modeset == "+": + param = modeparams.pop(0) + channel.modes[mode] = param + else: + channel.modes[mode] = None + elif mode in self.supports["CHANMODES"][3]: + if modeset == "+": + channel.modes[mode] = True + else: + channel.modes[mode] = False + elif "PREFIX" in self.supports and mode in self.supports["PREFIX"][0]: + modenick = modeparams.pop(0) + modeuser = self.user(modenick) + if modeuser.nick != modenick: + modeuser.nick = modenick + if modeset == "+": + if mode in channel.modes and modeuser not in channel.modes[mode]: + channel.modes[mode].append(modeuser) + if mode not in channel.modes: + channel.modes[mode] = [modeuser] + elif mode in channel.modes and modeuser in channel.modes[mode]: + channel.modes[mode].remove(modeuser) + else: + user = self.user(target) + modeparams = (params if params else extinfo).split() + setmodes = modeparams.pop(0) + modeset = "+" + for mode in setmodes: + if mode in "+-": + modeset = mode + continue + if modeset == "+": + if mode not in user.modes: + user.modes += mode + if mode == "s" and len(modeparams): + snomask = modeparams.pop(0) + for snomode in snomask: + if snomode in "+-": + snomodeset = snomode + continue + if snomodeset == "+" and snomode not in user.snomask: + user.snomask += snomode + if snomodeset == "-" and snomode in user.snomask: + user.snomask = user.snomask.replace(snomode, "") + if modeset == "-": + if mode in user.modes: + user.modes = user.modes.replace(mode, "") + if mode == "s": + user.snomask = "" + elif cmd == "TOPIC": + user = self.user(origin) + if user.nick != origin: + user.nick = origin + if user.idnt != ident: + user.idnt = ident + if user.host != host: + user.host = host + + channel = self.channel(target) + self.event("onRecv", channel.modules, line=line, data=parsed) + + with channel.lock: + if channel.name != target: + channel.name = target # Server seems to have changed the idea of the case of the channel name + channel.topic = extinfo + elif cmd == "PRIVMSG": + user = self.user(origin) + if user.nick != origin: + user.nick = origin + if user.idnt != ident: + user.idnt = ident + if user.host != host: + user.host = host + + if target[0] in self.supports["CHANTYPES"]: + channel = self.channel(target) + self.event("onRecv", channel.modules, line=line, data=parsed) + if channel.name != target: + channel.name = target # Server seems to have changed the idea of the case of the channel name + elif target[0] == "$": + pass # Server message -- Not implemented + else: + targetuser = self.user(target) + if targetuser.nick != target: + targetuser.nick = target # Server seems to have changed the idea of the case of the nickname + + ### CTCP handling + ctcp = re.findall("^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$", extinfo) + if ctcp: + (ctcptype, ext) = ctcp[0] + if ctcptype.upper() == "VERSION": + user.ctcpreply("VERSION", self.ctcpversion()) + if ctcptype.upper() == "TIME": + tformat = time.ctime() + tz = time.tzname[0] + user.ctcpreply("TIME", "%(tformat)s %(tz)s" % vars()) + if ctcptype.upper() == "PING": + user.ctcpreply("PING", "%(ext)s" % vars()) + if ctcptype.upper() == "FINGER": + user.ctcpreply("FINGER", "%(ext)s" % vars()) + elif cmd == 910: # Channel Access List + (channame, mask, setby, settime) = params.split() + channel = self.channel(channame) + self.event("onRecv", channel.modules, line=line, data=parsed) + if channel.name != channame: + channel.name = channame # Server seems to have changed the idea of the case of the channel name + if "w" in channel.modes.keys(): + if mask not in [m for (m, b, t) in channel.modes["w"]]: + channel.modes["w"].append((mask, setby, int(settime))) + else: + channel.modes["w"] = [(mask, setby, int(settime))] + elif cmd == 941: # Channel spamfilter List + (channame, mask, setby, settime) = params.split() + channel = self.channel(channame) + self.event("onRecv", channel.modules, line=line, data=parsed) + if channel.name != channame: + channel.name = channame # Server seems to have changed the idea of the case of the channel name + if "g" in channel.modes.keys(): + if mask not in [m for (m, b, t) in channel.modes["g"]]: + channel.modes["g"].append((mask, setby, int(settime))) + else: + channel.modes["g"] = [(mask, setby, int(settime))] + elif cmd == 954: # Channel spamfilter List + (channame, mask, setby, settime) = params.split() + channel = self.channel(channame) + self.event("onRecv", channel.modules, line=line, data=parsed) + if channel.name != channame: + channel.name = channame # Server seems to have changed the idea of the case of the channel name + if "X" in channel.modes.keys(): + if mask not in [m for (m, b, t) in channel.modes["X"]]: + channel.modes["X"].append((mask, setby, int(settime))) + else: + channel.modes["X"] = [(mask, setby, int(settime))] + elif cmd in (495, 384, 385, 386, 468, 470, 366, 315, 482, 484, 953, 368, 482, 349, 940, 911, 489, 490, 492, 520, 530): # Channels which appear in params + for param in params.split(): + if len(param) and param[0] in self.supports["CHANTYPES"]: + channel = self.channel(param) + self.event("onRecv", channel.modules, line=line, data=parsed) + + else: # Line does NOT match ":src cmd target params :extinfo" + self.event("onRecv", self.modules, line=line, + data=None) + except SystemExit: # Connection lost normally. + pass + except socket.error: # Connection lost due to either ping timeout or connection reset by peer. Not a fatal error. + with self.loglock: + exc, excmsg, tb = sys.exc_info() + timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) + print >>self.log, "%(timestamp)s *** Connection to %(server)s:%(port)s failed: %(excmsg)s." % vars() + self.log.flush() + except: # Unknown exception, treated as FATAL. Try to quit IRC and terminate thread with exception. + ### Quit with a (hopefully) useful quit message, or die trying. + try: + self.quit("%s" % traceback.format_exc() + .rstrip().split("\n")[-1]) + except: + pass + raise + finally: # Post-connection operations after connection is lost, and must be executed, even if exception occurred. + with self.lock: + modules = list(self.modules) + for channel in self.channels: + for module in channel.modules: + if module not in modules: + modules.append(module) + self.event("onDisconnect", self.modules) + + ### Tell outgoing thread to quit. + self.outgoing.put("quit") + + ### Wait until the outgoing thread dies. + if outgoingthread: + outgoingthread.join() + outgoingthread = None + + self.connected = False + self.registered = False + self.identity = None + + try: + self.connection.close() + except: + pass + + with self.loglock: + timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) + print >>self.log, "%(timestamp)s *** Connection Terminated." % vars() + self.log.flush() + + if self.quitexpected or not self.autoreconnect: + sys.exit() + + ### If we make it to this point, then it is because connection was lost unexpectedly, and will attempt to reconnect if self.autoreconnect is True. + time.sleep(self.retrysleep) + + except SystemExit: + pass + + except: # Print exception to log file + with self.loglock: + timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust( + 2, "0") for t in time.localtime()[0:6]]) + print >>self.log, "%(timestamp)s !!! Fatal Exception" % vars() + for tbline in traceback.format_exc().split("\n"): + print >>self.log, "%(timestamp)s !!! %(tbline)s" % vars() + self.log.flush() + sys.exit() + + finally: + with self.loglock: + timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust( + 2, "0") for t in time.localtime()[0:6]]) + print >>self.log, "%(timestamp)s ### Log session ended" % vars( + ) + self.log.flush() + + Thread.__init__(self) # Makes thread restartable + + def __repr__(self): + server = self.server + if self.ipv6 and ":" in server: + server = "[%s]"%server + port = self.port + if self.identity: + nick = self.identity.nick + ident = self.identity.idnt if self.identity.idnt else "*" + host = self.identity.host if self.identity.host else "*" + else: + nick = "*" + ident = "*" + host = "*" + if self.ssl and self.ipv6: + protocol = "ircs6" + elif self.ssl: + protocol = "ircs" + elif self.ipv6: + protocol = "irc6" + else: + protocol = "irc" + return "" % locals() + #else: return "" % locals() + + def quit(self, msg="", origin=None): + with self.lock: + self.quitexpected = True + if self.connected: + if len(msg): + self.raw("QUIT :%(msg)s" % vars(), origin=origin) + else: + self.raw("QUIT", origin=origin) + + def ctcpversion(self): + reply = [] + ### Prepare reply for module + reply.append("%(__name__)s %(__version__)s, %(__author__)s" % + vars(self)) + + ### Prepare reply for Python and OS versions + pyver = sys.version.split("\n") + pyver[0] = "Python "+pyver[0] + reply.extend(pyver) + reply.extend(platform.platform().split("\n")) + ### Prepare reply for extension modules + for module in self.modules: + try: + r = "%(__name__)s %(__version__)s" % vars(module) + if "__extinfo__" in vars(module): + r += ", %(__extinfo__)s" % vars() + reply.append(r) + except: + pass + return reduce(lambda x, y: "%s; %s" % (x, y), reply) + + def raw(self, line, origin=None): + self.outgoing.put((line, origin)) + + def user(self, nick): + users = [user for user in self.users if user.nick.lower( + ) == nick.lower()] + if len(users): + return users[0] + else: + user = User(nick, self) + self.users.append(user) + timestamp = reduce(lambda x, y: x+":"+y, [str( + t).rjust(2, "0") for t in time.localtime()[0:6]]) + with self.loglock: + print >>self.log, "%s *** User %s created."%(timestamp, nick) + self.log.flush() + return user + + def channel(self, name): + channels = [chan for chan in self.channels if name.lower( + ) == chan.name.lower()] + if len(channels): + return channels[0] + else: + timestamp = reduce(lambda x, y: x+":"+y, [str( + t).rjust(2, "0") for t in time.localtime()[0:6]]) + chan = Channel(name, self) + self.channels.append(chan) + with self.loglock: + print >>self.log, "%s *** Channel %s created."%( + timestamp, name) + self.log.flush() + return chan class Channel(object): - def __init__(self,name,context): - self.name=name - self.context=context - self.modules=[] - self.topic="" - self.topicsetby="" - self.topictime=() - self.topicmod="" - self.modes={} - self.users=[] - self.created=None - self.lock=Lock() - def msg(self, msg, origin=None): - chan=self.name - self.context.raw("PRIVMSG %(chan)s :%(msg)s" % vars(), origin=origin) - def settopic(self, msg, origin=None): - chan=self.name - self.context.raw("TOPIC %(chan)s :%(msg)s" % vars(), origin=origin) - def notice(self, msg, target="", origin=None): - chan=self.name - self.context.raw("NOTICE %(target)s%(chan)s :%(msg)s" % vars(), origin=origin) - def ctcp(self, act, msg="", origin=None): - if len(msg): self.msg("\01%(act)s %(msg)s\01" % vars(), origin=origin) - else: self.msg("\01%(act)s\01" % vars()) - def ctcpreply(self, act, msg="", origin=None): - if len(msg): self.notice("\01%(act)s %(msg)s\01" % vars(), origin=origin) - else: self.notice("\01%(act)s\01" % vars(), origin=origin) - def me(self,msg="", origin=None): self.ctcp("ACTION", msg, origin=origin) - def part(self, msg="", origin=None): - chan=self.name - if msg: self.context.raw("PART %(chan)s :%(msg)s" % vars(), origin=origin) - else: self.context.raw("PART %(chan)s" % vars(), origin) - def join(self, key="", origin=None): - chan=self.name - if key: self.context.raw("JOIN :%(chan)s %(key)s" % vars(), origin=origin) - else: self.context.raw("JOIN :%(chan)s" % vars(), origin=origin) - def kick(self, nick, msg="", origin=None): - chan=self.name - if len(msg): self.context.raw("KICK %(chan)s %(nick)s :%(msg)s" % vars(), origin=origin) - else: self.context.raw("KICK %(chan)s %(nick)s" % vars(), origin=origin) - def __repr__(self): - return "" + def __init__(self, name, context): + self.name = name + self.context = context + self.modules = [] + self.topic = "" + self.topicsetby = "" + self.topictime = () + self.topicmod = "" + self.modes = {} + self.users = [] + self.created = None + self.lock = Lock() + + def msg(self, msg, origin=None): + chan = self.name + self.context.raw("PRIVMSG %(chan)s :%(msg)s" % vars(), origin=origin) + + def settopic(self, msg, origin=None): + chan = self.name + self.context.raw("TOPIC %(chan)s :%(msg)s" % vars(), origin=origin) + + def notice(self, msg, target="", origin=None): + chan = self.name + self.context.raw("NOTICE %(target)s%(chan)s :%(msg)s" % + vars(), origin=origin) + + def ctcp(self, act, msg="", origin=None): + if len(msg): + self.msg("\01%(act)s %(msg)s\01" % vars(), origin=origin) + else: + self.msg("\01%(act)s\01" % vars()) + + def ctcpreply(self, act, msg="", origin=None): + if len(msg): + self.notice("\01%(act)s %(msg)s\01" % vars(), origin=origin) + else: + self.notice("\01%(act)s\01" % vars(), origin=origin) + + def me(self, msg="", origin=None): + self.ctcp("ACTION", msg, origin=origin) + + def part(self, msg="", origin=None): + chan = self.name + if msg: + self.context.raw("PART %(chan)s :%(msg)s" % vars(), origin=origin) + else: + self.context.raw("PART %(chan)s" % vars(), origin) + + def join(self, key="", origin=None): + chan = self.name + if key: + self.context.raw("JOIN :%(chan)s %(key)s" % vars(), origin=origin) + else: + self.context.raw("JOIN :%(chan)s" % vars(), origin=origin) + + def kick(self, nick, msg="", origin=None): + chan = self.name + if len(msg): + self.context.raw("KICK %(chan)s %(nick)s :%(msg)s" % + vars(), origin=origin) + else: + self.context.raw("KICK %(chan)s %(nick)s" % vars(), origin=origin) + + def __repr__(self): + return "" + + class User(object): - def __init__(self,nick,context): - self.nick=nick - self.idnt="" - self.host="" - self.channels=[] - self.context=context - self.modes="" - self.snomask="" - self.server=None - self.hops=None - self.ircop=False - def __repr__(self): - return "" % vars(self) - def msg(self, msg, origin=None): - nick=self.nick - self.context.raw("PRIVMSG %(nick)s :%(msg)s" % vars(), origin=origin) - def notice(self, msg, origin=None): - nick=self.nick - self.context.raw("NOTICE %(nick)s :%(msg)s" % vars(), origin=origin) - def ctcp(self, act, msg="", origin=None): - if len(msg): self.msg("\01%(act)s %(msg)s\01" % vars(), origin=origin) - else: self.msg("\01%(act)s\01" % vars(), origin=origin) - def ctcpreply(self, act, msg="", origin=None): - if len(msg): self.notice("\01%(act)s %(msg)s\01" % vars(), origin=origin) - else: self.notice("\01%(act)s\01" % vars(), origin=origin) - def me(self, msg, origin=None): self.ctcp("ACTION", msg, origin=origin) + def __init__(self, nick, context): + self.nick = nick + self.idnt = "" + self.host = "" + self.channels = [] + self.context = context + self.modes = "" + self.snomask = "" + self.server = None + self.hops = None + self.ircop = False + + def __repr__(self): + return "" % vars(self) + + def msg(self, msg, origin=None): + nick = self.nick + self.context.raw("PRIVMSG %(nick)s :%(msg)s" % vars(), origin=origin) + + def notice(self, msg, origin=None): + nick = self.nick + self.context.raw("NOTICE %(nick)s :%(msg)s" % vars(), origin=origin) + + def ctcp(self, act, msg="", origin=None): + if len(msg): + self.msg("\01%(act)s %(msg)s\01" % vars(), origin=origin) + else: + self.msg("\01%(act)s\01" % vars(), origin=origin) + + def ctcpreply(self, act, msg="", origin=None): + if len(msg): + self.notice("\01%(act)s %(msg)s\01" % vars(), origin=origin) + else: + self.notice("\01%(act)s\01" % vars(), origin=origin) + + def me(self, msg, origin=None): + self.ctcp("ACTION", msg, origin=origin) + + class SendLines(Thread): - def __init__(self, connection, lines, origin=None): - self.connection=connection - self.lines=lines - self.origin=origin - Thread.__init__(self) - def run(self): - for line in self.lines: - self.connection.raw(reply, origin=self.origin) - self.connection.log.flush() - time.sleep(2) + def __init__(self, connection, lines, origin=None): + self.connection = connection + self.lines = lines + self.origin = origin + Thread.__init__(self) + + def run(self): + for line in self.lines: + self.connection.raw(reply, origin=self.origin) + self.connection.log.flush() + time.sleep(2) + class Outgoing(Thread): - def __init__(self, IRC, throttle=0.25, lines=40, t=5): - self.IRC=IRC - self.throttle=throttle - self.lines=lines - self.time=t - #self.queue=Queue() - Thread.__init__(self) - def run(self): - throttled=False - timestamps=[] - while True: - q=self.IRC.outgoing.get() - if q=="quit" or not self.IRC.connected: break - line, origin=q - match=re.findall("^(.+?)(?:\\s+(.+?)(?:\\s+(.+?))??)??(?:\\s+:(.*))?$", line, re.I) - (cmd, target, params, extinfo)=match[0] - if cmd.upper()=="QUIT": self.IRC.quitexpected=True - timestamp=reduce(lambda x,y: x+":"+y,[str(t).rjust(2,"0") for t in time.localtime()[0:6]]) - with self.IRC.lock: - try: - self.IRC.connection.send("%(line)s\n" % vars()) - except socket.error: - try: - self.IRC.connection.shutdown(0) - except: - pass - raise - - ### Modify line if it contains a password so that the password is not logged or sent to any potentially untrustworthy modules - #if re.match("^(.+?)(?:\\s+(.+?)(?:\\s+(.+?))??)??(?:\\s+:(.*))?$", line, re.I): - if cmd.upper()=="PRIVMSG": - if target.upper()=="NICKSERV": - nscmd=re.findall(r"^\s*(\S+)\s+(\S+)(?:\s*(\S+)(?:\s*(.+))?)?$", extinfo, re.I) - if nscmd: - nscmd=nscmd[0] - if nscmd[0].upper() in ("IDENTIFY", "REGISTER"): - extinfo="%s ********"%nscmd[0] - line="%s %s :%s"%(cmd, target, extinfo) - elif nscmd[0].upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"): - extinfo="%s %s ********"%nscmd[:2] - line="%s %s :%s"%(cmd, target, extinfo) - elif nscmd[0].upper() == "SET": - if nscmd[1].upper() == "PASSWORD": - extinfo="%s %s ********"%nscmd[:2] - line="%s %s :%s"%(cmd, target, extinfo) - elif nscmd[0].upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"): - extinfo="********" - line="%s %s :%s"%(cmd, target, extinfo) - if target.upper()=="CHANSERV": - cscmd=re.findall(r"^\s*(\S+)\s+(\S+)\s+(\S+)(?:\s*(\S+)(?:\s*(.+))?)?$", extinfo, re.I) - if cscmd: - cscmd=cscmd[0] - if cscmd[0].upper() in ("IDENTIFY", "REGISTER"): - extinfo="%s %s ********"%cscmd[:2] - line="%s %s :%s"%(cmd, target, extinfo) - elif cscmd[0].upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"): - extinfo="%s %s %s ********"%cscmd[:3] - line="%s %s :%s"%(cmd, target, extinfo) - elif cscmd[0].upper() == "SET": - if cscmd[2].upper() == "PASSWORD": - extinfo="%s %s %s ********"%cscmd[:3] - line="%s %s :%s"%(cmd, target, extinfo) - elif cscmd[0].upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"): - extinfo="********" - line="%s %s :%s"%(cmd, target, extinfo) - #elif target.upper()=="CHANSERV": - #msg=extinfo.split(" ") - #if msg[0].upper() in ("IDENTIFY", "REGISTER") and len(msg)>2: - #msg[2]="********" - #extinfo=" ".join(msg) - #line="%s %s :%s"%(cmd, target, extinfo) - elif cmd.upper()=="NS": - if target.upper() in ("IDENTIFY", "REGISTER"): - params=params.split(" ") - while "" in params: params.remove("") - if len(params): - params[0]="********" - params=" ".join(params) - line="%s %s %s"%(cmd, target, params) - elif target.upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"): - params=params.split(" ") - while "" in params: params.remove("") - if len(params)>1: - params[1]="********" - params=" ".join(params) - line="%s %s %s"%(cmd, target, params) - elif target.upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"): - params="" - target="********" - line="%s %s"%(cmd, target) - elif cmd.upper()=="OPER": - params="********" - line="%s %s %s"%(cmd, target, params) - elif cmd.upper()=="PASS": - extinfo="********" - target="" - line="%s :%s"%(cmd, extinfo) - elif cmd.upper()=="IDENTIFY": - target="********" - line="%s %s"%(cmd, target) - self.IRC.event("onSend", self.IRC.modules, line=line, data=(cmd, target, params, extinfo), origin=origin) - with self.IRC.loglock: - print >>self.IRC.log, "%(timestamp)s >>> %(line)s" % vars() - self.IRC.log.flush() - timestamps.append(time.time()) - while timestamps[0]=self.lines: throttled=True - if throttled: time.sleep(max(timestamps[-1]+self.throttle-time.time(),0)) + def __init__(self, IRC, throttle=0.25, lines=40, t=5): + self.IRC = IRC + self.throttle = throttle + self.lines = lines + self.time = t + #self.queue=Queue() + Thread.__init__(self) + + def run(self): + throttled = False + timestamps = [] + while True: + q = self.IRC.outgoing.get() + if q == "quit" or not self.IRC.connected: + break + line, origin = q + match = re.findall("^(.+?)(?:\\s+(.+?)(?:\\s+(.+?))??)??(?:\\s+:(.*))?$", line, re.I) + (cmd, target, params, extinfo) = match[0] + if cmd.upper() == "QUIT": + self.IRC.quitexpected = True + timestamp = reduce(lambda x, y: x+":"+y, [str( + t).rjust(2, "0") for t in time.localtime()[0:6]]) + with self.IRC.lock: + try: + self.IRC.connection.send("%(line)s\n" % vars()) + except socket.error: + try: + self.IRC.connection.shutdown(0) + except: + pass + raise + + ### Modify line if it contains a password so that the password is not logged or sent to any potentially untrustworthy modules + #if re.match("^(.+?)(?:\\s+(.+?)(?:\\s+(.+?))??)??(?:\\s+:(.*))?$", line, re.I): + if cmd.upper() == "PRIVMSG": + if target.upper() == "NICKSERV": + nscmd = re.findall(r"^\s*(\S+)\s+(\S+)(?:\s*(\S+)(?:\s*(.+))?)?$", extinfo, re.I) + if nscmd: + nscmd = nscmd[0] + if nscmd[0].upper() in ("IDENTIFY", "REGISTER"): + extinfo = "%s ********"%nscmd[0] + line = "%s %s :%s"%(cmd, target, extinfo) + elif nscmd[0].upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"): + extinfo = "%s %s ********"%nscmd[:2] + line = "%s %s :%s"%(cmd, target, extinfo) + elif nscmd[0].upper() == "SET": + if nscmd[1].upper() == "PASSWORD": + extinfo = "%s %s ********"%nscmd[:2] + line = "%s %s :%s"%(cmd, target, extinfo) + elif nscmd[0].upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"): + extinfo = "********" + line = "%s %s :%s"%(cmd, target, extinfo) + if target.upper() == "CHANSERV": + cscmd = re.findall(r"^\s*(\S+)\s+(\S+)\s+(\S+)(?:\s*(\S+)(?:\s*(.+))?)?$", extinfo, re.I) + if cscmd: + cscmd = cscmd[0] + if cscmd[0].upper() in ("IDENTIFY", "REGISTER"): + extinfo = "%s %s ********"%cscmd[:2] + line = "%s %s :%s"%(cmd, target, extinfo) + elif cscmd[0].upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"): + extinfo = "%s %s %s ********"%cscmd[:3] + line = "%s %s :%s"%(cmd, target, extinfo) + elif cscmd[0].upper() == "SET": + if cscmd[2].upper() == "PASSWORD": + extinfo = "%s %s %s ********"%cscmd[:3] + line = "%s %s :%s"%(cmd, target, extinfo) + elif cscmd[0].upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"): + extinfo = "********" + line = "%s %s :%s"%(cmd, target, extinfo) + #elif target.upper()=="CHANSERV": + #msg=extinfo.split(" ") + #if msg[0].upper() in ("IDENTIFY", "REGISTER") and len(msg)>2: + #msg[2]="********" + #extinfo=" ".join(msg) + #line="%s %s :%s"%(cmd, target, extinfo) + elif cmd.upper() == "NS": + if target.upper() in ("IDENTIFY", "REGISTER"): + params = params.split(" ") + while "" in params: + params.remove("") + if len(params): + params[0] = "********" + params = " ".join(params) + line = "%s %s %s"%(cmd, target, params) + elif target.upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"): + params = params.split(" ") + while "" in params: + params.remove("") + if len(params) > 1: + params[1] = "********" + params = " ".join(params) + line = "%s %s %s"%(cmd, target, params) + elif target.upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"): + params = "" + target = "********" + line = "%s %s"%(cmd, target) + elif cmd.upper() == "OPER": + params = "********" + line = "%s %s %s"%(cmd, target, params) + elif cmd.upper() == "PASS": + extinfo = "********" + target = "" + line = "%s :%s"%(cmd, extinfo) + elif cmd.upper() == "IDENTIFY": + target = "********" + line = "%s %s"%(cmd, target) + self.IRC.event("onSend", self.IRC.modules, line=line, data=(cmd, target, params, extinfo), origin=origin) + with self.IRC.loglock: + print >>self.IRC.log, "%(timestamp)s >>> %(line)s" % vars() + self.IRC.log.flush() + timestamps.append(time.time()) + while timestamps[0] < timestamps[-1]-self.time-0.1: + del timestamps[0] + if throttled: + if len(timestamps) < 2: + throttled = False + else: + if len(timestamps) >= self.lines: + throttled = True + if throttled: + time.sleep(max(timestamps[-1]+self.throttle-time.time(), 0)) class Pinger(Thread): - def __init__(self, connection, lock=None): - self.connection=connection - self.lock=lock - self.daemon=True - Thread.__init__(self) - - def run(self): - pass \ No newline at end of file + def __init__(self, connection, lock=None): + self.connection = connection + self.lock = lock + self.daemon = True + Thread.__init__(self) + + def run(self): + pass -- cgit v1.2.3