#!/usr/bin/python """ ircAsync -- An asynchronous IRC client interface. This is intended as a component in a semantic web agent with several interfaces, one of them being IRC. It's implemented on top of asyncore so that the same agent can export an HTTP interface in asynchronous, non-blocking style. see Log at end for recent changes/status info. Share and Enjoy. Open Source license: Copyright (c) 2001 W3C (MIT, INRIA, Keio) http://www.w3.org/Consortium/Legal/copyright-software-19980720 $Id: ircAsync.py,v 1.9 2003/10/15 01:54:10 timbl Exp $ """ # asyncore -- Asynchronous socket handler # http://www.python.org/doc/current/lib/module-asyncore.html import string, re import socket import asyncore, asynchat #RFC 2811: Internet Relay Chat: Client Protocol #2.3 Messages # http://www.valinor.sorcery.net/docs/rfc2812/2.3-messages.html SPC="\x20" CR="\x0d" LF="\x0a" CRLF=CR+LF Port = 6667 # commands... PRIVMSG = 'PRIVMSG' NOTICE = 'NOTICE' PING='PING' PONG='PONG' USER='USER' NICK='NICK' JOIN='JOIN' PART='PART' INVITE='INVITE' QUIT='QUIT' # reply codes... RPL_WELCOME='001' class T(asynchat.async_chat): def __init__(self): asynchat.async_chat.__init__(self) self.bufIn = '' self.set_terminator(CRLF) # public attributes # no blanks in nick. # openprojects.net says: # Connect with your real username, in lowercase. # If your mail address were foo@bar.com, your username would be foo. # other limitations? @@see IRC RFC?""" self.nick = 'svnNotify' # manage this with __getattr__() in stead? hmm... self.userid = 'jpa' self.fullName = 'svnNotify based on ircAsync.py' self._startChannels = [] self._dispatch = [] self._doc = [] def makeConn(self, host, port): self.create_socket(socket.AF_INET, socket.SOCK_STREAM) debug("connecting to...", host, port) self.connect((host, port)) self.bufIn = '' def todo(self, args, *text): command = string.join(args) if text: command = command + ' :' + string.join(text) self.push(command + CRLF) debug("sent/pushed command:", command) # asyncore methods def handle_connect(self): debug("connected") #@@ hmm... RFC says mode is a bitfield, but # irc.py by @@whathisname says +iw string. self.todo([NICK, self.nick]) self.todo([USER, self.userid, "+iw", self.nick], self.fullName) # asynchat methods def collect_incoming_data(self, bytes): self.bufIn = self.bufIn + bytes def found_terminator(self): #debug("found terminator", self.bufIn) line = self.bufIn self.bufIn = '' if line[0] == ':': origin, line = string.split(line[1:], ' ', 1) else: origin = None try: args, text = string.split(line, ' :', 1) except ValueError: args = line text = '' args = string.split(args) debug("from::", origin, "|message::", args, "|text::", text) self.rxdMsg(args, text, origin) def bind(self, thunk, command, textPat=None, doc=None): """ thunk is the routine to bind; it's called ala thunk(matchObj or None, origin, args, text) command is one of the commands above, e.g. PRIVMSG textpat is None or a regex object or string to compile to one doc should be a list of strings; each will go on its own line""" if type(textPat) is type(""): textPat = re.compile(textPat) self._dispatch.append((command, textPat, thunk)) if doc: self._doc = self._doc + doc def rxdMsg(self, args, text, origin): if args[0] == PING: self.todo([PONG, text]) for cmd, pat, thunk in self._dispatch: if args[0] == cmd: if pat: #debug('dispatching on...', pat) m = pat.search(text) if m: thunk(m, origin, args, text) else: thunk(None, origin, args, text) def startChannels(self, chans): self._startChannels = chans self.bind(self._welcomeJoin, RPL_WELCOME) def _welcomeJoin(self, m, origin, args, text): for chan in self._startChannels: self.todo(['JOIN', chan]) def tell(self, dest, text): """send a PRIVMSG to dest, a channel or user""" for line in text.split('\n') : self.todo([PRIVMSG, dest], line) def tellLines(self, dest, text): lines = text.split("\n") for line in lines: self.tell(dest, line) def notice(self, dest, text): """send a NOTICE to dest, a channel or user""" self.todo([NOTICE, dest], text) def serverAddr(host, port): if port == Port: portPart = '' else: portPart = ":%s" % port return "irc://%s%s/" % (host, portPart) def chanAddr(host, port, chan): if port == Port: portPart = '' else: portPart = ":%s" % port if chan[0] == '&': chanPart = '%26' + chan[1:] elif chan[0] == '#': chanPart = chan[1:] else: raise ValueError # dunno what to do with this channel name return "irc://%s%s/%s" % (host, portPart, chanPart) def debug(*args): import sys sys.stderr.write("DEBUG: ") for a in args: sys.stderr.write(str(a)) sys.stderr.write("\n") from fifoRead import fifoReader def irc(hostName, port, chan): c = T() c.startChannels(chan) f = fifoReader( 'fifo', c ) f.start() try: c.makeConn(hostName, port) asyncore.loop() finally: f.stop() if __name__=='__main__': irc('irc.freenode.net', 6667, ['#jpatesttesttest']) #test('irc.openprojects.net', Port, '#rdfig')