Search
Joe Pruitt - A Software Architect's take on Network Security
You are here: DevCentral > Weblogs

Wednesday, June 17, 2009 #


PowerShell-twitpicTwitter, for those who don’t know about it, is a status updating service that is all the rage nowadays.  It’s popularity is primarily due to it’s simplistic nature.  You post a 140 character status (known as a “tweet”) about what you are doing.  You can also friend other folks to see what they are doing.  If you are still lost, read up on the entry in Wikipedia and you should get the gist of it. 

The downside to this minimalistic approach is that it does not allow for rich multimedia content such as images or videos to be associated with your “tweets”.  Let’s face it, posting a tweet about an awesome dinner you just had isn’t as good as posting a picture you took of it.  To account for this, various services have arisen to offload the image or video storage for twitter and many of the Twitter clients out there have incorporated these services into their apps.  TwitPic was an early entry into this category of services.  They allow you to post an image to their site while posting a “tweet” to Twitter including a link to the image.  Fortunately for us, TwitPic exposes their API to allow applications to integrate their features.

Since I’ve already tackled the PowerShell Twitter functionality with my PoshTweet library, I figured the next logical step would be to write a TwitPic library to enable posting images to TwitPic through their API.

The biggest challenge in writing this library was getting the multipart/form-data POST format working correctly.  Luckily I stumbled across a post by Martin Normark on his blog containing a C# TwitPic API client.  It was fairly straightforward coming up with a PowerShell conversion of his C# code.

My library includes the following functions

  • Get-EncodedDataFromFile – Encode a local image file into a string.
  • Get-ImageContentType – Get the Content Type for a given image file (ie. image/jpeg).
  • Execute-HTTPPostCommand – Send a HTTP POST command containing the multipart/form-data request.
  • Post-TwitPic – The main function that takes an image, posts it to TwitPic and optionally posts a Tweet to Twitter.

Without further ado, here’s the code:

   1: param(
   2:   [System.IO.FileInfo]$file = $null,
   3:   [string]$twitteruser = $null,
   4:   [string]$twitterpass = $null,
   5:   [string]$message = $null
   6: );
   7:  
   8: $CODEPAGE = "iso-8859-1";
   9:  
  10: $TWITPIC_API_UPLOAD = "http://twitpic.com/api/upload";
  11: $TWITPIC_API_UPLOADANDPOST = "http://twitpic.com/api/uploadAndPost";
  12:  
  13: #--------------------------------------------------------------------------------------------
  14: # function Get-EncodedDataFromFile
  15: #--------------------------------------------------------------------------------------------
  16: function Get-EncodedDataFromFile()
  17: {
  18:   param(
  19:     [System.IO.FileInfo]$file = $null,
  20:     [string]$codePageName = $CODEPAGE
  21:   );
  22:   
  23:   $data = $null;
  24:  
  25:   if ( $file -and [System.IO.File]::Exists($file.FullName) )
  26:   {
  27:     $bytes = [System.IO.File]::ReadAllBytes($file.FullName);
  28:     if ( $bytes )
  29:     {
  30:       $enc = [System.Text.Encoding]::GetEncoding($codePageName);
  31:       $data = $enc.GetString($bytes);
  32:     }
  33:   }
  34:   else
  35:   {
  36:     Write-Host "ERROR; File '$file' does not exist";
  37:   }
  38:   $data;
  39: }
  40:  
  41: #--------------------------------------------------------------------------------------------
  42: # function Get-ImageContentType
  43: #--------------------------------------------------------------------------------------------
  44: function Get-ImageContentType()
  45: {
  46:   param([System.IO.FileInfo]$file = $null);
  47:   $contentType = $null;
  48:   $contentTypeMap = @{
  49:     ".jpg"  = "image/jpeg";
  50:     ".jpeg" = "image/jpeg";
  51:     ".gif"  = "image/gif";
  52:     ".png"  = "image/png";
  53:     ".tiff" = "image/tiff";
  54:   }
  55:   
  56:   if ( $file )
  57:   {
  58:     $contentType = $contentTypeMap[$file.Extension.ToLower()];
  59:   }
  60:   $contentType;
  61: }
  62:  
  63: #----------------------------------------------------------------------------
  64: # function Execute-HTTPPostCommand
  65: #----------------------------------------------------------------------------
  66: function Execute-HTTPPostCommand()
  67: {
  68:   param(
  69:     [string] $url = $null,
  70:     [string] $data = $null,
  71:     [System.Net.NetworkCredential]$credentials = $null,
  72:     [string] $contentType = "application/x-www-form-urlencoded",
  73:     [string] $codePageName = "UTF-8",
  74:     [string] $userAgent = $null
  75:   );
  76:  
  77:   if ( $url -and $data )
  78:   {
  79:     [System.Net.WebRequest]$webRequest = [System.Net.WebRequest]::Create($url);
  80:     $webRequest.ServicePoint.Expect100Continue = $false;
  81:     if ( $credentials )
  82:     {
  83:       $webRequest.Credentials = $credentials;
  84:       $webRequest.PreAuthenticate = $true;
  85:     }
  86:     $webRequest.ContentType = $contentType;
  87:     $webRequest.Method = "POST";
  88:     if ( $userAgent )
  89:     {
  90:       $webRequest.UserAgent = $userAgent;
  91:     }
  92:     
  93:     $enc = [System.Text.Encoding]::GetEncoding($codePageName);
  94:     [byte[]]$bytes = $enc.GetBytes($data);
  95:     $webRequest.ContentLength = $bytes.Length;
  96:     [System.IO.Stream]$reqStream = $webRequest.GetRequestStream();
  97:     $reqStream.Write($bytes, 0, $bytes.Length);
  98:     $reqStream.Flush();
  99:     
 100:     $resp = $webRequest.GetResponse();
 101:     $rs = $resp.GetResponseStream();
 102:     [System.IO.StreamReader]$sr = New-Object System.IO.StreamReader -argumentList $rs;
 103:     $sr.ReadToEnd();
 104:   }
 105: }
 106:  
 107: #--------------------------------------------------------------------------------------------
 108: # function Post-TwitPic
 109: #--------------------------------------------------------------------------------------------
 110: function Post-TwitPic()
 111: {
 112:   param
 113:   (
 114:     [System.IO.FileInfo]$file = $null,
 115:     [string]$twitteruser = $null,
 116:     [string]$twitterpass = $null,
 117:     [string]$message = $null
 118:   );
 119:   
 120:   if ( $file -and $twitteruser -and $twitterpass )
 121:   {
 122:     $boundary = [System.Guid]::NewGuid().ToString();
 123:     $header = "--{0}" -f $boundary;
 124:     $footer = "--{0}--" -f $boundary;
 125:   
 126:     [System.Text.StringBuilder]$contents = New-Object System.Text.StringBuilder;
 127:     [void]$contents.AppendLine($header);
 128:  
 129:     $filedata = Get-EncodedDataFromFile -file $file -codePageName $CODEPAGE;
 130:     if ( $filedata )
 131:     {
 132:       $fileContentType = Get-ImageContentType -file $file;
 133:       $fileHeader = "Content-Disposition: file; name=""{0}""; filename=""{1}""" -f "media", $file.Name;
 134:  
 135:       [void]$contents.AppendLine($fileHeader);
 136:       [void]$contents.AppendLine("Content-Type: {0}" -f $fileContentType);
 137:       [void]$contents.AppendLine();
 138:       [void]$contents.AppendLine($fileData);
 139:  
 140:       [void]$contents.AppendLine($header);
 141:       [void]$contents.AppendLine("Content-Disposition: form-data; name=""username""");
 142:       [void]$contents.AppendLine();
 143:       [void]$contents.AppendLine($twitteruser);
 144:       
 145:       [void]$contents.AppendLine($header);
 146:       [void]$contents.AppendLine("Content-Disposition: form-data; name=""password""");
 147:       [void]$contents.AppendLine();
 148:       [void]$contents.AppendLine($twitterpass);
 149:       
 150:       $url = $TWITPIC_API_UPLOAD;
 151:       if ( $message )
 152:       {
 153:         [void]$contents.AppendLine($header);
 154:         [void]$contents.AppendLine("Content-Disposition: form-data; name=""message""");
 155:         [void]$contents.AppendLine();
 156:         [void]$contents.AppendLine($message);
 157:         
 158:         $url = $TWITPIC_API_UPLOADANDPOST;
 159:       }
 160:       
 161:       [void]$contents.AppendLine($footer);
 162:       
 163:       $contents.ToString() > ".\out.txt";
 164:       
 165:       $postContentType = "multipart/form-data; boundary={0}" -f $boundary;
 166:       
 167:       [xml]$resp = Execute-HTTPPostcommand -url $url -data $contents.ToString() `
 168:         -contentType $postContentType -codePageName $CODEPAGE -userAgent "PoshTwitPic";
 169:       
 170:       if ( $resp )
 171:       {
 172:         switch ($resp.rsp.stat)
 173:         {
 174:           "ok" {
 175:             $obj = 1 | select mediaid, mediaurl;
 176:             $obj.mediaid = $resp.rsp.mediaid;
 177:             $obj.mediaurl = $resp.rsp.mediaurl;
 178:             $obj;
 179:           }
 180:           "fail" {
 181:             $errcode = $resp.rsp.err.code;
 182:             $errmsg = $resp.rsp.err.msg;
 183:             Write-Host "Post Error: Code = '$errcode'; Message = '$errmsg'";
 184:           }
 185:         }
 186:       }
 187:     }
 188:   }
 189:   else
 190:   {
 191:     Write-Host "USAGE: Post-TwitPic.ps1 -file file -twitteruser user -twitterpass pass";
 192:   }
 193: }
 194:  
 195: if ( $file -and $twitteruser -and $twitterpass )
 196: {
 197:   Post-TwitPic -file $file -twitteruser $twitteruser -twitterpass $twitterpass -message $message;
 198: }
 199: else
 200: {
 201:   Write-Host "USAGE: Post-TwitPic.ps1 -file file -twitteruser user -twitterpass pass";
 202: }

The entire script can be downloaded from here: Post-TwitPic.ps1

Happy Tweeting!

Blog Stats

Posts:379
Comments:1067
Stories:1
Trackbacks:301
  

Article Categories

  iRules
  

Image Galleries

  

Joe's bookshelf: read

The Lost Gate
4 of 5 stars
This one started slow but I got really got into it about 1/3 of the way through. If you are an Ender's Game fan, you'll probably like this one as well.

goodreads.com


82,243 Members in 102 Countries and Growing!

Join DevCentral Today!

About DevCentral

DevCentral has been a successful, thriving community for many years. We have always strived to bring you the best technical documentation, discussion forums, blogs, media and much more that we can.

So dive in, get familiar with DevCentral. We hope you like it, we hope it makes your job easier, and lets you get that much more power out of the community. To learn more, make sure to check out the Getting Started section. And if you have any problems, or think something could be easier to use, drop us a line to let us know.

Got It !

We've received your comment and transmitted it directly to DevCentral HQ.

Thanks for taking time to let us know what's on your mind. At DevCentral | Community Matters!

Get In Touch With Us

Have questions, suggestions or just want to get something off your chest?

Use our handy form below to Direct Connect with DevCentral Mission Control.

Send Us Feedback       or