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:
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:
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).
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.
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.
The links for Live Writer do indeed launch the product…
… which pulls down the post’s contents and loads them into the editor:
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;
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. " 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;
// 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. :-)