diff options
| -rw-r--r-- | autoexec.py | 68 | ||||
| -rw-r--r-- | bouncer2.py | 452 | ||||
| -rw-r--r-- | irc4.py | 976 | ||||
| -rw-r--r-- | logger.py | 143 | ||||
| -rw-r--r-- | mibbit.py | 31 | ||||
| -rw-r--r-- | sedbot.py | 87 | ||||
| -rw-r--r-- | wallet.py | 37 | 
7 files changed, 1688 insertions, 106 deletions
| diff --git a/autoexec.py b/autoexec.py new file mode 100644 index 0000000..8a8b7e7 --- /dev/null +++ b/autoexec.py @@ -0,0 +1,68 @@ +#!/usr/bin/python +import re + +class Autoexec(object): +	def __init__(self): +		self.networks={} +	def onModuleAdd(self, IRC, label, onconnect=None, onregister=None, autojoin=None, usermodes=None, wallet=None, opername=None, opermodes=None, snomasks=None, operexec=None, operjoin=None): +		labels=[v[0] for v in self.networks.values()] +		if label in labels: +			raise BaseException, "Label already exists" +		if IRC in self.networks.keys(): +			raise BaseException, "Network already exists" +		self.networks[IRC]=(label, onconnect, onregister, autojoin, usermodes, wallet, opername, opermodes, snomasks, operexec, operjoin) +	def onModuleRem(self, IRC): +		del self.networks[IRC] +	def onConnect(self, IRC): +		(label, onconnect, onregister, autojoin, usermodes, wallet, opername, opermodes, snomasks, operexec, operjoin)=self.networks[IRC] +		if onconnect: +			for line in onconnect: +				IRC.raw(line, origin=self) +	def onRegistered(self, IRC): +		(label, onconnect, onregister, autojoin, usermodes, wallet, opername, opermodes, snomasks, operexec, operjoin)=self.networks[IRC] +		if onregister: +			for line in onregister: +				IRC.raw(line, origin=self) +		if usermodes: +			IRC.raw("MODE %s %s"%(IRC.identity.nick, usermodes), origin=self) +		if opername and wallet and "%s/opers/%s"%(label, opername) in wallet.keys(): +			IRC.raw("OPER %s %s"%(opername, wallet["%s/opers/%s"%(label, opername)]), origin=self) +		if autojoin: +			IRC.raw("JOIN %s"%(",".join(autojoin)), origin=self) +	def onRecv(self, IRC, line, data): +		if data==None: +			return +		(label, onconnect, onregister, autojoin, usermodes, wallet, opername, opermodes, snomasks, operexec, operjoin)=self.networks[IRC] +		(origin, ident, host, cmd, target, params, extinfo)=data +		if cmd=="381" and opermodes: +			if operexec: +				for line in operexec: +					IRC.raw(line, origin=self) +			if opermodes: +				IRC.raw("MODE %s %s"%(IRC.identity.nick, opermodes), origin=self) +			if snomasks: +				IRC.raw("MODE %s +s %s"%(IRC.identity.nick, snomasks), origin=self) +			if operjoin: +				IRC.raw("JOIN %s"%(",".join(operjoin)), origin=self) + +class NickServ(object): +	def __init__(self): +		self.networks={} +	def onModuleAdd(self, IRC, label, wallet=None, autojoin=None): +		labels=[v[0] for v in self.networks.values()] +		#print labels +		if label in labels: +			raise BaseException, "Label already exists" +		if IRC in self.networks.keys(): +			raise BaseException, "Network already exists" +		self.networks[IRC]=(label, wallet, autojoin) +	def onModuleRem(self, IRC): +		del self.networks[IRC] +	def onRecv(self, IRC, line, data): +		if data==None: return +		(origin, ident, host, cmd, target, params, extinfo)=data +		label, wallet, autojoin=self.networks[IRC] +		if target==IRC.identity.nick and origin=="NickServ" and re.match("This nickname is registered and protected.", extinfo) and wallet and "%s/NickServ/%s"%(label, target.lower()) in wallet.keys(): +			IRC.user("NickServ").msg("identify %s" % wallet["%s/NickServ/%s"%(label, target.lower())]) +		if cmd=="900" and autojoin: +			IRC.raw("JOIN %s"%(",".join(autojoin)), origin=self) diff --git a/bouncer2.py b/bouncer2.py new file mode 100644 index 0000000..2e6561a --- /dev/null +++ b/bouncer2.py @@ -0,0 +1,452 @@ +#!/usr/bin/python +import socket, ssl, os, re, time, sys, string, hashlib, traceback +from threading import Thread, Lock +import Queue + +class Bouncer (Thread): +	def __init__(self, addr="", port=16667, ssl=False, certfile=None, keyfile=None, ignore=None): +		self.__name__="Bouncer for pyIRC" +		self.__version__="1.0.0rc1" +		self.__author__="Brian Sherson" +		self.__date__="May 23, 2013" +		#print "Initializing ListenThread..." +		self.addr=addr +		self.port=port +		self.servers={} +		self.passwd={} +		self.socket=s=socket.socket() +		self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +		self.ssl=ssl +		self.certfile=certfile +		self.keyfile=keyfile +		s.bind((self.addr,self.port)) +		self.connections=[] +		self.ignore=ignore + +		### Keep track of what extensions/connections are requesting WHO, WHOIS, and LIST, because we don't want to spam every bouncer connection with the server's replies. +		### In the future, MAY implement this idea in the irc module. +		self.whoexpected={} +		self.whoisexpected={} +		self.listexpected={} +		#self.lock=Lock() +		self.starttime=int(time.time()) +		Thread.__init__ ( self ) +		self.daemon=True +		self.start() +	def __repr__(self): +		return "<Bouncer listening on port %(addr)s:%(port)s>" % vars(self) + +	def run(self): +		self.socket.listen(5) +		#print ((self,"Now listening on port "+str(self.port))) +		while True: +			try: +				(connection,addr)=self.socket.accept() +				if self.ssl: +					connection=ssl.wrap_socket(connection, server_side=True, certfile=self.certfile, keyfile=self.keyfile, ssl_version=ssl.PROTOCOL_SSLv23) +				try: +					hostname, aliaslist, addresslist = socket.gethostbyaddr(addr[0]) +					addr = (hostname, addr[1]) +				except: +					pass +				#print ((self,"New client connecting from %s:%s"%addr)) +			except socket.error: +				#print "Shutting down Listener" +				self.socket.close() +				#raise +				sys.exit() +			bouncer=BouncerConnection(self, connection, addr) +			#bouncer.daemon=True +			#self.connections.append(bouncer) +			#bouncer.start() +			#ccrecv.start() +			time.sleep(0.5) +		try: +			self.socket.close() +		except: pass +	def onRecv(self, IRC, line, data): +		if type(self.ignore) not in (list, tuple) or all([not re.match(pattern, line) for pattern in self.ignore]): +			if data: +				(origin, ident, host, cmd, target, params, extinfo)=data +				#print data +				if re.match("^\\d+$",cmd): cmd=int(cmd) ### Code is a numerical response +				if cmd in (352, 315): ### WHO reply +					if len(self.whoexpected[IRC]) and self.whoexpected[IRC][0] in self.connections: +						self.whoexpected[IRC][0].connection.send(line+"\n") +					if cmd==315: ### End of WHO reply +						del self.whoexpected[IRC][0] +				elif cmd in (307, 311, 312, 313, 317, 318, 319, 330, 335, 336, 378, 379): ### WHO reply +					if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections: +						self.whoisexpected[IRC][0].connection.send(line+"\n") +					if cmd==318: ### End of WHOIS reply +						del self.whoisexpected[IRC][0] +				elif cmd in (321, 322, 323): ### LIST reply +					if len(self.listexpected[IRC]) and self.listexpected[IRC][0] in self.connections: +						self.listexpected[IRC][0].connection.send(line+"\n") +					if cmd==323: ### End of LIST reply +						del self.listexpected[IRC][0] +				else: +					for bouncer in self.connections: +						#print bouncer.IRC +						#print IRC +						#print line +						if bouncer.IRC==IRC: bouncer.connection.send(line+"\n") +	def onSend(self, IRC, line, data, origin): +		if type(self.ignore) not in (list, tuple) or all([not re.match(pattern, line) for pattern in self.ignore]): +			(cmd, target, params, extinfo)=data +			if cmd.upper() in ("PRIVMSG", "NOTICE"): +				for bouncerconnection in self.connections: +					if bouncerconnection==origin: ### Do NOT send the message back to the originating client. +						continue +					if bouncerconnection.IRC==IRC: ### Send the message to the other clients connected to the bouncer. +						ctcp=re.findall("^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$",extinfo) +						if ctcp: +							(ctcptype,ext)=ctcp[0] +							if ctcptype=="ACTION": +								bouncerconnection.connection.send(":%s!%s@%s %s\n" % (bouncerconnection.IRC.identity.nick, bouncerconnection.IRC.identity.idnt, bouncerconnection.IRC.identity.host, line)) +							### Unless the message is a CTCP that is not ACTION. +						else: +							bouncerconnection.connection.send(":%s!%s@%s %s\n" % (bouncerconnection.IRC.identity.nick, bouncerconnection.IRC.identity.idnt, bouncerconnection.IRC.identity.host, line)) +			elif cmd.upper()=="WHO": +				#print origin, line +				self.whoexpected[IRC].append(origin) +			elif cmd.upper()=="WHOIS": +				#print origin, line +				self.whoisexpected[IRC].append(origin) +			elif cmd.upper()=="LIST": +				#print origin, line +				self.listexpected[IRC].append(origin) +	def onModuleAdd(self, IRC, label, passwd, hashtype="md5"): +		if IRC in [connection for (connection, passwd, hashtype) in self.servers.values()]: return # Silently do nothing +		if label in self.servers.keys(): return +		self.servers[label]=(IRC, passwd, hashtype) +		self.whoexpected[IRC]=[] +		self.whoisexpected[IRC]=[] +		self.listexpected[IRC]=[] + +	def onModuleRem(self, IRC): +		for bouncerconnection in self.connections: +			if bouncerconnection.IRC==IRC: +				bouncerconnection.stop(quitmsg="Bouncer extension removed") +		for (label, (connection, passwd, hashtype)) in self.servers.items(): +			if connection==IRC: +				del self.servers[label] + +	def stop(self): +		#self.quitmsg=quitmsg +		#self.connection.send("ERROR :Closing link: (%s@%s) [%s]\n" % (self.IRC.identity.nick, self.addr[0], self.quitmsg)) +		self.socket.shutdown(0) +	def disconnectall(self, quitmsg="Disconnecting all sessions"): +		for bouncerconnection in self.connections: +			bouncerconnection.stop(quitmsg=quitmsg) +	def onDisconnect(self, IRC): +		self.whoexpected[IRC]=[] +		self.whoisexpected[IRC]=[] +		self.listexpected[IRC]=[] +		for bouncerconnection in self.connections: +			if bouncerconnection.IRC==IRC: +				bouncerconnection.stop(quitmsg="IRC connection lost") + +class BouncerConnection (Thread): +	def __init__(self, bouncer, connection, addr): +		#print "Initializing ListenThread..." +		self.bouncer=bouncer +		self.connection=connection +		self.host, self.port=self.addr=addr +		self.IRC=None +		self.pwd=None +		self.nick=None +		self.label=None +		self.idnt=None +		self.realname=None +		self.addr=addr +		self.quitmsg="Connection Closed" + +		Thread.__init__ ( self ) +		self.daemon=True +		self.start() + +	def __repr__(self): +		server=self.IRC.server if self.IRC else "*" +		port=self.IRC.port if self.IRC else "*" +		if self.IRC and self.IRC.identity: +			nick=self.IRC.identity.nick +			ident=self.IRC.identity.idnt if self.IRC.identity.idnt else "*" +			host=self.IRC.identity.host if self.IRC.identity.host else "*" +		else: +			nick="*" +			ident="*" +			host="*" +		protocol="ircs" if self.IRC.ssl else "irc" +		addr=self.host +		return "<Bouncer connection from %(addr)s to %(nick)s!%(ident)s@%(host)s on %(protocol)s://%(server)s:%(port)s>" % locals() + +	def stop(self, quitmsg="Disconnected"): +		self.quitmsg=quitmsg +		#self.connection.send("ERROR :Closing link: (%s@%s) [%s]\n" % (self.IRC.identity.nick, self.host, self.quitmsg)) +		self.connection.shutdown(0) + +	def run(self): +		### Add connection to connection list. + +		listnumerics=dict(b=(367, 368, "channel ban list"), e=(348, 349, "Channel Exception List"), I=(346, 347, "Channel Invite Exception List"), w=(910, 911, "Channel Access List"), g=(941, 940, "chanel spamfilter list"), X=(954, 953, "channel exemptchanops list")) + +		passwd=None +		nick=None +		user=None + +		readbuf="" +		linebuf=[] + +		try: +			while True: +				### Read data (appending) into readbuf, then break lines and append lines to linebuf +				while len(linebuf)==0: +					timestamp=reduce(lambda x,y: x+":"+y,[str(t).rjust(2,"0") for t in time.localtime()[0:6]]) +					read=self.connection.recv(512) +					if read=="" and len(linebuf)==0: ### No more data to process.  +						#self.quitmsg="Connection Closed" +						sys.exit() + +					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)) +				match=re.findall("^(.+?)(?:\\s+(.+?)(?:\\s+(.+?))??)??(?:\\s+:(.*))?$", line, re.I) + +				if len(match)==0: continue +				(cmd, target, params, extinfo)=match[0] + +				if not passwd: ### Bouncer expects a password +					if cmd.upper()=="PASS": +						passwd=target if target else extinfo +					else: +						self.quitmsg="Access Denied" +						break + +				elif not nick: ### Bouncer expects a NICK command +					if cmd.upper()=="NICK": +						nick=target if target else extinfo +					else: +						self.quitmsg="Access Denied" +						break + +				elif not self.idnt: ### Bouncer expects a USER command to finish registration +					if cmd.upper()=="USER": +						self.idnt=target +						#print self.idnt +						if self.idnt in self.bouncer.servers.keys(): +							self.IRC, passwdhash, hashtype=self.bouncer.servers[self.idnt] +							passmatch=hashlib.new(hashtype, passwd).hexdigest()==passwdhash +							self.IRC.lock.acquire() +							if not (self.IRC.connected and self.IRC.registered and type(self.IRC.supports)==dict and "CHANMODES" in self.IRC.supports.keys() and passmatch): +								self.quitmsg="Access Denied" +								self.IRC.lock.release() +								break + +							### If we have made it to this point, then access has been granted. +							self.bouncer.connections.append(self) +							labels=[bouncerconnection.label for bouncerconnection in self.bouncer.connections if bouncerconnection.IRC==self.IRC and bouncerconnection.label] +							n=1 +							while "*%s_%d"%(self.idnt, n) in labels: +								n+=1 +							self.label="*%s_%d"%(self.idnt, n) + +							### Request Version info. +							self.connection.send(":$bouncer PRIVMSG %s :\x01VERSION\x01\n" % (self.IRC.identity.nick)) + +							### Send Greeting. +							self.connection.send(":%s 001 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.welcome)) +							self.connection.send(":%s 002 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.hostinfo)) +							self.connection.send(":%s 003 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.servinfo)) +							self.connection.send(":%s 004 %s %s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.serv004)) + +							### Send 005 response. +							supports=["CHANMODES=%s"%(",".join(value)) if name=="CHANMODES" else "PREFIX=(%s)%s"%value if name=="PREFIX" else "%s=%s"%(name, value) if value else name for name, value in self.IRC.supports.items()] +							supports.sort() +							supportsreply=[] +							supportsstr=" ".join(supports) +							index=0 +							while True: +								if len(supportsstr)-index>196: +									nextindex=supportsstr.rfind(" ", index, index+196) +									supportsreply.append(supportsstr[index:nextindex]) +									index=nextindex+1 +								else: +									supportsreply.append(supportsstr[index:]) +									break +							for support in supportsreply: +								self.connection.send(":%s 005 %s %s :are supported by this server\n" % (self.IRC.serv, self.IRC.identity.nick, support)) + +							### Send MOTD +							self.connection.send(":%s 375 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.motdgreet)) +							for motdline in self.IRC.motd: +								self.connection.send(":%s 372 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, motdline)) +							self.connection.send(":%s 376 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.motdend)) + +							### Send user modes and snomasks. +							self.connection.send(":%s 221 %s +%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.identity.modes)) +							if "s" in self.IRC.identity.modes and self.IRC.identity.snomask: +								self.connection.send(":%s 008 %s +%s :Server notice mask\n" % (self.IRC.server, self.IRC.identity.nick, self.IRC.identity.snomask)) + +							### Join user to internal bouncer channel. +							self.connection.send(":%s!%s@%s JOIN :$bouncer\n" % (self.IRC.identity.nick, self.IRC.identity.idnt, self.IRC.identity.host)) + +							### Set internal bouncer topic. +							self.connection.send(":$bouncer 332 %s $bouncer :Bouncer internal channel. Enter bouncer commands here.\n" % (self.IRC.identity.nick)) +							self.connection.send(":$bouncer 333 %s $bouncer $bouncer %s\n" % (self.IRC.identity.nick, self.bouncer.starttime)) + +							### Send NAMES for internal bouncer channel. +							self.connection.send(":$bouncer 353 %s @ $bouncer :%s\n" % ( +								self.IRC.identity.nick, +								string.join(["@*Bouncer*"]+["@%s"%bouncerconnection.label for bouncerconnection in self.bouncer.connections])) +								) +							self.connection.send(":$bouncer 366 %s $bouncer :End of /NAMES list.\n" % (self.IRC.identity.nick)) + +							### Give operator mode to user. +							self.connection.send(":*Bouncer* MODE $bouncer +o %s\n" % (self.IRC.identity.nick)) + + +							### Join user to channels. +							for channel in self.IRC.identity.channels: +								### JOIN command +								self.connection.send(":%s!%s@%s JOIN :%s\n" % (self.IRC.identity.nick, self.IRC.identity.idnt, self.IRC.identity.host, channel.name)) + +								### Topic +								self.connection.send(":%s 332 %s %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, channel.name, channel.topic)) +								self.connection.send(":%s 333 %s %s %s %s\n" % (self.IRC.serv, self.IRC.identity.nick, channel.name, channel.topicsetby, channel.topictime)) + +								### Determine if +s or +p modes are set in channel +								secret="s" in channel.modes.keys() and channel.modes["s"] +								private="p" in channel.modes.keys() and channel.modes["p"] + +								### Construct NAMES for channel. +								namesusers=[] +								modes, symbols=self.IRC.supports["PREFIX"] +								self.connection.send(":%s 353 %s %s %s :%s\n" % ( +									self.IRC.serv, +									self.IRC.identity.nick, +									"@" if secret else ("*" if private else "="), +									channel.name, +									string.join([string.join([symbols[k] if modes[k] in channel.modes.keys() and user in channel.modes[modes[k]] else "" for k in xrange(len(modes))],"")+user.nick for user in channel.users])) +									) +								self.connection.send(":%s 366 %s %s :End of /NAMES list.\n" % (self.IRC.serv, self.IRC.identity.nick, channel.name)) + +							### Announce connection to all other bouncer connections. +							for bouncerconnection in self.bouncer.connections: +								try: +									bouncerconnection.connection.send(":%s!%s@%s JOIN :$bouncer\n" % (self.label, self.idnt, self.addr[0])) +									bouncerconnection.connection.send(":*Bouncer* MODE $bouncer +o %s\n" % (self.label)) +								except: +									pass +							self.IRC.lock.release() +						else: ### User not found +							self.quitmsg="Access Denied" +							break +					else: ### Client did not send USER command when expected +						self.quitmsg="Access Denied" +						break + +				elif cmd.upper()=="QUIT": +					self.quitmsg=extinfo +					break + +				elif cmd.upper()=="PING": +					self.connection.send(":%s PONG %s :%s\n" % (self.IRC.serv, self.IRC.serv, self.IRC.identity.nick)) + +				elif cmd.upper()=="WHO" and target.lower()=="$bouncer": +					for bouncerconnection in self.bouncer.connections: +						self.connection.send(":$bouncer 352 %s $bouncer %s %s $bouncer %s H@ :0 %s\n" % (self.IRC.identity.nick, bouncerconnection.idnt, bouncerconnection.host, bouncerconnection.label, bouncerconnection.IRC)) +					self.connection.send(":$bouncer 315 %s $bouncer :End if /WHO list.\n" % (self.IRC.identity.nick)) + +				elif cmd.upper() in ("PRIVMSG", "NOTICE"): +					### Check if CTCP +					ctcp=re.findall("^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$",extinfo) + + +					if target.lower()=="$bouncer": ### Message to internal bouncer control channel +						if ctcp and cmd.upper()=="NOTICE": +							(ctcptype, ext)=ctcp[0] ### Unpack CTCP info +							if ctcptype=="VERSION": ### Client is sending back version reply +								for bouncerconnection in self.bouncer.connections: +									reply=":%s!%s@%s PRIVMSG $bouncer :Version reply: %s\n" % (self.label, self.idnt, self.addr[0], ext) +									try: +										bouncerconnection.connection.send(reply) +									except: +										pass +					elif ctcp: ### If CTCP, only want to  +						(ctcptype, ext)=ctcp[0] ### Unpack CTCP info + +						if ctcptype=="LAGCHECK": ### Client is doing a lag check. No need to send to IRC network, just reply back. +							self.connection.send(":%s!%s@%s %s\n" % (self.IRC.identity.nick, self.IRC.identity.idnt, self.IRC.identity.host, line)) +						else: +							self.IRC.raw(line, origin=self) +					else: +						self.IRC.raw(line, origin=self) + +				elif cmd.upper() == "MODE": ### Will want to determine is requesting modes, or attempting to modify modes. +					if target and "CHANTYPES" in self.IRC.supports.keys() and target[0] in self.IRC.supports["CHANTYPES"]: +						if params=="": +							channel=self.IRC.channel(target) +							modes=channel.modes.keys() +							modestr="".join([mode for mode in modes if mode not in self.IRC.supports["CHANMODES"][0]+self.IRC.supports["PREFIX"][0] and channel.modes[mode]]) +							params=" ".join([channel.modes[mode] for mode in modes if mode in self.IRC.supports["CHANMODES"][1]+self.IRC.supports["CHANMODES"][2] and channel.modes[mode]]) +							self.connection.send(":%s 324 %s %s +%s %s\n" % (self.IRC.serv, self.IRC.identity.nick, channel.name, modestr, params)) +							self.connection.send(":%s 329 %s %s %s\n" % (self.IRC.serv, self.IRC.identity.nick, channel.name, channel.created)) +						elif re.match("^\\+?[%s]+$"%self.IRC.supports["CHANMODES"][0], params) and extinfo=="": +							#print "ddd Mode List Request", params +							channel=self.IRC.channel(target) +							redundant=[] +							for mode in params.lstrip("+"): +								if mode in redundant or mode not in listnumerics.keys(): continue +								i,e,l=listnumerics[mode] +								if mode in channel.modes.keys(): +									for (mask, setby, settime) in channel.modes[mode]: +										self.connection.send(":%s %d %s %s %s %s %s\n" % (self.IRC.serv, i, channel.context.identity.nick, channel.name, mask, setby, settime)) +								self.connection.send(":%s %d %s %s :End of %s\n" % (self.IRC.serv, e, channel.context.identity.nick, channel.name, l)) +								redundant.append(mode) +						else: +							self.IRC.raw(line, origin=self) +					elif params=="" and target.lower()==self.IRC.identity.nick.lower(): +						self.connection.send(":%s 221 %s +%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.identity.modes)) +						if "s" in self.IRC.identity.modes and self.IRC.identity.snomask: +							self.connection.send(":%s 008 %s +%s :Server notice mask\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.identity.snomask)) +					else: +						self.IRC.raw(line, origin=self) +				else: +					self.IRC.raw(line, origin=self) + + + + + + + + +		except SystemExit: +			pass ### No need to pass error message if break resulted from sys.exit() +		except: +			exc,excmsg,tb=sys.exc_info() +			self.quitmsg=str(excmsg) +		finally: +			if self.IRC and self.IRC.lock.locked(): self.IRC.lock.release() ### Release lock in case lock is locked. +			try: +				self.connection.send("ERROR :Closing link: (%s@%s) [%s]\n" % (self.IRC.identity.nick if self.IRC else "*", self.host, self.quitmsg)) +				self.connection.shutdown(1) +				self.connection.close() +			except: +				pass + +			if self in self.bouncer.connections: +				self.bouncer.connections.remove(self) + +				### Announce QUIT to other bouncer connections. +				for bouncerconnection in self.bouncer.connections: +					try: +						bouncerconnection.connection.send(":%s!%s@%s QUIT :%s\n" % (self.label, self.idnt, self.host, self.quitmsg)) +					except: +						pass @@ -0,0 +1,976 @@ +#!/usr/bin/python +from threading import Thread, Event, Lock +import re, time, sys, string, socket, os, platform, traceback, Queue, ssl + +class Connection(Thread): +	def __init__(self, nick="ircbot", ident="python", realname="Python IRC Library", passwd=None, server="", port=None, ssl=False, autoreconnect=True, log=sys.stderr, timeout=300, retrysleep=5, maxretries=15, onlogin=None): +		self.__name__="pyIRC" +		self.__version__="1.0.0rc1" +		self.__author__="Brian Sherson" +		self.__date__="May 22, 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.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=[] + + +		### Initialize IRC environment variables +		self.motdgreet="" +		self.motd=[] +		self.identity=None + +		self.users=[] +		self.channels=[] +		self.motdgreet="" +		self.motd=[] + +		self.supports={} + +		self.lock=Lock() +		self.loglock=Lock() +		self.sendlock=Lock() +		self.outgoing=Queue.Queue() + +		### Initialize IRC environment variables +		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: +					exc,excmsg,tb=sys.exc_info() +					self.loglock.acquire() +					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.loglock.release() +					if exceptions: ### If set to true, we raise the exception. +						raise + +	def addModule(self, module, **params): +		if module in self.modules: +			raise BaseException, "Module already added." +		try: +			self.event("onModuleAdd", [module], exceptions=True, **params) +		except: +			raise +		finally: +			if self.lock.locked(): self.lock.release() +		self.modules.append(module) + +	def insertModule(self, index, module, **params): +		if module in self.modules: +			raise BaseException, "Module already added." +		try: +			self.event("onModuleAdd", [module], exceptions=True, **params) +		except: +			raise +		finally: +			if self.lock.locked(): self.lock.release() +		self.modules.insert(index, module) + +	def rmModule(self, module, **params): +		self.modules.remove(module) +		try: +			self.event("onModuleRem", [module], exceptions=True, **params) +		except: +			raise +		finally: +			if self.lock.locked(): self.lock.release() + +	def run(self): +		self.quitexpected=False +		server=self.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) +			self.event("onLogOpen", modules) + +			self.loglock.acquire() +			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.loglock.release() + +			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. +				for retry in xrange(self.maxretries): ### Enter retry loop +					self.event("onConnectAttempt", self.modules) + +					self.loglock.acquire() +					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() +					self.loglock.release() + +					try: +						if self.ssl: +							s=socket.socket(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_INET, socket.SOCK_STREAM) +						self.connection.connect((self.server, self.port)) +						self.lock.acquire() +						self.connected=True +					except: +						self.loglock.acquire() +						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() +						self.loglock.release() +						if self.quitexpected: sys.exit() + +					if self.connected: break +					if retry<self.maxretries-1: time.sleep(self.retrysleep) + +				if not self.connected: +					self.loglock.acquire() +					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() +					self.loglock.release() +					break + +				### Connection succeeded +				try: +					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) +					self.lock.release() + +					### 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): +							self.lock.acquire() +							self.connection.send("PONG :%s\n" % ping[0]) +							self.lock.release() +							continue + +						self.loglock.acquire() +						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() +						self.loglock.release() + +						### 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! +									self.lock.acquire() +									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) +									self.lock.release() + +								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 + +							### Send line to modules first +							self.event("onRecv", self.modules, line=line, data=parsed) + +							### Major codeblock here! Track IRC state. +							self.lock.acquire() +							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(): +										self.loglock.acquire() +										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.loglock.release() +										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]): +									self.loglock.acquire() +									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.loglock.release() +							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]): +									self.loglock.acquire() +									print >>self.log, "%s *** User %s!%s@%s was orphaned when parting %s."%(timestamp, user.nick, user.idnt, user.host, channel.name) +									self.loglock.release() +							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) +									channel.lock.acquire(True) +									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) +									channel.lock.release() +								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) + +								channel.lock.acquire(True) +								if channel.name!=target: channel.name=target ### Server seems to have changed the idea of the case of the channel name +								channel.topic=extinfo +								channel.lock.release() +							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) +							self.lock.release() + +						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. +					self.loglock.acquire() +					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() +					self.loglock.release() +				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. +					### Release locked locks, if any. +					if self.lock.locked(): self.lock.release() +					for channel in self.channels: +						if channel.lock.locked(): channel.lock.release() + +					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 + +					self.loglock.acquire() +					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() +					self.loglock.release() + +				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 +			self.loglock.acquire() +			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() +			self.loglock.release() +			sys.exit() + +		finally: +			self.loglock.acquire() +			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() +			self.loglock.release() + +			Thread.__init__(self) ### Makes thread restartable + +	def __repr__(self): +		server=self.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="*" +		protocol="ircs" if self.ssl else "irc" +		return "<IRC Context: %(nick)s!%(ident)s@%(host)s on %(protocol)s://%(server)s:%(port)s>" % locals() +		#else: return "<IRC Context: irc%(ssl)s://%(server)s:%(port)s>" % locals() +	def quit(self, msg="", origin=None): +		self.quitexpected=True +		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]]) +			self.loglock.acquire() +			print >>self.log, "%s *** User %s created."%(timestamp, nick) +			self.log.flush() +			self.loglock.release() +			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) +			self.loglock.acquire() +			print >>self.log, "%s *** Channel %s created."%(timestamp, name) +			self.log.flush() +			self.loglock.release() +			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 "<Channel: "+self.name+"@"+self.context.server+"/"+str(self.context.port)+">" +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 "<User: %(nick)s!%(idnt)s@%(host)s>" % 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) + +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 +			if re.match("^quit(\\s.*)?$",line,re.I): self.IRC.quitexpected=True +			timestamp=reduce(lambda x,y: x+":"+y,[str(t).rjust(2,"0") for t in time.localtime()[0:6]]) +			self.IRC.lock.acquire() +			try: +				self.IRC.connection.send("%(line)s\n" % vars()) +			except: +				try: +					self.IRC.connection.shutdown(0) +				except: +					pass +				raise +			finally: +				if self.IRC.lock.locked(): +					self.IRC.lock.release() +			self.IRC.loglock.acquire() +			print >>self.IRC.log, "%(timestamp)s >>> %(line)s" % vars() +			self.IRC.log.flush() +			self.IRC.loglock.release() +			match=re.findall("^(.+?)(?:\\s+(.+?)(?:\\s+(.+?))??)??(?:\\s+:(.*))?$", line, re.I) +			(cmd, target, params, extinfo)=match[0] +			for module in self.IRC.modules: +				#if module==origin: continue +				if "onSend" in dir(module) and callable(module.onSend): +					try: +						module.onSend(self.IRC, line, (cmd, target, params, extinfo), origin) +					except: +						exc,excmsg,tb=sys.exc_info() +						self.IRC.loglock.acquire() +						print >>self.IRC.log, "%(timestamp)s !!! Exception in module %(module)s" % vars() +						for tbline in traceback.format_exc().split("\n"): +							print >>self.IRC.log, "%(timestamp)s !!! %(tbline)s" % vars() +						self.IRC.loglock.release() +			#self.IRC.connection.send(line) +			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 @@ -7,6 +7,8 @@ class LogRotate(Thread):  	def __init__(self, logger):  		self.logger=logger  		Thread.__init__(self) +		self.daemon=True +		self.start()  	def run(self):  		Y, M, D, h, m, s, w, d, dst=time.localtime()  		nextrotate=int(time.mktime((Y, M, D+1, 0, 0, 0, 0, 0, -1))) @@ -15,7 +17,7 @@ class LogRotate(Thread):  		while True:  			#print "LogRotate will sleep until %d/%d/%d %d:%02d:%02d"%(time.localtime(nextrotate)[:6])  			while nextrotate>time.time(): ### May need to do this in a loop in case the following time.sleep command wakes up a second too early. -				time.sleep(max(0.25, nextrotate-time.time())) +				time.sleep(max(0.1, min((nextrotate-time.time(), 3600))))  			self.logger.rotatelock.acquire()  			if all([not log or log.closed for log in self.logger.consolelogs.values()+self.logger.channellogs.values()]):  				### If there are no logs to rotate @@ -26,19 +28,21 @@ class LogRotate(Thread):  			#print "Rotating Logs"  			now=time.localtime()  			timestamp=reduce(lambda x,y: x+":"+y,[str(t).rjust(2,"0") for t in now[0:6]]) -			for network in self.logger.labels.keys(): -				#network.loglock.acquire() -				if network.connected: -					network.lock.acquire() -					self.logger.rotateConsoleLog(network) -					if network.identity: -						for channel in network.identity.channels: -							self.logger.rotateChannelLog(channel) -					network.lock.release() +			for IRC in self.logger.labels.keys(): +				#IRC.loglock.acquire() +				if IRC.connected: +					IRC.lock.acquire() +					try: +						self.logger.rotateConsoleLog(IRC) +						if IRC.identity: +							for channel in IRC.identity.channels: +								self.logger.rotateChannelLog(channel) +					finally: +						IRC.lock.release()  			nextrotate+=3600*24  class Logger(object): -	def __init__(self, logroot, **networks): +	def __init__(self, logroot):  		self.logroot=logroot  		path=[logroot]  		#print path @@ -55,66 +59,84 @@ class Logger(object):  		#return  		self.consolelogs={}  		self.channellogs={} -		self.networks=networks  		self.labels={} -		for (label,network) in networks.items(): -			if not os.path.isdir(os.path.join(self.logroot, label)): -				os.mkdir(os.path.join(self.logroot, label)) -			if network in self.labels.keys(): -				raise BaseException, "Network already exists" -			self.labels[network]=label -			network.lock.acquire() -			network.modules.append(self) -			network.lock.release()  		self.rotatelock=Lock()  		self.logrotate=None -	def addNetworks(self, **networks): -		for (label,network) in networks.items(): +	def addNetworks(self, **IRCs): ### Deprecated +		for (label,IRC) in IRCs.items():  			if not os.path.isdir(os.path.join(self.logroot, label)):  				os.mkdir(os.path.join(self.logroot, label))  			if label in self.labels.values():  				raise BaseException, "Label already exists" -			if network in self.networks.keys(): +			if IRC in self.IRCs.keys():  				raise BaseException, "Network already exists" -		for (label,network) in networks.items(): -			self.labels[network]=label -			network.lock.acquire() -			network.modules.append(self) -			if network.connected: -				openConsoleLog(network) -			network.lock.release() -	def removeNetworks(self, *networks): -		for network in networks: -			if network not in self.networks.keys(): +		for (label,IRC) in IRCs.items(): +			self.labels[IRC]=label +			IRC.lock.acquire() +			IRC.modules.append(self) +			if IRC.connected: +				self.openConsoleLog(IRC) +			IRC.lock.release() +	def removeNetworks(self, *IRCs): ### Deprecated +		for IRC in IRCs: +			if IRC not in self.IRCs.keys():  				raise BaseException, "Network not added" -		for network in networks: -			network.lock.acquire() -			network.modules.append(self) -			if network.connected: -				closeConsoleLog(network) -			network.lock.release() -			del self.labels[network] -			del self.consolelogs[network] -	def openConsoleLog(self, network): +		for IRC in IRCs: +			IRC.lock.acquire() +			IRC.modules.append(self) +			if IRC.connected: +				self.closeConsoleLog(IRC) +			IRC.lock.release() +			del self.labels[IRC] +			del self.consolelogs[IRC] + +	def onModuleAdd(self, IRC, label): +		if label in self.labels.values(): +			raise BaseException, "Label already exists" +		if IRC in self.labels.keys(): +			raise BaseException, "Network already exists" +		if not os.path.isdir(os.path.join(self.logroot, label)): +			os.mkdir(os.path.join(self.logroot, label)) +		IRC.lock.acquire() +		self.labels[IRC]=label +		if IRC.connected: +			self.openConsoleLog(IRC) +			if IRC.identity: +				for channel in IRC.identity.channels: +					self.openChannelLog(channel) +		IRC.lock.release() + +	def onModuleRem(self, IRC): +		IRC.lock.acquire() +		if IRC.connected: +			for channel in self.channellogs.keys(): +				if channel in IRC.channels: +					if not self.channellogs[channel].closed: self.closeChannelLog(channel) +					del self.channellogs[channel] +			if not self.consolelogs[IRC].closed: self.closeConsoleLog(IRC) +		IRC.lock.release() +		del self.labels[IRC] +		del self.consolelogs[IRC] + +	def openConsoleLog(self, IRC):  		self.rotatelock.acquire()  		if not self.logrotate or not self.logrotate.isAlive():  			self.logrotate=LogRotate(self) -			self.logrotate.daemon=True -			self.logrotate.start()  		self.rotatelock.release()  		now=time.localtime()  		timestamp=reduce(lambda x,y: x+":"+y,[str(t).rjust(2,"0") for t in now[0:6]]) -		self.consolelogs[network]=open(os.path.join(self.logroot, self.labels[network], "console-%04d.%02d.%02d.log"%now[:3]), "a") -		print >>self.consolelogs[network], "%s %s ### Log session started" % (timestamp, time.tzname[now[-1]]) -		self.consolelogs[network].flush() -	def closeConsoleLog(self, network): -		now=time.localtime() -		timestamp=reduce(lambda x,y: x+":"+y,[str(t).rjust(2,"0") for t in now[0:6]]) -		print >>self.consolelogs[network], "%s %s ### Log session ended" % (timestamp, time.tzname[now[-1]]) -		self.consolelogs[network].close() -	def rotateConsoleLog(self, network): -		self.closeConsoleLog(network) -		self.openConsoleLog(network) +		self.consolelogs[IRC]=open(os.path.join(self.logroot, self.labels[IRC], "console-%04d.%02d.%02d.log"%now[:3]), "a") +		print >>self.consolelogs[IRC], "%s %s ### Log session started" % (timestamp, time.tzname[now[-1]]) +		self.consolelogs[IRC].flush() +	def closeConsoleLog(self, IRC): +		if IRC in self.consolelogs.keys() and type(self.consolelogs[IRC])==file and not self.consolelogs[IRC].closed: +			now=time.localtime() +			timestamp=reduce(lambda x,y: x+":"+y,[str(t).rjust(2,"0") for t in now[0:6]]) +			print >>self.consolelogs[IRC], "%s %s ### Log session ended" % (timestamp, time.tzname[now[-1]]) +			self.consolelogs[IRC].close() +	def rotateConsoleLog(self, IRC): +		self.closeConsoleLog(IRC) +		self.openConsoleLog(IRC)  	def openChannelLog(self, channel):  		self.rotatelock.acquire() @@ -151,10 +173,11 @@ class Logger(object):  			if channel.created: print >>self.channellogs[channel], "%s %s <<< :%s 329 %s %s %s" % (timestamp, time.tzname[now[-1]], channel.context.serv, channel.context.identity.nick, channel.name, channel.created)  		self.channellogs[channel].flush()  	def closeChannelLog(self, channel): -		now=time.localtime() -		timestamp=reduce(lambda x,y: x+":"+y,[str(t).rjust(2,"0") for t in now[0:6]]) -		print >>self.channellogs[channel], "%s %s ### Log session ended" % (timestamp, time.tzname[now[-1]]) -		self.channellogs[channel].close() +		if channel in self.channellogs.keys() and type(self.channellogs[channel])==file and not self.channellogs[channel].closed: +			now=time.localtime() +			timestamp=reduce(lambda x,y: x+":"+y,[str(t).rjust(2,"0") for t in now[0:6]]) +			print >>self.channellogs[channel], "%s %s ### Log session ended" % (timestamp, time.tzname[now[-1]]) +			self.channellogs[channel].close()  	def rotateChannelLog(self, channel):  		self.closeChannelLog(channel)  		self.openChannelLog(channel) diff --git a/mibbit.py b/mibbit.py new file mode 100644 index 0000000..73a929d --- /dev/null +++ b/mibbit.py @@ -0,0 +1,31 @@ +#!/usr/bin/python +import re + +class Mibbit(object): +	def onRecv(self, IRC, line, data): +		if data==None: return +		(origin, ident, host, cmd, target, params, extinfo)=data +		if len(target) and target[0]!="#": +			#print target +			#print cmd +			#print extinfo +			target=IRC.user(target) +			if cmd=="NOTICE" and target==IRC.identity: +				matches=re.findall("^\\*\\*\\* CONNECT: Client connecting on port ([0-9]+) \\(class (.*?)\\): (.+)!([0-9a-f]{8})@(.+\\.mibbit\\.com) \\((.+)\\) \\[(.+)\\]$",extinfo) +				#print matches +				"*** REMOTECONNECT: Client connecting at hypocrisy.insomniairc.net: B!463df443@ircip4.mibbit.com (109.169.29.95) [rrcs-70-61-244-67.central.biz.rr.com]" +				if matches: +					(port, cls, nick, hexip, mibbithost, mibbitip, host)=matches[0] +					IRC.raw("CHGHOST %s %s"%(nick, host)) +					IRC.raw("CHGIDENT %s %s"%(nick, "mibbit")) +					IRC.raw("CHGNAME %s %s"%(nick, "Mibbit User")) +					return +				matches=re.findall("^\\*\\*\\* REMOTECONNECT: Client connecting at (.+?): (.+)!([0-9a-f]{8})@(.+\\.mibbit\\.com) \\((.+)\\) \\[(.+)\\]$",extinfo) +				#print matches +				"*** REMOTECONNECT: Client connecting at hypocrisy.insomniairc.net: B!463df443@ircip4.mibbit.com (109.169.29.95) [rrcs-70-61-244-67.central.biz.rr.com]" +				if matches: +					(remotehost, nick, hexip, mibbithost, mibbitip, host)=matches[0] +					IRC.raw("CHGHOST %s %s"%(nick, host)) +					IRC.raw("CHGIDENT %s %s"%(nick, "mibbit")) +					IRC.raw("CHGNAME %s %s"%(nick, "Mibbit User")) +					return @@ -9,61 +9,56 @@ class SED(object):  		self.pattern=r"^!s([,/#])((?:.|\\\1)*)\1((?:.|\\\1)*)\1([ig]*)$"  	def onRecv(self, IRC, line, data):  		if data==None: return -		(origin, ident, host, cmd, target, params, extinfo)=data -		if len(target) and target[0]=="#": target=IRC.channel(target) -		if cmd=="PRIVMSG": -			matches=re.findall(self.pattern,extinfo) -			if matches: -				separator, find, replace, flags=matches[0] -				find=re.sub("\\\\([,/#\\\\])","\\1",find) -				replace=re.sub("\\\\(,/#\\\\)","\\1",replace) -				match=False -				for t, IRC2, (origin2, ident2, host2, cmd2, target2, params2, extinfo2) in self.history.__reversed__(): -					if target!=IRC2.channel(target2): continue -					try: -						if re.findall(find, extinfo2): -							sub=re.sub(find, replace, extinfo2, flags=re.I if "i" in flags else 0) -							target.msg("What %s really meant to say was: %s" % (origin2, sub), origin=self) -							match=True -							break -					except: -						target.msg("%s: Invalid syntax" % (origin), origin=self) -						raise -				if not match: -					target.msg("%s: I tried. I really tried! But I could not find the pattern: %s" % (origin, find), origin=self) -			else: -				self.history.append((time.time(), IRC, data)) -		while len(self.history) and self.history[0][0]<time.time()-1800: del self.history[0] +		self.replace(IRC, *data)  	def onSend(self, IRC, line, data, origin):  		if origin==self: return  		#print data  		(cmd, target, params, extinfo)=data -		if len(target) and target[0]=="#": target=IRC.channel(target) -		if cmd=="PRIVMSG": +		self.replace(IRC, IRC.identity.nick, IRC.identity.idnt, IRC.identity.host, *data) +	def replace(self, IRC, origin, ident, host, cmd, target, params, extinfo): +		if len(target) and target[0]=="#" and cmd=="PRIVMSG": +			target=IRC.channel(target)  			matches=re.findall(self.pattern,extinfo) -			#print matches  			if matches:  				separator, find, replace, flags=matches[0] -				find=re.sub("\\\\(.)","\\1",find) -				replace=re.sub("\\\\(.)","\\1",replace) +				#print matches +				find=re.sub("\\\\([,/#\\\\])","\\1",find) +				replace=re.sub("\\\\(,/#\\\\)","\\1",replace) +				#print find, replace  				match=False +				#print self.history +				#print find +				#print replace  				for t, IRC2, (origin2, ident2, host2, cmd2, target2, params2, extinfo2) in self.history.__reversed__(): -					#print target -					#print target2 -					#print IRC2.channel(target2) -					if target!=IRC2.channel(target2): continue -					try: -						if re.findall(find, extinfo2): -							sub=re.sub(find, replace, extinfo2, flags=re.I if "i" in flags else 0) -							#print sub -							target.msg("What %s really meant to say was: %s" % (origin2, sub), origin=self) -							match=True -							break -					except: -						target.msg("%s: Invalid syntax" % (origin), origin=self) -						raise +					#print target, target2, origin2, extinfo2 +					if target!=target2: continue +					action=re.findall("^\x01ACTION\\s+(.*)\x01$", extinfo2) +					#print action +					if action: +						try: +							if re.findall(find, action[0]): +								sub=re.sub(find, replace, action[0], flags=re.I if "i" in flags else 0) +								target.msg("What %s really meant was: *%s %s" % (origin2, origin2, sub), origin=self) +								match=True +								break +						except: +							target.msg("%s: Invalid syntax" % (origin), origin=self) +							raise +					else: +						try: +							if re.findall(find, extinfo2): +								sub=re.sub(find, replace, extinfo2, flags=re.I if "i" in flags else 0) +								target.msg("What %s really meant to say was: %s" % (origin2, sub), origin=self) +								match=True +								break +						except: +							target.msg("%s: Invalid syntax" % (origin), origin=self) +							raise  				if not match: -					target.msg("%s: I tried. I really tried! But I could not find the pattern: %s" % (IRC.identity.nick, find), origin=self) +					target.msg("%s: I tried. I really tried! But I could not find the pattern: %s" % (origin, find), origin=self)  			else: -				self.history.append((time.time(), IRC, (IRC.identity.nick, IRC.identity.idnt, IRC.identity.host)+data)) +				#print "History",(origin, ident, host, cmd, target, params, extinfo) +				self.history.append((time.time(), IRC, (origin, ident, host, cmd, target, params, extinfo))) +				#print self.history  		while len(self.history) and self.history[0][0]<time.time()-1800: del self.history[0] +		#print self.history diff --git a/wallet.py b/wallet.py new file mode 100644 index 0000000..05321f0 --- /dev/null +++ b/wallet.py @@ -0,0 +1,37 @@ +#!/usr/bin/python +import pickle, Crypto.Cipher.Blowfish, os, getpass +from threading import Lock + +class Wallet(dict): +	def __init__(self, filename): +		self.lock=Lock() +		if os.path.isfile(filename): +			self.f=open(filename,"rb+") +			self.passwd=getpass.getpass() +			self.crypt=Crypto.Cipher.Blowfish.new(self.passwd) +			contents_encrypted=self.f.read() +			contents=self.crypt.decrypt(contents_encrypted+"\x00"*((-len(contents_encrypted))%8)) +			if contents.startswith(self.passwd): +				self.update(dict(pickle.loads(contents[len(self.passwd):]))) +			else: +				self.f.close() +				raise BaseException, "Incorrect Password" +		else: +			self.f=open(filename,"wb+") +			passwd=self.passwd=None +			while passwd==None or passwd!=self.passwd: +				passwd=getpass.getpass("Enter new password: ") +				self.passwd=getpass.getpass("Confirm new password: ") +				if passwd!=self.passwd: print "Passwords do not match!" +			self.crypt=Crypto.Cipher.Blowfish.new(self.passwd) +			self.flush() +	def flush(self): +		contents=self.passwd+pickle.dumps(self.items(), protocol=2) +		self.lock.acquire() +		try: +			self.f.seek(0) +			self.f.write(self.crypt.encrypt(contents+"\x00"*((-len(contents))%8))) +			self.f.truncate() +			self.f.flush() +		finally: +			self.lock.release() | 
