Use the Google Reader API to purge old feeds

I've collected almost 3000 feeds in Google Reader. Find out how I used the not-public Google Reader API to purge old feeds.

"At the height of the sub-prime mortgage crisis, Wall Street had relaxed lending standards so much that
 in Ohio, 23 dead people actually got mortgages"
-- NPR

I use Google Reader frequently, I have it set up as a gadget section in my Google "/ig" custom home page and it helps me keep track of the many feeds I follow. One of the nicest features is that I can use it to  synch my feeds from home or notebook machines to my office machine. I just export my OPML of the home feeds, suck them up into Reader, then export them out for the office machine when I arrive there.

But more and more, I find myself just using Google's web-based Reader application. It offers search in feeds, which Internet Explorer doesn't have, and they recently added a "notes" facility that looks very promising (Note the 20 character "userId" at the end of the url. That's your unique id and it doesn't change). You can also share feeds with "Friends" whatever that means. I've already got so many online social "Friends" in various social applications, I think I've started to forget the real life ones... One of the biggest problems I have with Google Reader is the fact that I often import the OPML that other people publish (of their favorite feeds) so I can improve my feeds list. For example, somebody recently published OPML of around 75 Silverlight feeds. Well, I sucked that one right in! Now the problem is that I've accumulated close to 3,000 feeds and as you might guess, Reader has really started to slow down. So, what to do? If only I had a way to unsubscribe from all my feeds that say, hadn't had a new post in X number of months...

Well, KTHXBYE, I CAN HAS API! It's the Google Reader API to the rescue! There is a class library up on codeplex.com that apparently was abandoned by the author. In fact, as is, it won't even work. When you call the constructor, it attempts to "connect" without even having your username and password populated! I fixed that easily enough by changing the ctor to require your Google username and password.  The other problem is, he neglected to put in an unsubscribe method -- that's the one that I really needed. So, I figured that out and put one in. MVP buddy Daniel Cazzulino was helpful in suggesting some debugging tools, especially Firebug, to figure this out.  (Incidentally, I had a chance to chat with Daniel at the recent MVP Summit and he's involved with a unique organization that's using technology to mobilize disaster aid assets globally- you may want to check it out.)

So with my completed and tested Google Reader API class ready, I built a simple Windows Forms front end to allow me to unsubscribe any feeds whose latest post is over, say, 5 months old.  You put your username (e.g., "user@google.com") and your password in the appSettings elements in the config file, along with your optional userid (that's a 20 digit long string you can find by doing a View Source on any feed viewed at Reader in your browser). You also specify the number of months back to purge feeds. If you are concerned about trying this, I suggest simply exporting all your feeds as OPML first. Then, if the results aren't to your liking, you can always import all your feeds back in.

The application connects to Reader, gets all your feed folders, then iterates over every feed you are subscribed to and gets the latest items. It looks at the pubDate of the most recent item, and if it is more than "purgeMonths" old, it unsubscribes you from the feed using my new "Unsubscribe" method. This is all done on a ThreadPool to speed up operations. Of course, the app reports progress while it is working, and displays "ALL DONE" when all your feeds have been processed.

Here's the code for my Forms app so you can see how this is done:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using natol.GoogleReaderAPI;
using System.Configuration;
using System.Threading;
using System.Reflection;

namespace Test
{
    public partial class Form1 : Form
    {
        natol.GoogleReaderAPI.GoogleReader reader = null;
        int ctr = 0;
        int ctrDeleted = 0;
        int processedCtr = 0;
        List<string> list = null;
        int purgeMonths = int.Parse(ConfigurationManager.AppSettings["purgeMonths"]);

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)

        {
        }

        private void button1_Click(object sender, EventArgs e)
        {
           string userName = ConfigurationManager.AppSettings["userName"];
           string password =  ConfigurationManager.AppSettings["password"];
           reader = new GoogleReader(userName, password);  
           reader.UserID =  ConfigurationManager.AppSettings["userId"];
           this.button1.Enabled = false;
        
            GRootFolder rootFolder=    reader.GetFeedList();
     
         foreach (GFeed fd in rootFolder.Feeds)
         {
             ctr++;
             SetControlProperty(label1, "Text", ctr.ToString());  
             DoWork(fd.ID);        
         }           
       
         foreach(GFolder fldr in rootFolder.Folders )
            {
                foreach (GFeed fd in fldr.Feeds)
                {
                    ctr++;
                    SetControlProperty(label1, "Text", ctr.ToString());
                    DoWork(fd.ID);   
                 }
           }
                       
        }
   
        delegate void SetValueDelegate(Object obj, Object val, Object[] index);

    public void SetControlProperty(Control ctrl, String propName, Object val)
        {
            PropertyInfo propInfo = ctrl.GetType().GetProperty(propName);
            Delegate dgtSetValue = new SetValueDelegate(propInfo.SetValue);
            ctrl.Invoke(dgtSetValue, new Object[3] { ctrl, val, /*index*/ null });
        }

      private void DoWork(string feedUrl)
       {
           // Do work on ThreadPool thread. We can have our delegate inline
           ThreadPool.QueueUserWorkItem(
               delegate
               {                 
                   GFeed thisFeed = reader.GetFeed(feedUrl);
                   SetControlProperty(label2, "Text", thisFeed.ID);
                   processedCtr++;
                   try
                   {
                       if (thisFeed.Items[0].Published < DateTime.Now.AddMonths(-purgeMonths))
                       {                          
                           string res = reader.Unsubscribe(thisFeed.ID);
                           ctrDeleted++;
                           SetControlProperty(label3, "Text", "deleted: " +ctrDeleted.ToString());
                           SetControlProperty(label1, "Text", "DELETED--" +res +": "+thisFeed.ID);
                       }
                   }
                   catch {  /* bad date or no date, cannot do anything */}
                   if (processedCtr == ctr)
                   {
                       SetControlProperty(label1, "Text", "ALL DONE!");
                       ctr = 0;
                       processedCtr = 0;
                       ctrDeleted = 0;                       

SetControlProperty(button1, "Enabled", true);

} }); } } }
Note that in the code above I'm using a simple method called SetControlProperty that invokes a delegate to handle required Control.Invoke semantics, because we need to update a UI element from a background thread.

If you were so inclined, there are enough of the Reader API methods in this class to build a working feed reader application that uses Google Reader as it's back end. For my purposes, I really only use Google's web application for reading, so all I needed was this one way to "purge" dormant feeds.  You can download the Visual Studio 2008 solution here.  You can find out more about the Reader API here. Finally, I should mention that this implementation assumes you only have one level of "subfolders" below your feeds root folder. So, if you are one of those "hierarchical feed folder nuts", you will need to take care of that recursive call on your own.
By Peter Bromberg   Popularity  (3022 Views)