#!/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.8 2001/08/24 05:50:52 connolly Exp $ """ # asyncore -- Asynchronous socket handler # http://www.python.org/doc/current/lib/module-asyncore.html import string, re import socket import urllib import asyncore, asynchat import RDF #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?""" # can not contain !, @, #, $, %, &, *, (, ), <, >, /, ., ,, ", ', :, # ;, ? self.nick = 'redlandbot' # manage this with __getattr__() in stead? hmm... self.userid = 'nobody' self.fullName = 'ircAsync user' self._startChannels = ['#test'] 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: try: thunk(m, origin, args, text) except: debug("aw, fooey") return 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""" self.todo([PRIVMSG, dest], text) def notice(self, dest, text): """send a NOTICE to dest, a channel or user""" self.todo([NOTICE, dest], text) def actionFmt(str): return "\001ACTION" + str + "\001" def replyTo(myNick, origin, args): target = args[1] if target == myNick: # just to me nick, user, host = splitOrigin(origin) return nick else: return target def splitOrigin(origin): if origin and '!' in origin: nick, userHost = string.split(origin, '!', 1) if '@' in userHost: user, host = string.split(userHost, '@', 1) else: user, host = userHost, None else: nick = origin user, host = None, None return nick, user, host # cf irc:// urls in Mozilla # http://www.mozilla.org/projects/rt-messaging/chatzilla/irc-urls.html # Tue, 20 Mar 2001 21:28:14 GMT 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") def test(hostName, port, chan): c = T() c.startChannels([chan]) command_dict = { 'mbox': """select ?mbox where (?p foaf:mbox ?mbox) (?p foaf:nick "%s") using foaf for """ } def spam(m, origin, args, text, c=c): c.tell(replyTo(c.nick, origin, args), "spam, spam, eggs, and spam!") c.bind(spam, PRIVMSG, r"spam\?") def bye(m, origin, args, text, c=c): c.todo([QUIT], "bye bye!") c.bind(bye, PRIVMSG, r"bye bye bot") def put(m, origin, args, text, c=c): c.tell(replyTo(c.nick, origin, args), "Adding that to my database...") try: h1 = RDF.Storage(storage_name="mysql",name="abc",options_string="merge='yes',host='127.0.0.1',user='root',password='****',database='redland'") except: h1 = RDF.Storage(storage_name="mysql",name="abc",options_string="merge='yes',host='127.0.0.1',user='root',password='****',database='redland',new='true'") model = RDF.Model(h1) debug("Adding URL: ", m.group(1)) url = m.group(1) urldata = urllib.urlopen(m.group(1)) parser=RDF.Parser(name="rdfxml") i = 0 for statement in parser.parse_string_as_stream(urldata.read(),"%s"%url): if (statement._get_object().is_literal()): new_node = RDF.Node(statement._get_object().literal_value['string']) statement2 = RDF.Statement(statement._get_subject(), statement._get_predicate(), new_node) statement=statement2 if not statement in model: model.append(statement) i = i + 1 c.tell(replyTo(c.nick, origin, args), "Added %s statements from %s. Model size is %s."%(i, url, len(model))) c.bind(put, PRIVMSG, r"bot, put (.*)$") def query(m, origin, args, text, c=c): h1 = RDF.Storage(storage_name="mysql",name="abc",options_string="merge='yes',host='127.0.0.1',user='root',password='****',database='redland'") model = RDF.Model(h1) q1 = RDF.Query("%s"%m.group(1)) try: res = q1.execute(model) except: c.tell(replyTo(c.nick, origin, args), "Bad query.") return if not res: #c.tell(replyTo(c.nick, origin, args), "Query %s returned no results"%m.group(1)) return else: reply = "" i = 0 for statement in res: i = i + 1 if (i < 5): for key in statement: reply = "%s%s "%(reply, statement[key]) reply = "%s, "%(reply) c.tell(replyTo(c.nick, origin, args), "%s"%reply) c.bind(query, PRIVMSG, r"!query (.*)$") def newquery(m, origin, args, text, c=c): command_dict[m.group(1)] = m.group(2) c.tell(replyTo(c.nick, origin, args), "New command %s is %s"%(m.group(1), command_dict[m.group(1)])) c.bind(newquery, PRIVMSG, r"!newcommand (.*?) is (.*)$") def runcommand(m, origin, args, text, c=c): h1 = RDF.Storage(storage_name="mysql",name="abc",options_string="merge='yes',host='127.0.0.1',user='root',password='****',database='redland'") model = RDF.Model(h1) q1 = "" try: q1 = RDF.Query(command_dict[m.group(1)]%m.group(2)) except: c.tell(replyTo(c.nick, origin, args), "No such command.") return res = q1.execute(model) if not res: c.tell(replyTo(c.nick, origin, args), "Query %s returned no results"%(command_dict[m.group(1)]%(m.group(2)))) else: reply = "" for statement in res: for key in statement: reply = "%s%s "%(reply, statement[key]) reply = "%s, "%(reply) c.tell(replyTo(c.nick, origin, args), "%s"%reply) c.bind(runcommand, PRIVMSG, r"!runcommand (.*?) (.*)") def info(m, origin, args, text, c=c): c.tell(replyTo(c.nick, origin, args), "I'm a Redland/Python based RDF query bot. Source at http://crschmidt.net/rdfpython/bot.txt. @@ More here") c.bind(info, PRIVMSG, r"^!info\s*$") c.makeConn(hostName, port) asyncore.loop() if __name__=='__main__': #test('irc.w3.org', 6665, '#rdfbot') test('irc.freenode.net', Port, "#swhack") #$Log: ircAsync.py,v $ #Revision 1.8 2001/08/24 05:50:52 connolly #replaced onPrivMsg, onInvite etc. by bind. introduced LOTS of bugs, but I think I fixed most of them. added some documentation, which doubles as test material. # #Revision 1.7 2001/08/21 23:03:50 connolly #general clean-up and tidying... thinking #about base URIs and contexts. # #Revision 1.6 2001/08/21 20:34:12 connolly #integrated Aaron's patches for join/part. #holding off on his default prefix patch #to think about contexts some more. # #Revision 1.5 2001/08/20 14:04:56 connolly #got rid of dead code. # #Revision 1.4 2001/08/20 07:39:21 connolly #questions work now. #syntax errors are reported. #irc login protocol revised. (fixed?) # #Revision 1.3 2001/08/20 06:16:00 connolly #woohoo! it's working... #it accepts RDF/n3 via IRC and stores it in a cwm KB. # #Revision 1.2 2001/08/20 04:49:45 connolly #rdfn3chat has one feature: replies to "help" messages #with UTSL pointer # #Revision 1.1 2001/08/20 03:58:38 connolly #connects, reponds to PINGs with PONGs. #