#!/usr/bin/python """ ircAsync -- An asynchronous IRC client interface. Originally from http://dev.w3.org/cvsweb/2000/scribe-bot/ircAsync.py?rev=1.8&content-type=text/x-cvsweb-markup 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. 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 $ ################## Originally based off w3c code, modified by crschmidt for redlandbot purposes, completely refactored by Nico Chauvat to be easier to read and more maintainable. Many thanks to Nico! For RSS feed of commits, check out CIA. http://cia.navi.cx/stats/project/julie has info, or the #julie channel on IRC. """ # asyncore -- Asynchronous socket handler # http://www.python.org/doc/current/lib/module-asyncore.html import re import socket import time import popen2 import cPickle as pickle import urllib import urllib2 import asyncore, asynchat import getopt import sys import math import xml.dom.minidom as minidom import RDF from flowcontrol import FlowControl import datauri import wrapper as extras #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' def common_namespaces(): try: f = open("namespaces.crs", "r") COMMON_NAMESPACES = pickle.load(f) f.close() except: COMMON_NAMESPACES = { 'ilike': "http://rdf.netalleynetworks.com/ilike/20040830#", 'foaf': "http://xmlns.com/foaf/0.1/", 'rdfs': "http://www.w3.org/2000/01/rdf-schema#", 'geo': "http://www.w3.org/2003/01/geo/wgs84_pos#", 'dc': "http://purl.org/dc/elements/1.1/", 'srw10': "http://purl.org/net/inkel/rdf/schemas/lang/1.0#", 'srw11': "http://purl.org/net/inkel/rdf/schemas/lang/1.1#", 'bio': "http://purl.org/vocab/bio/0.1/", 'wot': "http://xmlns.com/wot/0.1/", 'trust': "http://trust.mindswap.org/ont/trust.owl#", 'kissed': "http://www.gnowsis.org/ont/kissology", 'contact': "http://www.w3.org/2000/10/swap/pim/contact#", 'astrology': "http://www.ideaspace.net/users/wkearney/schema/astrology/0.1#", 'music': "http://www.kanzaki.com/ns/music#", 'menow': "http://schema.menow.org/#", 'contact': "http://www.w3.org/2000/10/swap/pim/contact#", 'weather': "http://purl.org/net/vocab/2004/10/weather#", 'cyc': "http://opencyc.sourceforge.net/daml/cyc.daml#", 'ical': "http://www.w3.org/2002/12/cal/ical#", } f = open("namespaces.crs",'w') pickle.dump(COMMON_NAMESPACES, f) f.close() return COMMON_NAMESPACES def make_time_string(t): t = math.floor(t) if (t < 60): return "%i seconds"%t mins = math.floor(t / 60) secs = math.floor((t/60 - mins)*60) if (mins < 60): return "%i minutes, %i seconds"%(mins,secs) hours = math.floor(mins/60) mins = math.floor((mins/60 - hours)*60) if (hours < 24): return "%i hours, %i minutes, %i seconds"%(hours,mins,secs) days = math.floor(hours/24) hours = math.floor((hours/24 - days)*24) return "%i days, %i hours, %i minutes, %i seconds"%(days,hours,mins,secs) def debug(*args): """Debugging function. Useful for printing data to stdout. Dump any number of variables or strings in and have them printed out in the bot's STDOUT output""" import sys sys.stderr.write("DEBUG: ") for a in args: sys.stderr.write(str(a)) sys.stderr.write("\n") # HTTP Related class Grab(urllib.URLopener): def __init__(self, *args): self.version = 'SemWebAgent (julie/redlandbot 0.5)' urllib.URLopener.__init__(self, *args) urllib._urlopener = Grab() urllib._urlopener.addheader("Accept","application/rdf+xml,*/*") # IRC-related ################################################################## class IRCClient(asynchat.async_chat): """ Implement part of the IRC protocol on top of async_chat """ def __init__(self): """Set up nickname, channels, other information""" 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 = "julie" # manage this with __getattr__() in stead? hmm... self.userid = 'nobody' self.fullName = 'julie - the original redlandbot' self._startChannels = ['#test'] self._dispatch = [] self._doc = [] def makeConn(self, host, port): """ connect to 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): """ queue command """ command = ' '.join(args) if text: command = '%s : %s' % (command, ' '.join(text)) command = re.sub(r"\n", "", command) command = re.sub(r"\r", "", command) self.push(command + CRLF) debug("sent/pushed command:", command) # asyncore methods def handle_connect(self): """ callback on connect """ 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): """ append bytes to buffer """ self.bufIn += bytes def found_terminator(self): """ called by asyncore when terminator found in stream """ #debug("found terminator", self.bufIn) line = self.bufIn self.bufIn = '' if line.startswith(':') : origin, line = line[1:].split(' ', 1) else: origin = None try: args, text = line.split(' :', 1) except ValueError: args = line text = '' args = args.split() #try: debug("from::", origin, "|message::", args, "|text::", text) self.process_msg(args, text, origin) #except: # pass def bind(self, callback, command, textPat=None, doc=None): """ callback is the routine to bind; it's called ala callback(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 isinstance(textPat, str) : textPat = re.compile(textPat) self._dispatch.append( (command, textPat, callback) ) if doc: self._doc = self._doc + doc def process_msg(self, args, text, origin): """ process newly received message """ if args[0] == PING: self.todo([PONG, text]) for cmd, pat, callback in self._dispatch: if args[0] == cmd: if pat: #debug('dispatching on...', pat) m = pat.search(text) if m: try: callback(m, origin, args, text) except Exception, exc: self.tell(reply_to(self.nick, origin, args), "Error: %s"%exc) else: try: callback(None, origin, args, text) except Exception, exc: self.tell(reply_to(self.nick, origin, args), "Error: %s"%exc) def startChannels(self, channels): """ connect to channels """ self._startChannels = channels debug(channels) self.bind(self._welcomeJoin, RPL_WELCOME) def _welcomeJoin(self, m, origin, args, text): """Reply to welcome message: start joining channels, send identification string to nickserv.""" for chan in self._startChannels: self.todo([JOIN, chan]) self.todo([PRIVMSG, "nickserv"], "identify botbot") def tell(self, dest, text): """ send a PRIVMSG to dest, a channel or user """ if (len(text) > 450): reply1 = text[:450] reply2 = text[450:900] self.todo([PRIVMSG, dest], "%s"%reply1) self.todo([PRIVMSG,dest], "%s"%reply2) else: self.todo([PRIVMSG, dest], "%s"%text) def notice(self, dest, text): """ send a NOTICE to dest, a channel or user """ self.todo([NOTICE, dest], text) # 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 split_origin(origin): """ return (nick, user, host) """ if origin and '!' in origin: nick, userHost = origin.split('!', 1) if '@' in userHost: user, host = userHost.split('@', 1) else: user, host = userHost, None else: nick = origin user, host = None, None return nick, user, host def reply_to(myNick, origin, args): """ return nick or target, for use in determinig whether message is private or to channel """ target = args[1] if target == myNick: # just to me nick, user, host = split_origin(origin) return nick else: return target # RDF-related ################################################################## def shorten_uri(Node): """Shortens URLs using common namespaces for cleaner IRC output""" if Node.is_resource(): uri = Node.uri for key, value in common_namespaces().items() : p = re.compile("%s" % value) uri = p.sub("%s:" % key, "%s" % uri) return str(uri) return str(Node) def remove_datatype(mystr): return re.sub("\^\^<.*>", "", mystr) def get_storage(password) : """Fetches Redland Storage. If you wish to use something other than MySQL, modify this function.""" OPTIONS = "merge='yes',host='localhost',user='julie',password='%s',database='newdb'"%"julie" try: store = RDF.Storage(storage_name="mysql", name="julie", options_string=OPTIONS) except: store = RDF.Storage(storage_name="mysql", name="julie", options_string=OPTIONS+",new='true'") return store class Bot : """ RDQL-speaking IRC Bot """ def __init__(self, channels, nick, password) : """Load channels from file, load commands from file, start connecting.""" self._irc = IRCClient() self._irc.nick = nick self._irc.channels = "" self.db_password = password self.flow = FlowControl() try: f = file("channellist.crs") self._irc.channels = pickle.load(f) f.close() except: debug("Could not load pickle") f = open("channellist.crs",'w') self._irc.channels = channels pickle.dump(self._irc.channels, f) f.close() self._irc.startChannels([self._irc.channels]) # reload commands self.load_command_dict() self.bot_clocktime = time.clock() self.bot_uptime = time.time() debug(str(self._irc.channels), str(self._command_dict)) # Bindings. Set up commands here, leavign the catchall at the bottom # just in case I ever make it do anything more interesting. # Try to group commands together where appropriate. BINDINGS = [ (self.spam, PRIVMSG, r"^spam\?"), (self.bye, PRIVMSG, r"^bye bye bot"), (self.reload_commands, PRIVMSG, r"^\^reload$"), (self.delete_node, PRIVMSG, r"^\^deleteNode (.*?)$"), (self.forget_query, PRIVMSG, r"^\^forgetcommand (.*?)$"), (self.forget_query, PRIVMSG, r"^\^command forget (.*?)$"), (self.forget_query, PRIVMSG, r"^\^deletecommand (.*?)$"), (self.forget_query, PRIVMSG, r"^\^command delete (.*?)$"), (self.forget_query, PRIVMSG, r"^\^forget (.*?)$"), (self.forget_query, PRIVMSG, r"^\^delete (.*?)$"), (self.new_query, PRIVMSG, r"^\^newcommand (.*?) is (.*)$"), (self.new_query, PRIVMSG, r"^\^addcommand (.*?) is (.*)$"), (self.new_query, PRIVMSG, r"^\^command add (.*?) is (.*)$"), (self.construct, PRIVMSG, r"^\^construct (.*)$"), (self.query, PRIVMSG, r"^\^query (.*)$"), (self.sparqlquery, PRIVMSG, r"^\^sparqlquery (.*)$"), (self.prenamespaced_query, PRIVMSG, r"^\^q (.*)$"), (self.put, PRIVMSG, r"^\^add (.*)$"), (self.putturtle, PRIVMSG, r"^\^addturtle (.*)$"), (self.command_list, PRIVMSG, r"^\^commandlist\s*(.*)"), (self.command_list, PRIVMSG, r"^\^commandinfo\s*(.*)"), (self.command_list, PRIVMSG, r"^\^define\s+(.*)"), (self.run_command, PRIVMSG, r"^\^runcommand (.*?) (.*)"), (self.reload, PRIVMSG, r"^\^reload modules"), (self.on_invite, INVITE, r".*"), (self.part_channel, PRIVMSG, r"^\^part"), (self.join_channel, PRIVMSG, r"^\^join (.*)"), (self.ns, PRIVMSG, r"^\^ns (.*)$"), (self.addns, PRIVMSG, r"^\^addns (.*?) (?: is )?(.*)$"), (self.addns, PRIVMSG, r"^\^ns add (.*?) (?: is )(.*)$"), (self.listns, PRIVMSG, r"^\^listns$"), (self.listns, PRIVMSG, r"^\^nslist$"), (self.listns, PRIVMSG, r"^\^ns list$"), (self.status, PRIVMSG, r"^\^status$"), (self.parse, PRIVMSG, r"^\^parse (.*)$"), (self.witw, PRIVMSG, r"^\^witw (.*)$"), (self.witwmap, PRIVMSG, r"^\^witw-map (.*)$"), (self.listeningTo, PRIVMSG, r"^\^listeningTo (.*)$"), (self.addTodoItem, PRIVMSG, r"^\^todoItem (.*)$"), (self.info, PRIVMSG, r"^\^info\s*$"), (self.info, PRIVMSG, r"^%s[:,]\s*help\s*$"%self._irc.nick), (self.help, PRIVMSG, r"^[\^]help\s*(.*)$"), (self.catchall, PRIVMSG, r"^\^(.*)"), ] for method, command, pattern in BINDINGS : self._irc.bind(method, command, pattern) def load_command_dict(self) : """Load command dictionary from the file commandlist.crs.""" try: f = open("commandlist.crs", "r") self._command_dict = pickle.load(f) f.close() except: self._command_dict = {'mbox': """select ?mbox where (?p foaf:mbox ?mbox) (?p foaf:nick "%s") using foaf for """ } def save_command_dict(self): """Saves the current command dictionary to a file.""" f = open("commandlist.crs", "w") pickle.dump(self._command_dict, f) f.close() def connect(self, host, port) : self._irc.makeConn(host, port) def main_loop(self) : asyncore.loop(10000) def reply_to(self, origin, args) : """Built in IRC reply to to determine whether we reply to user or channel.""" return reply_to(self._irc.nick, origin, args) def tell(self, origin, args, msg) : """Built in tell function so we don't have to always use reply_to in later functions.""" self._irc.tell(self.reply_to(origin, args), msg) def spam(self, m, origin, args, text) : """ spam is not good """ self.tell(origin, args, "spam, spam, eggs, and spam\^") def bye(self, m, origin, args, text): """ good bye """ self._irc.todo([QUIT], "bye bye\^") def parse(self,m,origin,args,text): model = RDF.Model(get_storage(self.db_password)) before = len(model) type = extras.parse_anything(model, m.group(1)) after = len(model) self.tell(origin, args, "Model size increased by %s to %s by adding %s data." % ((after-before), after, type)) def put_threaded(self, m, origin, args, text): """ Put/Add is the way to add statements to the Model/Graph. It uses URL lib to fetch the URL, and then dumps it. """ try: url = m.group(1) self.tell(origin, args, "Adding %s to my database..." % url) model = RDF.Model(get_storage(self.db_password)) before = len(model) extras.parse_anything(model, url) self.tell(origin, args, "Added %s statements from %s. Model size is %s." \ % (len(model) - before, url, len(model))) except Exception, exc: self.tell(origin, args, "Adding that URL failed (%s)." % exc) def putturtle(self, m, origin, args, text): try: model = RDF.Model(get_storage(self.db_password)) model_size = len(model) turtle = m.group(1) for key, value in common_namespaces().items() : turtle = "@prefix %s: <%s> .\n%s"%(key,value, turtle) extras.parse_anything(model, turtle) new_model_size = len(model) self.tell(origin, args, "Model size increased by %s to %s via turtle statements." % ((new_model_size-model_size), new_model_size)) except Exception, E: self.tell(origin,args,"Adding Turtle failed (%s)."%E) def delete_node(self,m,origin,args,text): model = RDF.Model(get_storage(self.db_password)) before = len(model) extras.remove_node(model,m.group(1)) after = len(model) messagestring = "Model size decreased by %s to %s by removing %s data." % ((before-after), after, m.group(1)) self.tell(origin, args, messagestring) self._irc.todo([PRIVMSG, "crschmidt"], messagestring) def put(self, m, origin, args, text): """ Put/Add is the way to add statements to the Model/Graph. It uses URL lib to fetch the URL, and then dumps it. """ self.flow.queueModifier(lambda: self.put_threaded( m, origin, args, text)) def queries(self, commandname, commandparams, m, origin, args, text): """Run stored queries.""" res = "" if commandname in self._command_dict : model = RDF.Model(get_storage(self.db_password)) if '%s' in self._command_dict[commandname]: query = self._command_dict[commandname] % commandparams else: query = self._command_dict[commandname] self.flow.queueAccessor(lambda: self.query_thread(query, origin, args,model)) def sparqlquery(self, m, origin, args, text): """ Run queries against current RDF store. Queries are in RDQL. """ model = RDF.Model(get_storage(self.db_password)) query = m.group(1) self.flow.queueAccessor(lambda: self.query_thread(query,origin,args,model, "sparql")) def query(self, m, origin, args, text): """ Run queries against current RDF store. Queries are in RDQL. """ model = RDF.Model(get_storage(self.db_password)) query = m.group(1) self.flow.queueAccessor(lambda: self.query_thread(query,origin,args,model)) def construct(self,m,origin,args,text): model = RDF.Model(get_storage(self.db_password)) query = m.group(1) tempmodel = RDF.Model() try: debug(query) query = """PREFIX rdf: CONSTRUCT %s""" % query for key, value in common_namespaces().items() : query = "PREFIX %s: <%s> \n%s"%(key, value, query) q = RDF.SPARQLQuery(query) res = q.execute(model) if res.is_graph(): i = 0 oldsize = len(model) stream = "" for statement in res.as_stream() : tempmodel.append(statement) if len(tempmodel): for s in tempmodel: model.append(s) i += 1 self.tell(origin, args, "Created %s statements based on CONSTRUCT query: Model size changed by %s." % (i, (len(model) - oldsize))) else : self.tell(origin, args, "Query returned no results") except Exception, exc: self.tell(origin, args, "Bad query (%s)." % exc) def query_thread(self,query,origin,args,model,type="default"): try: if (type == "default"): if " {" in query: type="sparql" else: type="rdql" if (type == "rdql"): if not " for <" in query.lower(): query = "%s using rdf for "%query for key, value in common_namespaces().items() : query = "%s, %s for <%s>"%(query, key, value) debug(query) q = RDF.RDQLQuery(query) elif (type == "sparql"): if not "prefix" in query.lower(): query = "PREFIX rdf: \n %s"%query for key, value in common_namespaces().items() : query = "PREFIX %s: <%s> \n%s"%(key, value, query) debug(query) q = RDF.SPARQLQuery(query) res = q.execute(model) print dir(res) if res.is_bindings() and res: reply_dict = {} replies = [] for statement in res : stt = " ".join([remove_datatype(shorten_uri(val)) for val in statement.values()]) replies.append(stt) #reply_dict[stt] = reply_dict.setdefault(stt,0) + 1 #for stt, occurences in reply_dict.items() : # if occurences > 1 : # stt += " (x%s)" % occurences # replies.append(stt) self.tell(origin, args, ", ".join(replies)) elif res.is_boolean(): self.tell(origin, args, str(res.get_boolean())) elif res.is_graph(): stream = "" i = 0 for statement in res.as_stream() : if (i < 3): stream = "%s, %s" % (statement, stream) i += 1 self.tell(origin, args, "Total of %s statements: Here's the first three. %s" % (i,stream)) else : self.tell(origin, args, "Query returned no results") except Exception, exc: self.tell(origin, args, "Bad query (%s)." % exc) def prenamespaced_query(self, m, origin, args, text): """Uses common namespaces to create a query so that you don't have to worry about bindings.""" model = RDF.Model(get_storage(self.db_password)) query = m.group(1) self.flow.queueAccessor(lambda: self.query_thread(query,origin,args,model)) debug("testing get_thread") def new_query(self, m, origin, args, text): """ Add a new query to the command database. Useful for common queries like mailbox, aim name, etc. """ name = m.group(1) if name[0] == "^" : name = name[1:] self._command_dict[name] = m.group(2) self.save_command_dict() self.tell(origin, args, "New command %s is %s" % (name, self._command_dict[name])) def forget_query(self, m, origin, args, text): """Forget a stored query.""" if m.group(1) in self._command_dict : del self._command_dict[m.group(1)] self.save_command_dict() self.tell(origin, args, "Forgot stored query %s"%(m.group(1))) else: self.tell(origin, args, "No such stored query %s"%(m.group(1))) def reload_commands(self, m, origin, args,text): """Reloads the stored command dictionary. Only useful if you have two different sources modifing the dictionary for some reason.""" try: self.load_command_dict() self.tell(origin, args, "Reloaded stored commands from disk.") except: self.tell(origin, args, "Failure.") def command_list(self, m, origin, args, text): """ List available commands, or, if asked for information on a specific command, return the RDQL query behind it. """ response = "" command_request = m.group(1) if (command_request and command_request[0] == "^"): command_request = command_request[1:] if not command_request: debug("No matching command") for command in self._command_dict.keys(): response = "%s%s, "%(response,command) self.tell(origin, args, "Current commands: %s"%response) else: try: self.tell(origin, args, "Command %s: %s"%(command_request, self._command_dict[command_request])) except: self.tell(origin, args, "No such command is stored.") def run_command(self, m, origin, args, text): """ Runs commands stored via newquery. """ self.queries(m.group(1), m.group(2), m, origin, args, text) def on_invite(self, m, origin, args, text): """What to do when invited to a cahnnel.""" if (0): self._irc.todo([JOIN, text]) def part_channel(self, m, origin, args, text): """Response to a ^part command.""" if (0): self._irc.todo([PART, args[1]]) self._irc.channels = re.sub("%s,?"%args[1], "", self._irc.channels) try: f = open("channellist.crs", 'w') pickle.dump(self._irc.channels, f) f.close() except: debug("could not pickle...") def join_channel(self, m, origin, args, text): """What to do when told to ^join a channel.""" if (0): self._irc.todo([JOIN, m.group(1)]) self._irc.channels = "%s,%s" % (self._irc.channels,m.group(1)) try: f = open("channellist.crs", 'w') pickle.dump(self._irc.channels, f) f.close() except: debug("could not pickle...") def listns(self, m, origin, args,text): """ List namespaces available to the bot from the common_namespaces function, sorted alphabetically """ string = "" keylist = common_namespaces().keys() keylist.sort() for key in keylist : string = "%s, %s"%(string, key) self.tell(origin, args, "Namespace bindings are: %s"%string[2:]) def addns(self, m, origin, args,text): """ Add a namespace to the common_namespaces set """ try: common = common_namespaces() common[m.group(1)] = m.group(2) f = open("namespaces.crs",'w') pickle.dump(common, f) f.close() self.tell(origin, args, "Namespace %s added as %s. Total namespaces: %s"%(m.group(1), m.group(2), len(common))) except Exception, exc: self.tell(origin, args, "Error: %s." % exc) return def ns(self, m, origin, args, text): """ Return the stored URL for a namespace. Useful for creating queries or documents. """ if m.group(1) : if (m.group(1) in ("list", "add")): return try: ns = common_namespaces()[m.group(1)] self.tell(origin, args, "The URL for the %s namespace is %s" % (m.group(1), ns)) except: self.tell(origin, args, "That namespace is not in my database.") def witw(self,m,origin,args,text): user = m.group(1) u = urllib.urlopen("http://norman.walsh.name/2005/02/witw/is/%s" % user) try: doc = minidom.parseString(u.read()) loc = doc.getElementsByTagName("locations")[0].getElementsByTagName("location")[0] date = loc.getAttribute("date") t = time.mktime(time.gmtime()) - time.mktime(time.strptime(date, "%Y-%m-%dT%H:%M:%SZ")) self.tell(origin, args, "%s ago, %s was at long: %s, lat: %s"%(make_time_string(t), user, loc.getAttribute("long"), loc.getAttribute("lat"))) except Exception, E: self.tell(origin,args,"No such user. (%s)"%E) def witwmap(self,m,origin,args,text): user = m.group(1) u = urllib.urlopen("http://norman.walsh.name/2005/02/witw/is/%s" % user) try: doc = minidom.parseString(u.read()) loc = doc.getElementsByTagName("locations")[0].getElementsByTagName("location")[0] date = loc.getAttribute("date") t = time.mktime(time.gmtime()) - time.mktime(time.strptime(date, "%Y-%m-%dT%H:%M:%SZ")) self.tell(origin, args, "%s ago, %s was at lat: %s, long: %s. Map available at: http://www.mapquest.com/maps/map.adp?latlongtype=decimal&latitude=%s&longitude=%s&zoom=8"%(make_time_string(t), user, loc.getAttribute("lat"), loc.getAttribute("long"),loc.getAttribute("lat"), loc.getAttribute("long"))) except Exception, E: self.tell(origin,args,"No such user. (%s)"%E) def listeningTo(self,m,origin,args,text): mm = RDF.NS("http://musicbrainz.org/mm/mm-2.0#") rdf = RDF.NS("http://www.w3.org/1999/02/22-rdf-syntax-ns#") dc = RDF.NS("http://purl.org/dc/elements/1.1/") model = RDF.Model(get_storage(self.db_password)) trm = m.group(1) m = RDF.Model() p = RDF.Parser() p.parse_into_model(m, "http://musicbrainz.org/trmid/%s"%trm) p.parse_into_model(model, "http://musicbrainz.org/trmid/%s"%trm) tl = m.find_statements(RDF.Statement(None, mm.trackList, None)) tracklistBag = tl.current().object t1 = m.find_statements(RDF.Statement(tracklistBag, rdf['_1'], None)) trackId = t1.current().object.uri del p p = RDF.Parser() p.parse_into_model(m, trackId) p.parse_into_model(model, trackId) c = m.find_statements(RDF.Statement(trackId, dc.creator, None)) creator = c.current().object.uri del p p = RDF.Parser() p.parse_into_model(model, creator) tp = RDF.TurtleParser() timestamp = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) turtle = """[a foaf:Person; foaf:nick "%s"; menow:hasStatus [a menow:Status; dc:date "%s"; menow:listeningTo <%s>]].""" % (split_origin(origin)[0], timestamp, trackId) turtlestr = turtle for key, value in common_namespaces().items() : turtlestr = "@prefix %s: <%s> .\n%s" % (key, value, turtlestr) tp.parse_string_into_model(model, turtlestr, RDF.Uri("http://crschmidt.net/julie/data/")) self.query_thread("""select ?t, ?n, ?d where (?p foaf:nick "%s") (?p menow:hasStatus ?s) (?s dc:date ?d) (?s menow:listeningTo ?o) (?o dc:title ?t) (?o dc:creator ?a) (?a dc:title ?n) AND ?d =~ /%s/""" % (split_origin(origin)[0], timestamp) ,origin,args,model) def addTodoItem(self, m, origin, args, text): model = RDF.Model(get_storage(self.db_password)) tp = RDF.TurtleParser() turtlestr = """[a todo:Item; todo:owner [a foaf:Person; foaf:nick "%s"]; dc:date "%s"; todo:text "%s"].""" % (split_origin(origin)[0], time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), m.group(1)) for key, value in common_namespaces().items() : turtlestr = "@prefix %s: <%s> .\n%s" % (key, value, turtlestr) tp.parse_string_into_model(model, turtlestr, RDF.Uri("http://crschmidt.net/julie/data/")) self.query_thread("""select ?owner, ?text, ?date WHERE (?a rdf:type todo:Item) (?a todo:owner ?p) (?p foaf:nick "%s") (?p foaf:nick ?owner) (?a todo:text ?text) (?a dc:date ?date)""" % (split_origin(origin)[0]), origin,args,model) def status(self, m, origin, args, text): """Returns bot status.""" model = RDF.Model(get_storage(self.db_password)) common = common_namespaces() size = len(model) namespaces = len(common) commandlist_size = len(self._command_dict) (out, err) = popen2.popen2('uptime'); uptime = out.read() (out, err) = popen2.popen2('hostname -f'); hostname = out.read() bot_uptime = (time.time() - self.bot_uptime) / 3600 bot_clocktime = (time.clock() - self.bot_clocktime) /60 self.tell(origin, args, "I currently hold %s triples, %s namespaces, and %s stored commands. I have been running for %.3f hours, and have used %.3f minutes of CPU time. Stats on current machine (%s): %s"%(size, namespaces, commandlist_size, bot_uptime, bot_clocktime, hostname, uptime)) # Informational function - an extension of the help function for moree general, non-functional information def info(self, m, origin, args, text): """General information function.""" self.tell(origin, args, "I'm a Redland/Python based RDF query bot. Source in SVN at . Commands are ^add , which adds statements, ^newcommand is , ^runcommand . ^commandlist lists current commands, ^commandlist shows info on a command, ^part will have me part a channel, ^join <#chan> will have me join a channel. Talk to crschmidt for more.") def reload(self,m,origin,args,text): reload(extras) self.tell(origin,args, "Reloaded extras.") # Help function: Define help for functions here. def help(self, m, origin, args, text): """Help function for in-channel help with commands.""" try: if m.group(1).startswith("^") : helpword = m.group(1)[1:] else: helpword = m.group(1) except: helpword = "" if helpword == "add" : self.tell(origin, args, "^add : This command adds all the triples available at a URL to the RDF database. It uses Accept headers to indicate that it prefers application/rdf+xml (with */* at the end for failsafe), and does its best to report errors back if an exception is called (such as a 404). In the future, this command will support GZIP encoding.") elif helpword in ("addns", "nsadd") : self.tell(origin, args, "^%s is : This adds a namespace to the list of stored namespaces. URI should be a bare URI (not surrounded by <>). The 'is' is optional."%helpword) elif helpword == "ns" : self.tell(origin, args, "^ns : Gives the URI of the given prefix. In addition, ^ns has several other subcommands: ^ns add is is the same as ^addns, ^ns list is the same as ^nslist, listing all the available prefixes.") elif helpword in ("newcommand", "addcommand") : self.tell(origin, args, "^%s is : This adds a query to the stored commands in Julie's database. RDQL Queries are defined by the RDQL grammar submission, at http://www.w3.org/Submission/2004/SUBM-RDQL-20040109/ . Specifically, the installed version of Rasqal is used, which is .9.3 on the base redlandbot install."%helpword) elif helpword in ("forgetcommand", "deletecommand", "forget", "delete") : self.tell(origin, args, "^%s : Forget a saved query. Good for removing a query which has proved to return inaccurate results or to not function as expected."%helpword) elif helpword in ("commandlist", "commandinfo", "define") : self.tell(origin, args, "^%s : Display a list of saved commands, which can be used with ^. %s : shows the stored RDQL query which corresponds to the command in question."%(helpword,helpword)) else : self.tell(origin, args, "Commands are ^add , which adds statements, ^newcommand is , ^runcommand . ^commandlist lists current commands, ^commandlist shows info on a command, ^part will have me part a channel, ^join <#chan> will have me join a channel.") # A catchall, designed to catch anything starting with ^ def catchall(self, m, origin, args, text): try: commandname, commandparam = m.group(0)[1:].split(' ', 1) debug(commandname) self.queries(commandname, commandparam, m, origin, args, text) except: pass def run(host, port, chan, nick, password): bot = Bot(chan, nick, password) bot.connect(host, port) bot.main_loop() if __name__ == '__main__': opts, args = getopt.getopt(sys.argv[1:], "s:n:p:", ["help", "server=", "nick=", "pass="]) server = "irc.freenode.net"; nick = "julie"; password = "default" for o, a in opts: if o in ("-s", "--server"): server = a if o in ("-n", "--nick"): nick = a if o in ("-p", "--pass"): password = a run(server, Port, "#julie", nick, password)