Ok I don’t know about you but whenever I have the chance to create what anybody labels a “robot” I totally flip out. So Google has “robots” that you can include in your wave conversations and I thought to myself “Finally a chance to create a robot this will be badass!”. I got as far as back propagation neural networks before I decided that, not only do I not know what I wanted my robot to do, but whatever it did I certainly wouldn’t need some sort of half assed AI to do it.
So I gave up with the robot thing and focused my newly found bpnn info on my job. Soon I realized two things:
- We don’t even track the information I needed to properly train my network
- It’s suddenly Friday and I still have to finish throwing more functionality into our spaghetti logic mess of a program.
So I again gave up and went camping. Camping always brings several levels of victories. I was victorious because I slept outside in the freezing rain. I suppose you could also call that a defeat since I only chose to sleep outside because the cabin was filled with wasps.
That is neither here nor there. On with googlebots!
Googlebots have to be programmed in Python because Google only distributes the API in Java and Python and I hate Python just less then I hate Java. If you look at the two APIs, you will probably choose Java because it(on first appearance anyway) appears to far outshine the Python version. Anyways, I want a simple bot to help automate developer conversations. Specifically, to replace talk about a bug with links to that actual bug. So here:
- Monitor a wave and try to tell if the people are talking about a bug
- Lookup that bug on the bugzilla page
- Modify the bug text to link to the desired bug
Not very aggressive, but then again I’ve never written a Googlebot and I don’t know Python. So what more do you want. [Edit: as I went through this experience, it turned out to be more an exercise in learning Python then learning to design a google robot. I feel that the API is pretty simple and building a basic robot is not very complex]
To start we must setup our initial robot and monitor a wave. This requires nothing more then the cookie cutter code Google provides, and listening to the BlipSubmitted event.
| Python | | copy code | | ? |
| 01 | from waveapi import events |
| 02 | from waveapi import model |
| 03 | from waveapi import robot |
| 04 | |
| 05 | import httplib |
| 06 | import re |
| 07 | import logging |
| 08 | |
| 09 | def OnBlipSubmitted(properties, context): |
| 10 | """Invoked when a blip is entered(the 'done' button is clicked) |
| 11 | |
| 12 | def OnRobotAdded(properties, context): |
| 13 | """Invoked when the robot has been added.""" |
| 14 | |
| 15 | if __name__ == '__main__': |
| 16 | myRobot = robot.Robot('ryansrobot', |
| 17 | image_url='http://ryansrobot.appspot.com/icon.png', |
| 18 | version='3', |
| 19 | profile_url='http://ryansrobot.appspot.com/') |
| 20 | myRobot.RegisterHandler(events.BLIP_SUBMITTED, OnBlipSubmitted) |
| 21 | myRobot.RegisterHandler(events.WAVELET_SELF_ADDED, OnRobotAdded) |
| 22 | myRobot.Run() |
Thats just about all there is to the wave code. When your robot is added to a Wave, OnRobotAdded will be run. Anytime a blip is submitted, OnBlipSubmitted is run. Lets examine the OnRobotAdded method more indepth.
| Python | | copy code | | ? |
| 1 | def OnRobotAdded(properties, context): |
| 2 | """Invoked when the robot has been added.""" |
| 3 | root_wavelet = context.GetRootWavelet() |
| 4 | root_wavelet.CreateBlip().GetDocument().SetText("Robot checking in") |
| 5 | logging.info('robotAdded') |
| 6 |
This is pretty straightforward. You create a new blip in the wavelet your robot has been added to. The new blip is appended to the end of the wavelet. You set the text of your new blip’s document to say ‘Robot checking in’. You can do neat things also, like create a new wavelet but not invite anyone to that wavelet. Only your robot can access the wavelet now and can use it to store/retrieve information. We don’t need to do that though, so we just deal with the current wavelet.
So now what we do is monitor new blips and look for things that appear to be bugs. As I mentioned before, I’m not bothering to do any half-assed AI in order to learn the Wave API, so I don’t do much to determine what is bug talk. I feel that is beyond the scope of this document.
| Python | | copy code | | ? |
| 01 | def OnBlipSubmitted(properties, context): |
| 02 | logging.info('blipSubmitted') |
| 03 | blip = context.GetBlipById(properties['blipId']) |
| 04 | contents = blip.GetDocument().GetText() |
| 05 | pattern = re.compile('bug (\d+)', re.M | re.I) |
| 06 | m = pattern.search(contents) |
| 07 | if m: |
| 08 | msg = getDescription(m.group(1)) |
| 09 | newmsg = re.sub(m.group(0), "<a href=\""+msg+"\">"+m.group(0)+"</a>", contents) |
| 10 | waveId = blip.GetWaveId() |
| 11 | waveletId = blip.GetWaveletId() |
| 12 | if msg != "Not Found": |
| 13 | blip.GetDocument().SetText(" ") |
| 14 | context.builder.DocumentAppendMarkup(waveId, waveletId, blip.GetId(), newmsg) |
| 15 | |
| 16 | def getDescription(term): |
| 17 | logging.info(term); |
| 18 | host = "bugzilla.mozilla.org" |
| 19 | h = httplib.HTTPS(host) |
| 20 | h.putrequest('GET', '/buglist.cgi?quicksearch='+term) |
| 21 | h.putheader('Host', host) |
| 22 | h.putheader('User-agent', 'Ryans Robot') |
| 23 | h.endheaders() |
| 24 | returncode, returnmsg, headers = h.getreply() |
| 25 | if returncode == 302: |
| 26 | return headers.getheader('Location') |
| 27 | else: |
| 28 | return 'Not Found' |
| 29 |
Non wave: Look through the blip’s document for text matching r’bug (\d+)’. Search for that \d+ on your bugzilla site(in this case Mozilla) and return a link.
Wave: Now change the blip’s document to be the original text, but with with the anchor tag attached to the bug text. This is a little more complicated with the Python API then with the Java API. Java has a .appendMarkup() method, which we don’t have in Python. What we do is use a direct builder method to create the JSON that gets sent back to the Wave server. This lets us create bold tags and anchor tags, and as long as you have well formed HTML you can put whatever in the blip’s document.
First we erase whatever text was in the document, then we replace the document with the original text, including the anchor tag. This gives us a nice link the the actual bug.
The above code that adds html tags to the blip is really the last wavy part of the robot. The rest is python parsing and other things, which I’m not good at and am in no position to explain. The code works on Mozilla’s bugzilla page, so I’ll paste that here.
Wave can be used on your local network so my goal here is to allow my company to discuss bugs on our Wave server, and provide live links to those bugs on our bugzilla site. Maybe somebody reading this will be able to help me out with the Python, which I’m sure is attrocious.
| Python | | copy code | | ? |
| 01 | # http://www.ryanday.net/ |
| 02 | # search bugzilla by bug ID, replace blip text with link |
| 03 | |
| 04 | from waveapi import events |
| 05 | from waveapi import model |
| 06 | from waveapi import robot |
| 07 | |
| 08 | import httplib |
| 09 | import re |
| 10 | import logging |
| 11 | logging.info('base') |
| 12 | |
| 13 | import BaseHTTPServer, urlparse, sys |
| 14 | |
| 15 | def OnParticipantsChanged(properties, context): |
| 16 | """Invoked when any participants have been added/removed.""" |
| 17 | logging.info('participantsChanged') |
| 18 | added = properties['participantsAdded'] |
| 19 | for p in added: |
| 20 | Notify(context) |
| 21 | |
| 22 | def OnBlipSubmitted(properties, context): |
| 23 | logging.info('blipSubmitted') |
| 24 | blip = context.GetBlipById(properties['blipId']) |
| 25 | contents = blip.GetDocument().GetText() |
| 26 | pattern = re.compile('bug (\d+)\n?', re.M | re.I) |
| 27 | m = pattern.search(contents) |
| 28 | if m: |
| 29 | msg = getDescription(m.group(1)) |
| 30 | newmsg = re.sub(m.group(0), "<a href=\""+msg+"\">"+m.group(0)+"</a>", contents) |
| 31 | waveId = blip.GetWaveId() |
| 32 | waveletId = blip.GetWaveletId() |
| 33 | if msg != "Not Found": |
| 34 | blip.GetDocument().SetText(" ") |
| 35 | context.builder.DocumentAppendMarkup(waveId, waveletId, blip.GetId(), newmsg) |
| 36 | wavelet = context.GetRootWavelet() |
| 37 | |
| 38 | def getDescription(term): |
| 39 | host = "bugzilla.mozilla.org" |
| 40 | h = httplib.HTTPS(host) |
| 41 | h.putrequest('GET', '/show_bug.cgi?id='+term) |
| 42 | h.putheader('Host', host) |
| 43 | h.putheader('User-agent', 'Ryans Robot') |
| 44 | h.endheaders() |
| 45 | returncode, returnmsg, headers = h.getreply() |
| 46 | if returncode == 200: |
| 47 | body = h.getfile().read() |
| 48 | regex = '<title>Bug ' + term + ' ' |
| 49 | pattern = re.compile(regex, re.M) |
| 50 | m = pattern.search(body) |
| 51 | if m: |
| 52 | return 'https://bugzilla.mozilla.org/show_bug.cgi?id='+term |
| 53 | else: |
| 54 | return 'Not Found' |
| 55 | |
| 56 | def OnRobotAdded(properties, context): |
| 57 | """Invoked when the robot has been added.""" |
| 58 | root_wavelet = context.GetRootWavelet() |
| 59 | root_wavelet.CreateBlip().GetDocument().SetText("Robot checking in") |
| 60 | logging.info('robotAdded') |
| 61 | |
| 62 | def Notify(context): |
| 63 | root_wavelet = context.GetRootWavelet() |
| 64 | root_wavelet.CreateBlip().GetDocument().SetText("<dr. nick>Hi everybody!</dr. nick>") |
| 65 | |
| 66 | if __name__ == '__main__': |
| 67 | logging.info('main') |
| 68 | myRobot = robot.Robot('ryansrobot', |
| 69 | image_url='http://ryansrobot.appspot.com/icon.png', |
| 70 | version='3', |
| 71 | profile_url='http://ryansrobot.appspot.com/') |
| 72 | myRobot.RegisterHandler(events.BLIP_SUBMITTED, OnBlipSubmitted) |
| 73 | myRobot.RegisterHandler(events.WAVELET_SELF_ADDED, OnRobotAdded) |
| 74 | myRobot.Run() |
| 75 |
