#!/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 <chauvat@nerim.net> 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 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='julie'"%"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.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 <http://xmlns.com/foaf/0.1/>""" }

    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 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 <http://www.w3.org/1999/02/22-rdf-syntax-ns#>"%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: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\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)
            if res:
                reply_dict = {}
                for statement in res :
                    stt = " ".join([shorten_uri(val) for val in statement.values()])
                    reply_dict[stt] = reply_dict.setdefault(stt,0) + 1
                replies = []
                for stt, occurences in reply_dict.items() :
                    if occurences > 1 :
                        stt += " (x%s)" % occurences
                    replies.append(stt)
                self.tell(origin, args, ", ".join(replies))
            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 <http://crschmidt.net/svn/rdfpython/trunk/>. Commands are ^add <url>, which adds statements, ^newcommand <name> is <RDQL Query>, ^runcommand <commandname> <arguments> . ^commandlist lists current commands, ^commandlist <command> 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 <url> : 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 <prefix> is <uri> : 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 <prefix> : Gives the URI of the given prefix. In addition, ^ns has several other subcommands: ^ns add <prefix> is <uri> 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 <commandname> is <RDQL Query> : 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 <commandname> : 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 ^<commandname>.  %s <commandname> : shows the stored RDQL query which corresponds to the command in question."%(helpword,helpword))
        else :
            self.tell(origin, args, "Commands are ^add <url>, which adds statements, ^newcommand <name> is <RDQL Query>, ^runcommand <commandname> <arguments> . ^commandlist lists current commands, ^commandlist <command> 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)
