Creating an index of blog posts to launch Windows Live Writer

This one's mainly for my fellow bloggers – especially those using the Typepad blogging platform – to help address a problem related to editing old blog posts using Windows Live Writer, a tool I can honestly say I can no longer blog without. Incidentally I'm now working with Live Writer 2012.

With that said, it may also be of interest for people who want to programmatically query their favourite blogs and create offline indices into their posts. Maybe. 🙂

I use Live Writer primarily to author blog posts – which it allows me to do offline, something I find incredibly valuable – but I also use it to edit older posts, whether to add an update or to change its assigned categories. It's easy to access recent posts – they're accessible directly within the product – but from time to time I need to modify a post that was written before I'd adopted Live Writer for writing my blog (or one that for some reason doesn't show up in my "recent posts").

Typepad does provide a web-based post editor but this very often messes up the formatting of code that I've posted, which I find extremely frustrating. I therefore avoid it at all costs – I want to do as much as possible in Live Writer.

Live Writer does a reasonable job of pulling down posts from your preferred blogging service, but – at least with Typepad, and admittedly they've been having problems of late – it's very hard to load posts beyond the last 100 or so. If I try to go back to the last 500 or more I get a timeout error:

WLW timeout

And now that I have around more than 1100 posts I find myself regularly having to go back and make edits to posts that are not in the last 100.

I did a bit of research and came across this post that referred to this helpful Windows Live Writer plugin. The application installs a custom URI handler using the "wlw" prefix that can (for instance) be launched via the "Run" command or the address bar in Windows/Internet Explorer:

wlw://[yourblogdomain]/?postid=[postid]

As an example, here's a link that opens the first ever post made to this blog:

wlw://through-the-interface.typepad.com/?postid=6a00d83452464869e200d8352e3b2b53ef

So far so good. The tricky bit is to work out the IDs of the individual posts. The good news is that Typepad provides a RESTful API to get at this information (more work will be needed to support blogs hosted by other providers).

I threw together a simple C#/WinForms app that queries the specified blog and populates a grid for copying/pasting into Excel. I decided to fill this grid with the number and title of the post, as well as links to the published post and to load the editable post into Live Writer. So it may be of some use to avid readers as well as my fellow bloggers. We'll see.

One thing the Typepad API calls need, in particular, is the blog's ID. I found this for my blog by loading the web-based Typepad dashboard and extracting it from the URL:

Typepad's blog dashboard

The code is fairly straightforward: it downloads batches of posts (you can specify the number of posts per batch, as well as configuring which one to start from). Typepad posts are retrieved in reverse chronological order via the API, it seems, so the code assigns a number from the total number of posts down to 1 (the first ever post).

Post list from Through the Interface

The intention is that you'll copy the contents of the list of posts – and there's a button to simplify that part of things – and paste it into Excel (and presumably save it as an spreadsheet file of some sort). When you paste the contents in (as plain text – the formatting is ugly), you'll find the links in column D and F are indeed links (to the published post and to load Live Writer, respectively), even if they aren't blue and underlined.

Our post list in Excel

You can use this spreadsheet to find your preferred post and then either load the published post via the link in the D column or into Live Writer (assuming you have authoring permission on my blog, of course ;-). I've hidden columns C and E, by the way, as these are the "plain text" versions of columns D and F.

Yes, those are actually links

The links for Live Writer do indeed launch the product…

Which will load the post into Windows Live Writer

… which pulls down the post's contents and loads them into the editor:

And here's WLW with our chosen post

Here's the project along with the C# code that does most of the heavy lifting:

using System;

using System.IO;

using System.Linq;

using System.Net;

using System.Threading.Tasks;

using System.Web;

using System.Windows.Forms;

using Newtonsoft.Json.Linq;

 

namespace PostRetriever

{

  public partial class PostRetrievalForm : Form

  {

    public PostRetrievalForm()

    {

      InitializeComponent();

    }

 

    private void EnableControls(bool enabled)

    {

      getButton.Enabled = enabled;

      idLabel.Enabled = enabled;

      blogIdBox.Enabled = enabled;

      domainLabel.Enabled = enabled;

      domainBox.Enabled = enabled;

      startLabel.Enabled = enabled;

      startBox.Enabled = enabled;

      incLabel.Enabled = enabled;

      incBox.Enabled = enabled;

      copyButton.Enabled = enabled ? postGrid.RowCount > 0 : false;

    }

 

    private async void getButton_Click(object sender, EventArgs e)

    {

      if (postGrid.RowCount > 0)

      {

        var res = MessageBox.Show(

          Control.FromHandle(this.Handle),

          "This will clear the existing post list.",

          "Post List Not Empty",

          MessageBoxButtons.OKCancel

        );

        if (res == DialogResult.Cancel)

          return;

 

        postGrid.Rows.Clear();

      }

      else

      {

        // If the grid is empty let's assume we have to

        // add the columns that are needed for our data

 

        var col1 = postGrid.Columns.Add("Number", "Number");

        postGrid.Columns.Add("Title", "Title");

        var col3 =

          postGrid.Columns.Add("BlogLinkText", "Blog Link Text");

        var col4 = postGrid.Columns.Add("BlogLink", "Blog Link");

        var col5 =

          postGrid.Columns.Add("WLWLinkText", "WLW Link Text");

        var col6 =

          postGrid.Columns.Add("LiveWriterLink", "Live Writer Link");

 

 

        // The first column is numeric

 

        postGrid.Columns[col1].DefaultCellStyle.Alignment =

          DataGridViewContentAlignment.MiddleRight;< /p>

        postGrid.Columns[col1].ValueType = typeof(int);

 

        // We'll set the link columns to be the size of the header

        // rather than the content

 

        postGrid.Columns[col3].AutoSizeMode =

          DataGridViewAutoSizeColumnMode.ColumnHeader;

        postGrid.Columns[col4].AutoSizeMode =

          DataGridViewAutoSizeColumnMode.ColumnHeader;

        postGrid.Columns[col5].AutoSizeMode =

          DataGridViewAutoSizeColumnMode.ColumnHeader;

        postGrid.Columns[col6].AutoSizeMode =

          DataGridViewAutoSizeColumnMode.ColumnHeader;

      }

 

      // Let's disable the UI controls while we query our posts

 

      EnableControls(false);

 

      // And enable the grid control

 

      postGrid.Enabled = true;

 

      // Extract some info from the UI that willl be passed to

      // our main function

 

      string blogId = blogIdBox.Text;

      string domain = domainBox.Text;

      int start = Int32.Parse(startBox.Text);

      int inc = Int32.Parse(incBox.Text);

 

      await PopulatePostInfo(domain, blogId, start, inc, postGrid);

 

      EnableControls(true);

    }

 

    private async Task PopulatePostInfo(

      string domain, string blogId, int start, int inc,

      DataGridView grid

    )

    {

      // String to ask Excel to turn the current cell into a

      // link to the cell to the left

 

      const string toLink = "=HYPERLINK(INDIRECT(\"RC[-1]\",0))";

 

      int i = start;

 

      using (var client = new WebClient())

      {

        // First let's just find out how many posts there are

        // on this blog

 

        var initRes = await GetPosts(client, blogId, 0, 1);

        if (String.IsNullOrEmpty(initRes))

        {

          MessageBox.Show("Unable to access blog from provided ID");

          return;

        }

 

        var jo = JObject.Parse(initRes);

        var total = (int)jo["totalResults"];

 

        // While we haven't gotten to the end of our blogs or

        // hit an error, keep going

 

        while (i < total)

        {

          // Get the next batch of posts

 

          var results = await GetPosts(client, blogId, inc, i);

 

          if (
String.IsNullOrEmpty(results)) break;

 

          // Parse the returned JSON, using LINQ to extract

          // an IEnumerable of tuples of the title string

          // and the post ID

 

          var o = JObject.Parse(results);

          var posts =

            from p in o["entries"]

            select

              new Tuple<string, string, string>(

                (string)p["title"],

                (string)p["urlId"],

                (string)p["permalinkUrl"]

              );

 

          int j = 0; // An index into this batch

          foreach (var post in posts)

          {

            // Add our new record to the gridview

 

            int row = grid.Rows.Add();

            int idx = 0;

 

            // Set the post count (counting down to 1)

            // in the first column

 

            grid.Rows[row].Cells[idx++].Value =

              (total + 1 - (i + j)).ToString();

 

            // Let's go an extra step and decode any HTML

            // characters (e.g. &quot; and &dash) found

            // in the post title before setting that in

            // the second column

 

            var sw = new StringWriter();

            HttpUtility.HtmlDecode(post.Item1, sw);

            string title = sw.ToString();

            grid.Rows[row].Cells[idx++].Value = title;

 

            // Insert the blog link next

 

            grid.Rows[row].Cells[idx++].Value = post.Item3;

 

            // Hardcode a standard value in the next column:

            // this will create a link in Excel based on the

            // value in the previous column

 

            grid.Rows[row].Cells[idx++].Value = toLink;

 

            // Generate the text for the link based on the

            // domain and the post ID and place it in the

            // next column

 

            string link =

              String.Format(

                "wlw://{0}/?postid={1}", domain, post.Item2

              );

            grid.Rows[row].Cells[idx++].Value = link;

 

         
60;  // Hardcode a standard value in the next column:

            // this will create a link in Excel based on the

            // value in the previous column

 

            grid.Rows[row].Cells[idx].Value = toLink;

 

            j++;

          }

 

          // We didn't get any posts in this batch, so let's

          // break out of the while loop

 

          if (j == 0) break;

 

          // Scroll the grid view down so the bottom of it

          // is visible

 

          grid.FirstDisplayedScrollingRowIndex = grid.RowCount - 1;

 

          // Increment the counter ready for the next batch

 

          i += inc;

        }

      }

    }

 

    // Helper function to get a batch of posts from the blog

 

    private async Task<string> GetPosts(

      WebClient client, string blogId, int inc, int i

    )

    {

      // Generate the URL for the query

 

      var url = string.Format(

        "http://api.typepad.com/blogs/{0}/" +

        "post-assets.json?start-index={1}&max-results={2}",

        blogId, i, inc

      );

 

      // Try to execute it asynchronously via the WebClient

      // passed in (if it fails we'll just return an empty

      // string)

 

      try

      {

        return await client.DownloadStringTaskAsync(url);

      }

      catch

      {

        return "";

      }

    }

 

    private void copyButton_Click(object sender, EventArgs e)

    {

      // Copy the contents of the gridview to the clipboard

 

      postGrid.ClipboardCopyMode =

        DataGridViewClipboardCopyMode.EnableWithoutHeaderText;

      postGrid.SelectAll();

      var obj = postGrid.GetClipboardContent();

      postGrid.ClearSelection();

      Clipboard.SetDataObject(obj, true);

    }

  }

}

Hopefully this tool will be of some use to people who want an offline set of links into this blog or for my fellow bloggers who want to use it to create a set of Live Writer links to their own posts.

The tool could clearly be enhanced to perform an incremental download & update, but as bloggers can always access recent posts from within Live Writer they shouldn't need to run it very often once they have a populated Excel spreadsheet. And the code is provided for people to extend as they wish. 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *