''' Copyright (C) 2009 by Keith Morrow IbLib is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. IbLib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with IbLib. If not, see <http://www.gnu.org/licenses/>.'''import socket, asyncore, asynchat, sys, timePASS=0SKIP=1CANCEL=2QUIT=3def ircsplit(string): if string[0] == ':': #Not from server resultA=False string=string[1:] else: resultA=True if ":" in string: resultB,resultC=string.split(":",1) else: resultB,resultC=string,None resultB=resultB.split(" ") while "" in resultB: resultB.remove("") return [resultA,resultB,resultC] quotes=["'",'"']def quotesplit(string): result=[] a=string.split() i=0 while i < len(a): isquote = False for j in quotes: if j == a[i][0]: isquote = True findendquote = False for k in range(i+1,len(a)): if a[k][-1] == j: if len(a[k]) != 1: if a[k][-2] == "\\": a[k] continue findendquote = True result.append(" ".join(a[i:k+1])[1:-1]) i = k+1 break if not findendquote: print "ERROR: could not parse input" return False print "got ending quote" break if not isquote: result.append(a[i]) i = i+1 return resultdef nicksplit(string): resultA,resultB = string.split("!",1) resultB,resultC = resultB.split("@",1) return [resultA,resultB,resultC]def commandsplit(string): if not " " in string: return string[len(config['prefix']):], [] command,args = string.split(" ",1) args=quotesplit(args) return [command[len(config['prefix']):],args]config = { 'port': 6667, 'channels': [], 'user': 'iblib', 'prefix': '!', 'flooddelay': 0, 'floodtimeout': 0, 'floodcontrol': 0, 'debug': False}hook = { #initialization hooks 'beforeconnect': [], #Before a connection is established (good for setting config data last-minute) 'afterconnect': [], #Right after a connection is established (if you need to send a server password (This will be implemented better eventually)) 'afterun': [], #After USER and NICK are sent (good for sending a nickserv password) 'afterinit': [], #After all channels have been joined #various session hooks 'join': [], #If you or someone else joins (good for an intro message) 'part': [], #If you or someone else parts (dunno what this would be good for) 'kick': [], #If you or someone else is kicked (good for automatically having the bot re-join) 'quit': [], #If you or someone else quits (only from the server) 'mode': [], #If there is a mode change on you or the channel (so you can react to a mute if needed) 'getmessage': [], #When someone says something that you can see (good for annoying regex bots) 'getcommand': [], #When someone sends a command to the bot (before it's parsed, so you can filter input if needed) 'sendmessage': [], #Just before you send a message (good for filtering output) #ending hooks 'beforedc': [], #Before disconnecting, (good for sending a quit message) 'afterdc': [] #After disconnected}def cfgDefaults(rootClass, userConfig): if not 'server' in userConfig: print "You need to specify a server to connect to. Exiting..." exit() else: config['server'] = userConfig['server'] if 'port' in config: config['port'] = userConfig['port'] if 'channels' in config: config['channels'] = userConfig['channels'] if not 'nick' in userConfig: config['nick'] = rootClass.__class__._name__ else: config['nick'] = userConfig['nick'] if 'user' in userConfig: config['user'] = userConfig['user'] if not 'real' in userConfig: config['real'] = rootClass.__class__._name__ + ' in iblib' else: config['real'] = userConfig['real'] if 'prefix' in userConfig: config['prefix'] = userConfig['prefix'] if 'flooddelay' in userConfig: config['flooddelay'] = userConfig['flooddelay'] if 'floodcontrol' in userConfig: config['floodcontrol'] = userConfig['floodcontrol'] if 'debug' in userConfig: config['debug'] = userConfig['debug']def handleHooks(name): global hook for i in hook[name]: retval = i() if retval == QUIT: print "Hook %s in %s requested to quit." % (i.__name__, name) exit(1) elif retval == CANCEL: print "Hook %s in %s requested to cancel this event." % (i.__name__, name) return CANCEL elif retval == SKIP: print "Hook %s in %s requested to skip all other hooks" % (i.__name__, name) breakdef join(channel): conn.write("JOIN %s" % channel)def part(channel, message=None): if message: conn.write("PART %s :%s" % (channel, message))def privmsg(destination,message): for i in message.splitlines(): conn.write("PRIVMSG %s :%s" % (destination, i))def notice(destination,message): for i in message.splitlines(): conn.write("NOTICE %s :%s" % (destination, i))class request: nick=None user=None host=None dest=None #only on kicks channel=None message=None method=None command=None args=Noneclass response: destination=None message=None method=Nonedef fillRequest(data): if len(data[1]) > 1: command = data[1][1].upper() if command in ['PRIVMSG','NOTICE','JOIN','PART','KICK','QUIT']: request.nick, request.user, request.host = nicksplit(data[1][0]) request.method=command if command in ['PRIVMSG','NOTICE','PART','KICK','QUIT']: request.message=data[2] else: request.message=None if command in ['PRIVMSG','NOTICE','PART','KICK']: request.channel=data[1][2] elif command=='JOIN': request.channel=data[2] else: request.channel=None if command=='KICK': request.dest=data[1][3] else: request.nick=None request.user=None request.host=None request.method=None request.command=None request.args=Nonedef dispatch(rootClass,command,args): response.method='PRIVMSG' if request.channel == None: response.destination = request.nick else: response.destination = request.channel try: response.message=getattr(rootClass, command)(*args) except AttributeError: response.message="Command %s does not exist" % command except: exc = sys.exc_info() response.message="%s: %s in %s line %d" % (exc[1].__class__.__name__,exc[1].args[0],exc[2].tb_frame.f_code.co_filename,exc[2].tb_lineno)def ircHandler(data,rootClass): fillRequest(data) if request.method==None: return elif request.method in ['PRIVMSG','NOTICE']: handleHooks('getmessage') if data[2][:len(config['prefix'])] == config['prefix']: command,args = commandsplit(data[2]) request.command = command request.args = args handleHooks('getcommand') dispatch(rootClass,command,args) handleHooks('sendmessage') for i in response.message.splitlines(): conn.write("%s %s :%s" % (response.method, response.destination, i)) elif request.method=='JOIN': handleHooks('join') elif request.method=='PART': handleHooks('part') elif request.method=='KICK': handleHooks('kick') elif request.method=='QUIT': handleHooks('quit')conn = Nonedef start(rootClass, config): global conn print "Setting up internal configuration..." cfgDefaults(rootClass, config) print "Initializing and connecting..." conn = ircConnection(rootClass) print "We have now started." try: asyncore.loop() except KeyboardInterrupt: print "Quitting..." handleHooks('beforedc') conn.flush() conn.close() handleHooks('afterdc')class ircConnection(asynchat.async_chat): global config, request, response def __init__(self,root): if not 'server' in config: print "You need to specify a server to connect to. Exiting..." exit() asynchat.async_chat.__init__(self) self.set_terminator('\r\n') self.ibuffer=[] self.buffer='' self.initialized = False self.rootClass = root self.create_socket(socket.AF_INET, socket.SOCK_STREAM) handleHooks('beforeconnect') self.connect((config['server'],config['port'])) def write(self,data): self.buffer += data+'\r\n' def flush(self): while self.writable(): self.handle_write() def handle_connect(self): handleHooks('afterconnect') self.write("USER %s * * :%s" % (config['user'],config['real'])) self.write("NICK %s" % config['nick']) handleHooks('afterun') def collect_incoming_data(self, data): self.ibuffer.append(data) def writable(self): return (len(self.buffer) > 0) def found_terminator(self): data = "".join(self.ibuffer) idata = ircsplit(data) if config['debug']: print "PS: %s" % idata self.ibuffer=[] if self.initialized: ircHandler(idata,self.rootClass) else: if idata[0] == False and idata[1][1] == "001": self.initialized = True for i in config['channels']: join(i) handleHooks('afterinit') def handle_write(self): sent = self.send(self.buffer) print "OUT: %s" % self.buffer[:sent].replace("\r\n","\\r\\n") self.buffer = self.buffer[sent:]