The New Bloglines Web Services
Pages: 1, 2
A Complete Bloglines API Application
Because Bloglines provides so much infrastructure behind the scenes, it's easy to make an application that offers full RSS/Atom functionality with very little work. A Bloglines API application needs only to get the subscription list, wait for the user to select a subscription, get a list of items for that subscription, and display the item contents for items the user selects. Using the Java-based scripting language Groovy, we can build a standard three-pane desktop RSS/Atom aggregator in just about 150 lines of code--not much at all!
If you're interested in learning more about Groovy, check out Get ${Stuff} Done with Groovy. To run this application with Groovy, follow the Groovy installation instructions, and then run the Bloglines client with the command:
groovy BloglinesClient.groovy
The application will ask you for your Bloglines email address and password, then will show the full interface.
Below is the Groovy code for our RSS/Atom aggregator. As you'll see, the tasks we have to accomplish are mostly to get the XML information from Bloglines and to organize that information into an interface the user can comfortably use. All of the heavy lifting of normalizing data and keeping state for the user is already done for us by the Bloglines APIs. (Download the code here.)
/*
* BloglinesClient.groovy - an example of the Bloglines Web Services
*
* Written by Marc Hedlund <marc@precipice.org>, September 2004.
* Requires Groovy 1.0-beta-6; see <http://groovy.codehaus.org/>.
*
* This work is licensed under the Creative Commons Attribution
* License. To view a copy of this license, visit
* <http://creativecommons.org/licenses/by/2.0/> or send a letter to
* Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
*/
import groovy.swing.SwingBuilder;
import java.awt.BorderLayout;
import java.net.URL;
import javax.swing.BorderFactory;
import javax.swing.JOptionPane;
import javax.swing.JSplitPane;
import javax.swing.JTree;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeSelectionModel;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.methods.GetMethod;
// Set up global variables and data types
server = 'rpc.bloglines.com';
apiUrl = { method | "http://${server}/${method}" };
class Feed { name; id; unread; String toString() {
return (unread == "0" ? name : "${name} (${unread})");
}
}
class Item { title; contents; String toString() { return title; } }
// Ask the user for account information (using simple dialogs)
email =
JOptionPane.showInputDialog(null, "Email address:", "Log in to Bloglines",
JOptionPane.QUESTION_MESSAGE);
password =
JOptionPane.showInputDialog(null, "Password:", "Log in to Bloglines",
JOptionPane.QUESTION_MESSAGE);
// Use HTTPClient for web requests since the server requires authentication
client = new HttpClient();
credentials = new UsernamePasswordCredentials(email, password);
client.getState().setCredentials("Bloglines RPC", server, credentials);
// Get the list of subscriptions and parse it into a GPath structure
opml = new XmlParser().parseText(callBloglines(apiUrl('listsubs')));
def callBloglines(url) {
try {
get = new GetMethod(url);
get.setDoAuthentication(true);
client.executeMethod(get);
return get.getResponseBodyAsString();
} catch (Exception e) {
println "Error retrieving <${url}>: ${e}";
return "";
}
}
// Descend into the subscription outline, adding to the feed tree as we go
treeTop = new DefaultMutableTreeNode("My Feeds");
parseOutline(opml.body.outline.outline, treeTop);
def parseOutline(parsedXml, treeLevel) {
parsedXml.each() { outline |
if (outline['@xmlUrl'] != null) { // this is an individual feed
feed = new Feed(name:outline['@title'], id:outline['@BloglinesSubId'],
unread:outline['@BloglinesUnread']);
treeLevel.add(new DefaultMutableTreeNode(feed));
} else { // this is a folder of feeds
folder = new DefaultMutableTreeNode(outline['@title']);
parseOutline(outline.outline, folder);
treeLevel.add(folder);
}
}
}
// Build the base user interface objects and configure them
swing = new SwingBuilder();
feedTree = new JTree(treeTop);
itemList = swing.list();
itemText = swing.textPane(contentType:'text/html', editable:false);
model = feedTree.getSelectionModel();
model.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
itemList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
// Set up the action closures that will react to user selections
listItems = { feed |
rssText = callBloglines(apiUrl('getitems') + "?s=${feed.id}&n=0");
if (rssText != null) {
try {
rss = new XmlParser().parseText(rssText);
items = new Vector();
rss.channel.item.each() {
item = new Item(title:it.title[0].text(),
contents:it.description[0].text());
items.add(item);
}
itemList.setListData(items);
feed.unread = "0"; // update the unread item count in the feed list
} catch (Exception e) {
println "Error during <${feed.name}> RSS parse: ${e}";
}
}
}
feedTree.valueChanged = { event |
itemText.setText(""); // clear any old item text
node = (DefaultMutableTreeNode) feedTree.getLastSelectedPathComponent();
if (node != null) {
feed = node.getUserObject();
if (feed instanceof Feed && feed.unread != "0") {
listItems(feed);
}
}
}
itemList.valueChanged = { event |
item = event.getSource().getSelectedValue();
if (item != null && item instanceof Item) {
itemText.setText("<html><body>${item.contents}</body></html>");
}
}
// Put the user interface together and display it
gui =
swing.frame(title:'Bloglines Client', location:[100,100], size:[800,600],
defaultCloseOperation:WindowConstants.EXIT_ON_CLOSE) {
panel(layout:new BorderLayout()) {
splitPane(orientation:JSplitPane.HORIZONTAL_SPLIT, dividerLocation:200) {
scrollPane() {
widget(feedTree);
}
splitPane(orientation:JSplitPane.VERTICAL_SPLIT, dividerLocation:150) {
scrollPane(constraints:BorderLayout.CENTER) {
widget(itemList);
}
scrollPane(constraints:BorderLayout.CENTER) {
widget(itemText);
}
}
}
}
}
gui.show();
That's all there is to it. With these few lines of code, we already have a desktop aggregator comparable to many of the full applications that otherwise would take much longer to develop. Here is a screenshot of the finished aggregator in action:

Ideas for Other Bloglines API Applications
Obviously there are some benefits to implementing desktop aggregators with the Bloglines Web Services APIs (foremost among them, as discussed above, are the bandwidth savings and the server-maintained state), but the most exciting part of the Bloglines Web Services is the opportunity for new RSS/Atom applications to emerge:
- The Bloglines APIs would be a great basis for an offline RSS reader. If you're getting on a plane and will be offline for a while, just sync up your unread feeds, and use a desktop system to read them on the road.
- Make a better notifier. Right now the Bloglines notifier simply alerts you to how many new articles you have to read. With the new APIs, you could present a list of new article titles, show which feeds have updates, and more.
- Read your feeds in email ... or nntp ... or on your pager ... or wherever. The APIs would make writing an RSS-to-anything gateway trivial. You could probably even spit out stock prices on an old ticker tape machine.
- Merge "public" and internal feeds in one interface. You could use the Bloglines APIs to collect information from public RSS/Atom feeds, and then merge that information with behind-the-firewall, private information within your company or organization.
These are just a few ideas to get you started. Hopefully, the Bloglines Web Services APIs will be a great platform for developers, and a boon to RSS/Atom publishers and readers around the Internet.
Marc Hedlund is an entrepreneur working on a personal finance startup, Wesabe.
Return to the O'Reilly Network
-
Fix errors when running in new groovy ( unexpected token)
2005-09-07 21:45:39 nsathishk [View]
- Trackback from http://oriol.joor.net/blog-dev/?itemid=1489&catid=13
Groovy: simplificant Java
2004-11-16 11:48:25 [View]
- Trackback from http://jroller.com/page/sftarch/20041103#poor_mans_webstart_using_groovy
Poor mans webstart using Groovy
2004-11-03 12:06:22 [View]
- Trackback from http://www.adplusplus.net/adplusplus/2004/10/bloglines_web_s.html
Bloglines Web Services
2004-10-07 14:58:16 [View]
- Trackback from http://www.jacobgrier.com/blog/archives/000236.html
I need help
2004-10-05 14:12:05 [View]
- Trackback from http://gort.ucsd.edu/mtdocs/archives/laz/001927.html
Lots of Great News for Blog Readers
2004-10-04 12:11:26 [View]
- Trackback from http://www.chaosmagnet.com/blog/archives/000482.html
Bloglines Web Services
2004-10-04 11:54:20 [View]
- Trackback from http://www.koldark.net/archives/2004/10/04/the_new_bloglines_web_services.php
The New Bloglines Web Services
2004-10-04 09:04:49 [View]
- Trackback from http://jaeger.blogmatrix.com/weblog/archives/2004_10.shtml#002985
More on the Bloglines API
2004-10-02 11:39:29 [View]
- Trackback from http://www.ofoghlu.net/log/archives/000463.html
Bloglines API may provide scalability for RSS
2004-09-29 12:03:50 [View]
- Trackback from http://pubcrawler.org/archives/000516.html
Bloglines Goodies
2004-09-29 10:08:20 [View]
- Trackback from http://www.robot0.com/archives/000043.html
links for 2004-09-29
2004-09-29 05:15:45 [View]
- Trackback from http://groovymother.com/links/archives/week_2004_09_26.html#001437
O'Reilly Network: The New Bloglines Web Services
2004-09-28 13:03:04 [View]
-
A minor point...
2004-09-28 12:48:44 jgroth [View]
-
A minor point...
2004-09-28 13:52:53 Marc Hedlund |
[View]
- Trackback from http://upian.net/znarf/blog/?2004/09/28/102-subscriptions-synchronisation
subscriptions synchronisation
2004-09-28 11:43:21 [View]
- Trackback from http://blog.bulknews.net/mt/archives/001294.html
Bloglines API on O'Reilly Network
2004-09-28 10:23:04 [View]



