FogBugz's BugzScout capability actually makes this pretty easy. RSS feeds, in order to work correctly, have to uniquely identify each entry in the feed. That way, feed readers can periodically load the feed and sort out which items are new.
It turns out that if you use the FogBugz API to submit a BugzScout request with the feed item's unique identifier as the "Scout Message" and set to flag to "Stop Reporting", you can send that API call as many times as you want, and only one case will ever be created for that feed item.
This is still a very new process for us, so there are some kinks to be worked out. I'm going to use this as a placeholder to come and post the code, once it's checked in and not embarrassing. It's a pretty simple Python script that runs on top of the BeautifulSoup XML parsing code.
Here's what it does, roughly:
- Loads the Twitter Search RSS feed for a list of words, here they are "FogBugz" and "Fog Creek". (Copilot and Kiln are too general to find in Twitter through this method.)
- Creates a fake correspondent address for the twitter account. This is a personal choice that helps other internal processes we have.
- Submits the BugzScout case with the URL of the feed (and a "salt" value) as the sScoutDescription. This means that even if we keep sending the same request again and again, they'll all be aggregated under one case.
Known Issues:
- The Occurrences field will increment every time the script is run. Just ignore it.
- It's not particularly good at managing Twitter conversations, where there's a string of @ replies in which your product is mentioned once. A future version will reconstruct conversations that included your product in the middle, will group conversations into one case, and will update the case should the conversation progress.
- I always end up clicking on the link to the status update and losing my original window due to the link not opening in a new window. This is a FogBugz design choice that I generally support, but for some reason, it breaks the user model here.
Here's the code:
#!/usr/local/bin/python
import re, string, sys
from fogbugz import FogBugz
import urllib
import urllib2
from BeautifulSoup import BeautifulSoup, CData
class TwitterConnectionError(Exception):
pass
# this is a list of people, including Fog Creek employees,
# whose tweets we will ignore
ignore_list = ('richarmstrong',
'fogbugz',
'spolsky') # some names removed to protect the innocent.
product_list = ('fogbugz',
'%22fog+creek%22') #gross, encoding the parameter literally
fogbugz = FogBugz('http://foo.fogbugz.com', "tokenremovedforsecurity")
twitter_search = urllib2.build_opener()
for product in product_list:
try:
fogbugz_tweets = BeautifulSoup(
twitter_search.open(
'http://search.twitter.com/search.atom?q=%s'% product
)
)
except URLError:
raise TwitterConnectionError("Could not find Twitter.")
for tweet in fogbugz_tweets.findAll('entry'):
# get the twitter profile name so we can build a fake correspondent address.
author = tweet.author.contents[1].string.partition(' ')[0]
if author.lower() in ignore_list:
continue
author = author + "@twitbugz.fake"
resp = fogbugz.new(sTitle='[tweet] ' + tweet.title.string,
sProject="Inbox",
sArea="Support",
sScoutDescription='my_secret_salt-' + tweet.link['href'],
fScoutStopReporting=1,
sEvent=tweet.link['href'],
sCategory="Inquiry",
sCustomerEmail=author)
#print resp.prettify() #uncomment this line to get the case number back
The code relies on a slightly modified version of the library available at the bottom of the page here. My tweaked version allows you to create the FogBugz object with a token, rather than creating it and logging on to get a token.