Slideshare API Upload

I've been experimenting a little with Slideshare as a mechanism to make our slidedecks more broadly available. I spent a fun morning last week emailing 15 slide decks to the Slideshare site. When they didn't appear by the next day I was a little surprised. When they hadn't appeared a week later I thought I'd better check what was going on.

The response I got was "upload by email and by url are disabled at the moment while we fix a few issues in our upload process". Fair enough. It might have been nice to put a message to that effect on the site or disable the "Email Upload" link or perhaps an auto-reply. Any of those might have saved me a few hours of effort. Anyway, what to do?

Slideshare does have an API and they've even built a .NET API Kit. A quick check of the documentation revealed an upload_slideshow resource. Great news! Unfortunately it's not exposed in the .NET API Kit. Booo! So I set about making some changes to the API Kit to allow me to do a bulk upload with titles, descriptions, tags etc (the big advantage over using the Bulk upload facility on the site).

It wasn't as easy as I thought as you have to manually craft the elements of the multipart/form-data request. In the end I came across this article on Code Project which gave me most of what I needed. After that it was a question of sniffing requests with Fiddler comparing the Slideshare Upload sample with my code. In the end I arrived at something that works. It's not terribly pretty but it works.

This is based on the .NET API Kit so download and unpack that first as a start point. In there you'll find sampleprogram.cs. I've added a simple class to represent the Slidedeck metadata and modified the sampleprogram class to drive the uploads from an XML file:

 class Slidedeck
{
    public string Title { get; set; }
    public string Description { get; set; }
    public string Tags { get; set; }
    public string Filepath { get; set; }
}

class sampleprogram
{
    static void Main(string[] args)
    {
        slideshare ss = new slideshare();
        ss.initializeKeys(key, secret);

        XDocument xdoc = XDocument.Load("UploadManifest.xml");

        var query = from x in xdoc.Descendants("Slidedeck")
                    select new Slidedeck
                    {
                        Title = (string)x.Element("Title"),
                        Description = (string)x.Element("Description"),
                        Tags = (string)x.Element("Tags"),
                        Filepath = (string)x.Element("Filepath")
                    };

        foreach (var item in query.ToList())
        {
            using (FileStream fs = File.Open(item.Filepath, FileMode.Open))
            {
                Console.WriteLine("Uploading {0}...", item.Title);
                Console.WriteLine(
                    ss.uploadSlidedeck(username, password,
                    item.Title,
                    item.Description,
                    item.Tags,
                    fs));
                Console.WriteLine("Finished Uploading {0}...", item.Title);
                Console.WriteLine();
            }
        }

        Console.ReadLine();

Then in the slideshare class in slideshare.cs I added a new uploadSlidedeck() method:

 public string uploadSlidedeck(
    string Username, 
    string Password, 
    string Title, 
    string Description, 
    string Tags, 
    FileStream Sourcefile)
{
    Dictionary<string, string> parameters = new Dictionary<string, string>()
    {
        { "api_key", api_key },
        {"sharedsecret", shared_secret },
        {"ts", timestamp},
        {"hash", CalculateSHA1(shared_secret + timestamp, Encoding.UTF8)},
        {"username", Username},
        {"password", Password},
        {"slideshow_title", Title},
        {"slideshow_description", Description},
        {"slideshow_tags", Tags}
    };

    return executePostCommand(
        "http://www.slideshare.net/api/1/upload_slideshow", 
        parameters, 
        Sourcefile);
}

and in turn a modified version of the executePostCommand() method:

 private string executePostCommand(
    string uri,
    Dictionary<string, string> parameters,
    Stream contentStream)
{
    if (api_key == null || shared_secret == null || timestamp == null)
        return "Keys must be initialized first";

    string boundary = "----------" + DateTime.Now.Ticks.ToString("x");

    HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uri);

    webRequest.ContentType = "multipart/form-data; boundary=" + boundary;
    webRequest.Method = "POST";
    webRequest.Timeout = 600000;

    Stream os = null;

    try
    {
        StringBuilder sb = new StringBuilder();

        // Add each of the parameters to the request
        foreach (var item in parameters)
        {
            sb.Append("--");
            sb.Append(boundary);
            sb.Append("\r\n");
            sb.Append("Content-Disposition: form-data; name=\"");
            sb.Append(item.Key);
            sb.Append("\"");
            sb.Append("\r\n\r\n");
            sb.Append(item.Value);
            sb.Append("\r\n");
        }

        // Add the file upload itself to the request
        sb.Append("--");
        sb.Append(boundary);
        sb.Append("\r\n");
        sb.Append("Content-Disposition: form-data; name=\"");
        sb.Append("slideshow_srcfile");
        sb.Append("\"; filename=\"");
        sb.Append("Test.ppt");
        sb.Append("\"");
        sb.Append("\r\n");
        sb.Append("Content-Type: ");
        sb.Append("application/vnd.ms-powerpoint");
        sb.Append("\r\n");
        sb.Append("\r\n");

        string postHeader = sb.ToString();
        byte[] postHeaderBytes = Encoding.UTF8.GetBytes(postHeader);
        byte[] boundaryBytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");

        // request will be postHeader (parameters) + contentStream (file) + boundary
        webRequest.ContentLength = postHeaderBytes.Length +
                                   contentStream.Length +
                                   boundaryBytes.Length;

        os = webRequest.GetRequestStream();

        // Write the parameters to the request stream
        os.Write(postHeaderBytes, 0, postHeaderBytes.Length);


        // Copy from the file to the request stream
        byte[] buffer = new byte[32768];
        while (true)
        {
            int read = contentStream.Read(buffer, 0, buffer.Length);
            if (read <= 0)
                break;

            os.Write(buffer, 0, read);
        }

        // Write the final boundary to the request stream
        os.Write(boundaryBytes, 0, boundaryBytes.Length);

    }
    catch (WebException ex)
    {
        return ex.ToString();
    }

    finally
    {
        if (os != null)
        {
            os.Close();
        }
    }

    try
    {
        WebResponse webResponse = webRequest.GetResponse();
        if (webResponse == null)
        { return null; }
        StreamReader sr = new StreamReader(webResponse.GetResponseStream());
        return sr.ReadToEnd().Trim();
    }
    catch (WebException ex)
    {
        return ex.ToString();
    }
}

And finally you need an XML file which I named UploadManifest.xml which describes the slidedecks you want to upload:

 <?xml version="1.0" encoding="utf-8"?>
<Slidedecks>

  <Slidedeck>
    <Title></Title>
    <Description></Description>
    <Tags></Tags>
    <Filepath></Filepath>
  </Slidedeck>
  
</Slidedecks>

And believe it or not, it looks as though - just as I post this - my emailed slide decks (mailed on 2/12) have started to come through. At exactly the same time as my API uploads (bit of a coincidence). So I now have duplicates of everything. Argh...

Technorati Tags: slideshare,api,multipart/form-data,.net,upload